Django-C005-说说MVT之外的事情
此文章完成度【100%】留着以后忘记的回顾。多写多练多思考,我会努力写出有意思的demo,如果知识点有错误、误导,欢迎大家在评论处写下你的感想或者纠错。
【Django version】: 2.1
【pymysql version】:0.9.3
【python version】: 3.7
【Pillow version】:6.0.0
常用
到此为止,关于Django框架的三大块MVT已经告一段落,让我们扩充一些Django其他的功能,为了更好的完成开发,而努力吧
主要知识点如下:
-
静态文件处理
-
中间件
-
上传图片
-
admin站点
-
分页
-
示例:省市区选择、jquery、ajax
接下来才是每天都最重要的环节,重复重复不断重复的创建项目:
创建项目test5
django-admin startproject test5
进入到项目目录test5,创建应用school
cd test5 python manage.py startapp school
在test5下的settings中的INSTALLED_APPS中注册应用
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'school', ]
在test5下的settings中的DATABASES中指定数据库引擎,并配置
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'school', 'USER': 'root', 'PASSWORD': 'toor', 'HOST': 'localhost', 'PORT': 3306, } }
在test5下的settings中的TEMPLATS中添加模板路径
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
创建模板目录,并将所有school的html添加到school这个文件夹中
在test5下的urls.py,添加url配置指向到school应用下的urls.py中
from django.contrib import admin from django.urls import path, include app_url_patterns = ('school.urls', 'school') urlpatterns = [ path('admin/', admin.site.urls), path('', include(app_url_patterns, namespace='school')), ]
在school应用下创建urls.py 并且添加index测试路径
from django.urls import re_path, include from . import views urlpatterns=[ re_path(r'^$', views.index, name='index'), ]
在school应用下的views.py中创建index视图
from django.shortcuts import render def index(request): return render(request, 'school/index.html')
在模板templates/school目录下创建index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>常用技术练习</title> </head> <body> <h1>常用技术练习</h1> </body> </html>
在应用school下的模型models.py中定义AreaInfo
from django.db import models class AreaInfo(models.Model): """地区模型类""" name = models.CharField(max_length=50) area_parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
静态文件
项目中的CSS、图片、js都是静态文件。一般会将静态文件放到一个单独的目录中,以方便管理。在html页面中调用,也需要指定静态文件的路径,django中提供了一个解析的方式配置静态文件路径。静态文件可以放在项目根目录下,也可以放在应用的目录下, 由于有些静态文件在项目中是通用,所以推荐放在项目的根目录下、方便使用。
创建静态文件存放目录 ,这里选择的是在项目的根目下创建,并且分别常见css、js、img目录
在应用下的urls.py中创建img_static的url规则
from django.urls import re_path, include from . import views urlpatterns=[ re_path(r'^$', views.index, name='index'), re_path(r'^img_static$', views.img_static), ]
在视图views.py中定义img_static视图函数
from django.shortcuts import render ... def img_static(request): return render(request, 'school/img_static.html')
在templates/school下创建img_static.html
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <img src="{% static 'img/cat.jpg'%}"> <img src="static/img/cat.jpg"> </body> </html>
当然这些还是不够的,最重要的一步就是,在test5/settings文件中配置路径
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]
保存图片到static/img/目录下
访问127.0.0.1:8000/img_static,两个img标签的图片都显示在浏览器中,所以这两种写路径的方式都可以。
配置静态文件
Django提供了一种配置,可以在html页面中隐藏真是路径
<1> 在test5/settings.py中修改STATIC_URL项
# STATIC_URL = '/static/' STATIC_URL = '/abc/'
再次访问127.0.0.1:8000/img_static,可以看出动态获取的还是可以显示,但是静态写入的地址就不能生效了
为了安全考虑可以通过配置项隐藏真实图片路径
注意:这种方案可以隐藏真实的静态文件路径,但是结合Nginx布署时,会将所有的静态文件都交给Nginx处理,而不用转到Django部分,所以这项配置就无效了。
如果你想禁用一些非法操作的用户,那么你需要怎么呢?
当然就是获取用户的IP将它废掉,那么如何获取IP呢,我就不再罗嗦
# 获取浏览器端的IP地址 # 使用request对象的META属性 request.META['REMOTE_ADDR']
既然要封其IP,断其连接,那就是我们应用下的所有网页都不允许访问,我们这就可以写成一个函数装饰器
在视图中views.py 定义装饰器函数
from django.shortcuts import render from django.http import HttpResponse # 禁用的IP列表 EXCLUDE_IPS = ['192.168.3.5'] # 这个ip是我自己的, def blocked_ips(view_func): """阻止访问IP的装饰器函数""" def wrapper(request, *view_args, **view_kwargs): user_ip = request.META['REMOTE_ADDR'] # 获取用户的ip if user_ip in EXCLUDE_IPS: # 直接返回一个response页面给他 return HttpResponse('<h1>禁用访问111</h1>') return view_func(request, *view_args, **view_kwargs) return wrapper # 将所有禁止访问的页面都添加一个函数装饰器 @blocked_ips # 阻止IP访问的函数装饰器 def index(request): user_ip = request.META['REMOTE_ADDR'] return render(request, 'school/index.html')
访问127.0.0.1:8000 因为禁止的是我自己ip,所以成功得到了禁止访问111
那么你会说这依然还是很麻烦。那么就让我们学习一下中间件,看看他当中是否有能解决,我们这个问题的能力
中间件
Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出。中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性,其他的MVC框架也有这个功能,名称为IoC。
>>> : 接下来是插播,理解不深也不用着急,下面会有更全面的解释
那么接下来说人话,就好比刚才用函数装饰器来阻断一个ip的访问,我们会在他访问每一个页面的时候返回一个response给他。所以他看到的是我们准备给他的一个针对他的页面。如果启用中间件的话,就比那个简单得多了,首先应用好比一个大楼,在发来请求的时候,在访问视图之前,在通过url匹配之后,我们准备函数阻断他的行为,这样就不需要在每一个函数上给装了一个防盗门(装饰器),就好比直接在大楼外安插了一个保安对每一个违规请求者, 在直接拒绝在大门外,之后的扩展的新视图函数也不需要依次添加装饰器。
<1> 将之前的装饰器注释掉,或者删除掉,因为你了解了这个方法,就不会在管那些装饰器啥啥的。
<2> 在school应用下创建一个中间件名字叫middleware.py的文件,并在里面创建一个BlankedIPMiddleware这个中间件类
< 2-1 > 简单的讲解一下,先从我们导入的这个类说起 【from django.utils.deprecation import MiddlewareMixin】,为啥是它,因为NB,为啥NB,Django提供了 django.utils.deprecation.MiddlewareMixin 来简化中间件类的创建,这些中间件类同时兼容 MIDDLEWARE 和 旧的MIDDLEWARE_CLASSES。所以我们这里就继承自MiddlewareMixin
在school目录下创建middleware.py文件
from django.http import HttpResponse from django.utils.deprecation import MiddlewareMixin class BlockedIPMiddleware(MiddlewareMixin): """阻断ip访问的中间件类""" EXCLUDE_IPS = ['192.168.3.5','127.0.0.1'] # 定义一个类属性,来记录所有阻止访问的IP # 处理视图前,url匹配成功后,调用。 # 返回None就会继续接下来的操作,什么都没有发生一样或直接返回HttpResponse对象阻断那些不友好者 def process_view(self, request, view_func, *view_args, **view_kwargs): user_ip = request.META['REMOTE_ADDR'] # 获取当前的浏览器提交的IP if user_ip in BlockedIPMiddleware.EXCLUDE_IPS: return HttpResponse('<h1>通过中间件禁止访问</h1>')
<3>在test5/settings中注册中间件
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'school.middleware.BlockedIPMiddleware', # 注册中间件 ]
<4>访问12.0.0.1:8000
每个中间件组件都负责执行一些特定的功能,例如,Django中包含的一个中间件组件,AuthenticationMiddleware它将用户与使用的请求会话关联起来。接下来我将讲解一下中间件的工作原理,如何注册中间件,你如何创建自己的中间件,Django有一些内置的中间件,您可以立即使用它们,他们已经设置在settings中的MIDDLEWARE中,并且后续需要注册的中间件也在这里:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
中间件将获取一个get_response 然后返回一个中间件,它接受请求并返回响应,就像视图一样。例如:
def simple_middleware(get_response): # 只初始化一次 def middleware(request): # 这里的代码会在每一个请求之前执行 # 调用视图(和之后的中间件) response = get_response(request) # 这里的代码会在请求/响应之后执行 # 视图已经被调用 return response return middleware
或者他也可以写成一个类
class SimpleMiddleware: def __init__(self, get_response): self.get_response = get_response # 只初始化一次 def __call__(self, request): # 这里的代码会在每一个请求之前执行 # 调用视图(和之后的中间件) response = self.get_response(request) # 这里的代码会在请求/响应之后执行 # 视图已经被调用 return response
初始化:必须只有get_response参数,你也可以使用中间件初始化一些全局状态。请注意这两点:
- Django的get_response只是来初始化你的中间件,不能在这里定义其他参数
- 当Web应用运行时,__init__() 只被调用一次
def __init__(self, get_response): self.get_response = get_response
在请求阶段,在调用视图之前,Django按中间件中定义的顺序自上向下定义中间件。您可以把它看作是一个洋葱:每个中间件类都是一个“层”,它们包裹在视图之外,视图位于洋葱的核心。如果请求通过洋葱的所有层(每个层都调用get_response将请求传递到下一层),一直传递到核心视图,那么在返回的过程中,响应将通过每一层(在有内向外)。
除了前面描述的基本请求/响应中间件模式之外,您还可以向基于类的中间件添加其他三种特殊方法:
<1> 处理视图前:在每一个请求上,视图函数调用之前,返回None找到指定的视图或HttpResponse对象直接返回:
def process_view(self, request, view_func, *view_args, **view_kwargs): pass
它会返回None或者HttpResponse对象,如果返回None,Django将继续处理这个请求,执行其他中间件中的process_view(),然后执行匹配的视图。如果它返回HttpResponse对象,Django就不会调用视图,它将把响应中间件应用到HttpResponse并返回结果。
注意:请不要在中间件中访问request.POST
<2> 异常处理:当视图抛出异常时调用,在每一个请求上调用,返回一个HttpResponse对象返回给浏览器,否则,将启动缺省异常处理:
process_exception(self, request,exception)
<3>模版响应:在视图完成后执行,如果响应实例具有render()方法,表明它是一个模板响应或等效的响应。
process_template_response(request, response)
中间件常见的用途:缓存、会话认证、日志记录、异常
中间件执行流程
演示
在school下的middleware.py中创建中间件类:
class MyMiddleware: def __init__(self, get_response): print('-----init-----') self.get_response = get_response def __call__(self, request): print("中间件开始") response = self.get_response(request) print("中间件结束") return response def process_view(self, request, view_func, *view_args, **view_kwargs): print("请求实际函数前执行")
在settings.py中,向MIDDLEWARE中注册中间件:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', # 'school.middleware.BlockedIPMiddleware', 'school.middleware.MyMiddleware', ]
修改views.py中index视图:
from django.shortcuts import render def index(request): print("----index 视图执行-----") return render(request, 'school/index.html')
运行服务器
访问127.0.0.1:8000,然后再访问一次
__init__()只会在web服务运行时加载一次
注意:如果多个注册的中间件类中都有process_exception的方法,则先注册的后执行。
Admin站点
内容发布的部分由网站的管理员负责增、删、改、查数据的,开发这些重复的功能是一件单调乏味、缺乏创造性的工作,为此,Django能够根据自定义的模块类自动地生成管理模块。
在第一部分对管理站点坐了简单的介绍,现在我们将零碎的知识扩充一下, 在Django项目中默认启用admin管理站点。
第一步:准备工作:创建管理员的用户名和密码。
python manage.py createsuperuser
第二步:使用在应用的admin.py中注册模型类
from django.contrib import admin from school.models import AreaInfo admin.site.register(AreaInfo) # 将区域模型类注册到Admin站点
第三步:访问127.0.0.1:8000/admin
列表页显示效果
更改管理页展示效果
ModelAdmin类可以控制模型admin界面中的展示方式,主要包括在列表页的展示方式、添加修改页面的展示方式
第一步:在应用下的admin.py中,注册魔心那个累前定义管理类AreaAdmin
from django.contrib import admin from school.models import AreaInfo class AreaAdmin(admin.ModelAdmin): pass admin.site.register(AreaInfo)
管理类有两种使用方式:注册参数、装饰器
注册参数:打开应用下的admin.py文件,注册模型类
admin.site.register(AreaInfo, AreaAdmin)
装饰器:打开应用下的admin.py文件,在管理类上注册模型类
from django.contrib import admin from school.models import AreaInfo @admin.register(AreaInfo) class AreaAdmin(admin.ModelAdmin):
pass
更改列表页展示效果
列表页选项
页大小
默认每页显示100条数据,list_per_page属性
list_per_page = 100
打开应用下的admin.py文件,修改AreaAdmin类
from django.contrib import admin from school.models import AreaInfo @admin.register(AreaInfo) class AreaAdmin(admin.ModelAdmin): list_per_page = 20
访问127.0.0.1:8000/admin/school/areainfo/
“操作选项”的位置
顶部显示的属性,设置为True在顶不显示,设置为False不在顶部显示,默认为True
actions_on_top = True
效果区分
底部显示的属性,设置为True在底部显示,默认为False
actions_on_bottom = True # 设置选项在底部,默认值是False
列表中的列
属性
list_display = ['id', 'name', 'area_parent_id'] # 借用AreaInfo模型类的信息展示
点击列头可以进行排序的转换
将方法作为列
列可以是模型里的字段,也可以是模型里的方法,如果列表项显示的是方法,该方法必须要有返回值
修改一:在应用下的models.py文件中,修改AreaInfo类:
from django.db import models class AreaInfo(models.Model): """地区模型类""" name = models.CharField(max_length=50) area_parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True) def title(self): return str(self.name) + ":" +str(self.id)
修改二:在应用下的admin.py文件中,将方法名添加到列表中
from django.contrib import admin from school.models import AreaInfo @admin.register(AreaInfo) class AreaAdmin(admin.ModelAdmin): list_per_page = 20 # 每一页展示多少数据 actions_on_top = True # 设置选项在顶部,默认值是True actions_on_bottom = True # 设置选项在底部,默认值是False list_display = ['id', 'name', 'area_parent_id', 'title'] # 添加了一个方法名(title)
访问127.0.0.1:8000/admin/school/areainfo/?o=1
方法列默认是不能排序的。如果需要排序,需要为方法指定排序依据
在应用下的models.py文件中修改AreaInfo类
title.admin_order_field = 'id' # 设置title这个方法的排序依据为id
列头上的1和2是排序的优先级
列表题:列标题默认为属性或方法的名称,可以通过属性设置,需要先将模型字段封装成方法,在对方法使用这个属性,模型字段不能直接使用这个属性:short_description
在应用下的models.py文件,修改AreaInfo类
from django.db import models class AreaInfo(models.Model): """地区模型类""" name = models.CharField(max_length=50) area_parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True) def title(self): return str(self.name) + ":" +str(self.id) title.admin_order_field = 'id' title.short_description = '区域名称:id' # 更改列标题
访问127.0.0.1:8000/admin/school/areainfo/?o=1
关联对象
优化访问关联对象显示它的name属性
在应用下models.py文件中修改AreaInfo类:
from django.db import models class AreaInfo(models.Model): """地区模型类""" ... def parent(self): if self.area_parent is None: return "" return self.area_parent.name parent.short_description = "父级地区"
在应用下的admin.py中修改list_display
from django.contrib import admin from school.models import AreaInfo @admin.register(AreaInfo) class AreaAdmin(admin.ModelAdmin): list_per_page = 20 # 每一页展示多少数据 actions_on_top = True # 设置选项在顶部,默认值是True actions_on_bottom = True # 设置选项在底部,默认值是False # list_display = ['id', 'name', 'area_parent_id', 'title'] list_display = ['id', 'name', 'parent', 'title'] # 修改为parent
访问127.0.0.1:8000/admin/school/areainfo/?o=1
右侧栏过滤器
属性 list_filter
只能接受字段,会将对应字段的值列出来,用于快速郭列。一般用于重复值的字段。
在应用下的admin.py文件中修改AreaAdmin类
from django.contrib import admin from school.models import AreaInfo @admin.register(AreaInfo) class AreaAdmin(admin.ModelAdmin): ... list_filter = ['name']
访问127.0.0.1:8000/admin/school/areainfo/?o=1
搜索框
属性 search_fields
用于对指定字段的值进行搜索,支持模糊查询。类型为列表,表示这些字段上进行搜索。
在应用下的admin.py修改AreaAdmin类
from django.contrib import admin from school.models import AreaInfo @admin.register(AreaInfo) class AreaAdmin(admin.ModelAdmin): ... search_fields = ['id','name']
访问127.0.0.1:8000/admin/school/areainfo/?o=1
中文标题
在应用下的models.py文件中修改AreaInfo模型类,为字段指定verbose_name参数。第一个参数
from django.db import models class AreaInfo(models.Model): """地区模型类""" name = models.CharField('地区名称', max_length=50) ...
访问127.0.0.1:8000/admin/school/areainfo/?o=1
编辑页选项
显示字段顺序
属性fields,类型是个列表
在应用下的admin.py文件,修改AreaAdmin类
from django.contrib import admin from school.models import AreaInfo @admin.register(AreaInfo) class AreaAdmin(admin.ModelAdmin): ... fields = ['area_parent', 'name']
随便点击一行id,可以转到修改页面
Area parent点开下拉列表你会发现输出的都是AreaInfo object(id),这样会非常不好识别,选择父级非常的麻烦,这样我就需要想一个解决办法,把它格式化成字符串。
str方法考虑一下,在应用下打开models.py,修改AreaInfo类,添加str方法
from django.db import models class AreaInfo(models.Model): """地区模型类""" ... def __str__(self): return self.name
分组显示
属性fieldset
fieldset=( ('组1标题',{'fields':('字段1','字段2')}), ('组2标题',{'fields':('字段3','字段4')}), )
在应用下的admin.py文件修改AreaAdmin类:
from django.contrib import admin from school.models import AreaInfo @admin.register(AreaInfo) class AreaAdmin(admin.ModelAdmin): ... # fields = ['area_parent', 'name'] fieldset = ( ('基本',{'fields': ['name']}), ('高级',{'fields': ['area_parent']}), )
随便点击一个id
注意: fields与fieldsets两者需要选择一个使用
关联对象
在一对多的关系中,可以在一这边的编辑页面中编辑多这边的对象,嵌入多这边的对象的方式包括表格、块两种。类型是InlineModelAdmin:表示在模型的编辑页面嵌入关联模型的编辑,子类TabularInline:以表格的形式嵌入。子类StackedInline:以块的形式嵌入。
StackedInline:在应用下的admin.py文件中创建AreaStackedInline类,并且修改AreaAdmin类
from django.contrib import admin from school.models import AreaInfo class AreaStackedInline(admin.StackedInline): model = AreaInfo # 关联子对象 extra = 2 # 额外编辑2个子对象 @admin.register(AreaInfo) class AreaAdmin(admin.ModelAdmin): list_per_page = 20 # 每一页展示多少数据 actions_on_top = True # 设置选项在顶部,默认值是True actions_on_bottom = True # 设置选项在底部,默认值是False # list_display = ['id', 'name', 'area_parent_id', 'title'] list_display = ['id', 'name', 'parent', 'title'] list_filter = ['name'] search_fields = ['id','name'] # fields = ['area_parent', 'name'] fieldsets = ( ('基本',{'fields': ['name']}), ('高级',{'fields': ['area_parent']}), ) inlines = [AreaStackedInline]
刷新浏览器,下面将显示出所有关联:北京市:110100 的区域,并且还有可以额外增加两条地区的地方
下面我们感受一下表格的形式嵌入式什么感觉,注释你刚才添加块的代码。
TabularInline:在应用下修改admin.py文件,并创建AreaTabularInline类:
from django.contrib import admin from school.models import AreaInfo # class AreaStackedInline(admin.StackedInline): # model = AreaInfo # 关联子对象 # extra = 2 # 额外编辑2个子对象 class AreaTabularInline(admin.TabularInline): model = AreaInfo # 关联子对象 extra = 2 # 额外编辑2个子对象 @admin.register(AreaInfo) class AreaAdmin(admin.ModelAdmin): list_per_page = 20 # 每一页展示多少数据 actions_on_top = True # 设置选项在顶部,默认值是True actions_on_bottom = True # 设置选项在底部,默认值是False # list_display = ['id', 'name', 'area_parent_id', 'title'] list_display = ['id', 'name', 'parent', 'title'] list_filter = ['name'] search_fields = ['id','name'] # fields = ['area_parent', 'name'] fieldsets = ( ('基本',{'fields': ['name']}), ('高级',{'fields': ['area_parent']}), ) # inlines = [AreaStackedInline] inlines = [AreaTabularInline]
刷新页面,点击id进入修改模式,多观察一些数据
重写模板
第一步:在templates目录下创建admin目录
第二步:找到Django的项目下的admin文件夹,大家的路径应该都不一样。大概流程是你安装的Python路径下的Lib中找到一个叫site-packages的文件夹下面会有你安装的Django在找到contrib下就会有admin
C:\Users\Circle\AppData\Local\Programs\Python\Python37-32\Lib\site-packages\django\contrib\admin
第三步:将需要更改文件拷贝到自己项目下的templates里的admin,此处以base_site.html为例:
第四步:编辑base_site.html文件:
{% extends "admin/base.html" %} {% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} {% block branding %} <h1 id="site-name"><a href="{% url 'admin:index' %}"><h1>自定义的管理页模板</h1>{{ site_header|default:_('Django administration') }}</a></h1> {% endblock %} {% block nav-global %}{% endblock %}
刷新网页
多熟悉一下你想更改的模板的源码。你就可以创建一个属于你的模板,想更改什么就去寻找什么
上传图片
在Python中进行图片操作,需要安装PIL,可以使用pip安装
在Django中上传图片包括两种方式:
-
管理页面admin中上传
-
自定义form表单中上传图片
上传图片后,将图片存储在服务器上,然后将图片的路径存储在表中。
创建包含图片的模型类
将模板类的属性定义成models.ImageField类型。
在应用下的models.py文件中定义PicTest模型类
from django.db import models class PicTest(models.Model): pic = models.ImageField(upload_to='school')
生成迁移文件,执行迁移
python manage.py makemigrations
python manage.py migrate
打开项目下的settings.py文件设置图片保存到static目录下
MEDIA_ROOT=os.path.join(BASE_DIR,"static/media")
在static目录下创建media目录,在创建应用名的目录
在管理页面admin中上传图片
在应用下的admin.py文件,注册PicTest
admin.site.register(PicTest)
刷新页面,点击PicTest的增加,选择文件,然后找一张图片保存
查看数据库是否添加成功
数据保存的位置:static\media\school\
自定义form表单中上传图片
在应用下的views.py文件创建视图pic_upload
from django.shortcuts import render ... def pic_upload(request): return render(request, 'school/pic_upload.html')
在应用下的urls.py文件中配置url
re_path(r'^pic_upload$', views.pic_upload),
在templates下的school目录中创建pic_upload.html
在模板中定义上传表单要求:
- form的属性enctype="multipart/form-data"
- form的method为post
- input类型为file
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>上传图片</title> </head> <body> <form method="POST" action="/pic_handle" enctype="multipart/form-data"> {%csrf_token%} <input type="file" name="pic"><br> <input type="submit" value="上传"> </form> </body> </html>
在应用views.py文件中创建视图pic_handle,用于接收表单保存图片,request对象的FILES属性用于接收请求的文件、图片
from django.http import HttpResponse from django.conf import settings def pic_handle(request): p = request.FILES.get('pic') p_name = '%s/school/%s' % (settings.MEDIA_ROOT, p.name) with open(p_name, 'wb') as pic: for c in p.chunks(): pic.write(c) return HttpResponse('OK')
在应用下的urls.py 中配置url
re_path(r'^pic_handle$', views.pic_handle),
访问127.0.0.1:8000/pic_upload
会获得pic_handle返回的ok,查看static/media/school下是否的到文件
修改视图pic_handle,添加写入数据库的功能
from django.http import HttpResponse from django.conf import settings from school.models import PicTest def pic_handle(request): p = request.FILES.get('pic') p_name = '%s/school/%s' % (settings.MEDIA_ROOT, p.name) with open(p_name, 'wb') as pic: for c in p.chunks(): pic.write(c) pic_db = PicTest() # 创建PicTest模型类对象 pic_db.pic = 'school/' + p.name pic_db.save() return HttpResponse('OK')
查看数据库中是否写入
显示图片
在应用的views.py文件中创建pic_show视图
from django.shortcuts import render from school.models import PicTest def pic_show(request): pt = PicTest.objects.all() context = {'pt': pt} return render(request, 'school/pic_show.html', context)
在应用下的urls.py配置url
re_path(r'^pic_show$', views.pic_show),
在templates在school中创建pic_show.html文件
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>显示图片</title> </head> <body> {%for pic in pt%} <img src="{%static '' %}media/{{pic.pic}}"> {%endfor%} </body> </html>
访问127.0.0.1:8000/pic_show
分页
Django提供了数据分页的类,这些类被定义在django/core/paginator.py中,类Paginator用于对列进行一页n条数据的分页运算,类Page用于表示第m页的数据
Paginator类实例对象
-
方法_init_(列表,int):返回分页对象,第一个参数为列表数据,第二个参数为每页数据的条数。
-
属性count:返回对象总数。
-
属性num_pages:返回页面总数。
-
属性page_range:返回页码列表,从1开始,例如[1, 2, 3, 4]。
-
方法page(m):返回Page类实例对象,表示第m页的数据,下标以1开始。
Page类实例对象
-
调用Paginator对象的page()方法返回Page对象,不需要手动构造。
-
属性object_list:返回当前页对象的列表。
-
属性number:返回当前是第几页,从1开始。
-
属性paginator:当前页对应的Paginator对象。
-
方法has_next():如果有下一页返回True。
-
方法next_page_number(): 返回下一页的页码
-
方法has_previous():如果有上一页返回True。
-
方法previous_page_number():返回前一页的页码
-
方法len():返回当前页面对象的个数。
演示:
在应用下的views.py文件中创建视图page_test
from django.shortcuts import render from school.models import AreaInfo from django.core.paginator import Paginator
def page_test(request, pIndex): # 查询所有的省 area_list = AreaInfo.objects.filter(area_parent__isnull = True) # 将地区信息按一页10条显示 paginator = Paginator(area_list, 10) # 默认获取第一页 if pIndex == '': pIndex = '1' # 将字符串转换成int类型 pIndex = int(pIndex) # 获取第pIndex页的数据 page = paginator.page(pIndex) return render(request, 'school/page_test.html', {'page': page})
在应用下的urls.py文件中配置url
re_path(r'^page(?P<pIndex>\d*)$', views.page_test),
在templates文件夹下的school中创建page_test.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>分页展示</title> </head> <body> 显示当前页的地区信息:<br> <ul> {%for area in page%} <li>{{ area.id }} -- {{ area.name }}</li> {%endfor%} </ul> <hr> {%if page.has_previous%} <a href='/page{{ page.previous_page_number }}'>上一页</a> {%else%} 上一页 {%endif%} {%for pindex in page.paginator.page_range%} {%if page.number == pindex%} {{pindex}} {%else%} <a href='/page{{ pindex }}'>{{pindex}}</a> {%endif%} {%endfor%} {%if page.has_next%} <a href='/page{{ page.next_page_number }}'>下一页</a> {%else%} 下一页 {%endif%} </html>
访问127.0.0.1:8000/page
既然省地区信息的已经可以在页面显示,那么我们做一个关于地区的三级联动案例,这个案例需要在Django中使用到jquery的ajax进行数据交互,jquery框架提供了三种方法用于异步交互,$.ajax、$.get、$.post,由于CSRF约束,我们这里也只是查询信息,所以使用$.get演示:先观看一下最终实现效果:
第一步:将jquery文件拷贝到static/js目录下
第二步:在应用下的views.py文件中,添加areas视图
def areas(request): return render(request, 'school/areas.html')
第三步:在应用下的urls.py中配置url
re_path(r'^areas$', views.areas),
第四步:在templates中的school下创建areas.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>地区三级联动</title> {%load static%} <script type="text/javascript" src='{% static "js/jquery-1.12.4.min.js"%}'></script> <script type="text/javascript"> $(function(){ // ajax post请求方式的简写 第一个参数是访问的url,第二个参数是传入的数据, 第三个参数回调函数 // $.post('/prov', {'val':'value'}, function(data){ // }) //ajax get请求的简写,第一个参数是请求的url,第二个参数是回调函数 $.get('/prov', function(data){ // 回调函数 // 获取省列表 pro_data = data.area_data pro = $('#pro') // 遍历省的列表 for (var i = 0; i < pro_data.length; i++) { //将id和name分别获取 id = pro_data[i][0] name = pro_data[i][1] // 格式化option标签 option_str = '<option value=' + id + '>' + name + '</option>' // 将每一个标签添加到页面 pro.append(option_str) } }) //绑定pro下拉列表框的change事件,获得省下面的市的信息 $('#pro').change(function(){ //发起一个ajax请求,/city 获取省下面的市 //获取点击省的id pro_id = $(this).val() $.get('/prov'+pro_id, function(data){ //回调函数 city_data = data.area_data // append默认是追加模式,如果不清空,每次求情都会在最后面追加 city = $('#city') city.empty().append("<option>----请选择市区----</option>") dis = $('#dis') dis.empty().append("<option>----请选择市区----</option>") // 遍历的第二种方式 $.each(city_data, function(index, item){ id = item[0] name = item[1] // console.log(id) //测试 // console.log(name) //测试 option_city = '<option value=' + id + '>' + name + '</option>' city.append(option_city) }) }) }) //绑定pro下拉列表框的change事件,获得省下面的市的信息 $('#city').change(function(){ //发起一个ajax请求,/city 获取省下面的市 //获取点击省的id city_id = $(this).val() $.get('/prov'+city_id, function(data){ //回调函数 dis_data = data.area_data dis = $('#dis') dis.empty().append("<option>----请选择市区----</option>") $.each(dis_data, function(index, item){ id = item[0] name = item[1] // console.log(id) // console.log(name) option_city = '<option value=' + id + '>' + name + '</option>' dis.append(option_city) }) }) }) }) </script> </head> <body> <h1 style='text-align: center'>省市区列表</h1> <div> <select id="pro"> <option >----请选择省----</option> </select> <select id='city'> <option>----请选择市区----</option> </select> <select id='dis'> <option>----请选择区县----</option> </select> </div> </body> </html>
第五步:在应用下views.py添加prov视图
from django.http import JsonResponse def prov(request, parent_id): # 如果为空说明请求的是省的下拉列表 if parent_id == '': areas = AreaInfo.objects.filter(area_parent__isnull = True) else: # 否则就是请求parent_id所对应的下拉列表 areas = AreaInfo.objects.filter(area_parent = parent_id) # 用于添加json数据 area_list = [] # 遍历获得的区域列表,添加到json数据中 for area in areas: area_list.append((area.id,area.name)) # 返回json数据给页面 return JsonResponse({'area_data':area_list})
第六步:在应用下的urls.py文件中配置url
re_path(r'^prov(?P<parent_id>\d*)$', views.prov),
第七步:访问127.0.0.1:8000/areas
-The End-