索引
一、静态文件
项目中的CSS、图片、js都是静态文件。一般会将静态文件放到一个单独的目录中,以方便管理。在html页面中调用时,也需要指定静态文件的路径,Django中提供了一种解析的方式配置静态文件路径。静态文件可以放在项目根目录下,也可以放在应用的目录下,由于有些静态文件在项目中是通用的,所以推荐放在项目的根目录下,方便管理。
示例
1)在项目的settings.py文件中定义静态文件存放的物理目录。
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
2)在项目根目录下创建static目录,再创建img、css、js目录。
3)在booktest/views.py中定义视图static_test。
def static_test(request):
return render(request,'booktest/static_test.html')
4)在booktest/urls.py中配置url。
url(r'^static_test$',views.static_test),
5)在templates/booktest/下创建static_test.html文件。
<html>
<head>
<title>静态文件</title>
</head>
<body>
<img src="/static/img/test.png"/>
</body>
</html>
6)保存图片到static/img/目录下,名称为test.png。
7)运行服务器,就能在浏览器看到图片了。
配置静态文件
Django提供了一种配置,可以在html页面中可以隐藏真实路径。
1)在项目的settings.py文件中修改STATIC_URL项。
# STATIC_URL = '/static/'
STATIC_URL = '/abc/'
2)刷新浏览器,图片找不到了
3)修改templates/booktest/static_test.html如下:
<html>
<head>
<title>静态文件</title>
</head>
<body>
修改前:<img src="/static/img/test.png"/>
<hr>
修改后:<img src="/abc/img/test.png"/>
</body>
</html>
3)刷新浏览器,发现又能显示;
为了安全可以通过配置项隐藏真实图片路径,在模板中写成固定路径,后期维护太麻烦,可以使用static标签,根据配置项生成静态文件路径。
1)修改templates/booktest/static_test.html如下:
<html>
<head>
<title>静态文件</title>
</head>
<body>
修改前:<img src="/static/img/test.png"/>
<hr>
修改后:<img src="/abc/img/test.png"/>
<hr>
动态配置:
{%load static from staticfiles%}
<img src="{%static "img/test.png" %}"/>
</body>
</html>
2)刷新浏览器,动态配置的能显示。
二、中间件
中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。
说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。
自定义中间件
中间件可以定义五个方法,分别是:(主要的是process_request和process_response)
- process_request(self,request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。
自定义一个中间件示例
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response
process_request
process_request有一个参数,就是request,这个request和视图函数中的request是一样的。
它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。
我们来看看多个中间件时,Django是如何执行其中的process_request方法的。
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request):
print("MD1里面的 process_request") class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass
在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',
'middlewares.MD1', # 自定义中间件MD1
'middlewares.MD2' # 自定义中间件MD2
]
此时,我们访问一个视图,会发现终端中打印如下内容:
MD1里面的 process_request
MD2里面的 process_request
======index======
把MD1和MD2的位置调换一下,再访问一个视图,会发现终端中打印的内容如下:
MD2里面的 process_request
MD1里面的 process_request
======index======
看结果我们知道:视图函数还是最后执行的,MD2比MD1先执行自己的process_request方法。
在打印一下两个自定义中间件中process_request方法中的request参数,会发现它们是同一个对象。
由此总结一下:
- 中间件的process_request方法是在执行视图函数之前执行的。
- 当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
- 不同中间件之间传递的request都是同一个对象
多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。
process_response
它有两个参数,一个是request,一个是response,request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象。该方法的返回值也必须是HttpResponse对象。
给上述的M1和M2加上process_response方法:
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass def process_response(self, request, response):
print("MD2里面的 process_response")
return response
访问一个视图,看一下终端的输出:
MD2里面的 process_request
MD1里面的 process_request
======index======
MD1里面的 process_response
MD2里面的 process_response
看结果可知:
process_response方法是在视图函数之后执行的,并且顺序是MD1比MD2先执行。(此时settings.py中 MD2比MD1先注册)
多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。
process_view
process_view(self, request, view_func, view_args, view_kwargs)
该方法有四个参数
request是HttpRequest对象。
view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)
view_args是将传递给视图的位置参数的列表.
view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。
Django会在调用视图函数之前调用process_view方法。
它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。
给MD1和MD2添加process_view方法:
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__) class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass def process_response(self, request, response):
print("MD2里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__)
访问index视图函数,看一下输出结果:
MD2里面的 process_request
MD1里面的 process_request
--------------------------------------------------------------------------------
MD2 中的process_view
<function index at 0x000001B973777BF8> index
--------------------------------------------------------------------------------
MD1 中的process_view
<function index at 0x000001B973777BF8> index
======index======
MD1里面的 process_response
MD2里面的 process_response
process_view方法是在process_request之后,视图函数之前执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的
process_exception
process_exception(self, request, exception)
该方法两个参数:
一个HttpRequest对象
一个exception是视图函数异常产生的Exception对象。
这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
给MD1和MD2添加上这个方法:
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD1 中的process_exception") class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass def process_response(self, request, response):
print("MD2里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD2 中的process_exception")
如果视图函数中无异常,process_exception方法不执行。
想办法,在视图函数中抛出一个异常:
def index(request):
print('======index======')
raise ValueError("主动抛出异常")
return render(request, 'booktest/index.html')
在MD1的process_exception中返回一个响应对象:
class MD1(MiddlewareMixin): def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD1 中的process_exception")
return HttpResponse(str(exception)) # 返回一个响应对象
看输出结果:
MD2里面的 process_request
MD1里面的 process_request
--------------------------------------------------------------------------------
MD2 中的process_view
<function index at 0x0000019288FB7BF8> index
--------------------------------------------------------------------------------
MD1 中的process_view
<function index at 0x0000019288FB7BF8> index
======index======
主动抛出异常
MD1 中的process_exception
MD1里面的 process_response
MD2里面的 process_response
注意,这里并没有执行MD2的process_exception方法,因为MD1中的process_exception方法直接返回了一个响应对象。
process_template_response(用的比较少)
process_template_response(self, request, response)
它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。
process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。
class MD1(MiddlewareMixin): def process_request(self, request):
print("MD1里面的 process_request") def process_response(self, request, response):
print("MD1里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD1 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD1 中的process_exception")
return HttpResponse(str(exception)) def process_template_response(self, request, response):
print("MD1 中的process_template_response")
return response class MD2(MiddlewareMixin):
def process_request(self, request):
print("MD2里面的 process_request")
pass def process_response(self, request, response):
print("MD2里面的 process_response")
return response def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("MD2 中的process_view")
print(view_func, view_func.__name__) def process_exception(self, request, exception):
print(exception)
print("MD2 中的process_exception") def process_template_response(self, request, response):
print("MD2 中的process_template_response")
return response
view.py中:
def index(request):
print('======index======') def render():
print("in index/render")
return HttpResponse("OK") rep = HttpResponse("OK")
rep.render = render
return rep
访问index视图,终端输出的结果:
MD2里面的 process_request
MD1里面的 process_request
--------------------------------------------------------------------------------
MD2 中的process_view
<function index at 0x0000020E931A8BF8> index
--------------------------------------------------------------------------------
MD1 中的process_view
<function index at 0x0000020E931A8BF8> index
======index======
MD1 中的process_template_response
MD2 中的process_template_response
in index/render
MD1里面的 process_response
MD2里面的 process_response
从结果看出:
视图函数执行完之后,立即执行了中间件的process_template_response方法,顺序是倒序,先执行MD1的,在执行MD2的,接着执行了视图函数返回的HttpResponse对象的render方法,返回了一个新的HttpResponse对象,接着执行中间件的process_response方法。
中间件的执行流程
三、Admin站点
内容发布的部分由网站的管理员负责查看、添加、修改、删除数据,开发这些重复的功能是一件单调乏味、缺乏创造力的工作,为此,Django能够根据定义的模型类自动地生成管理模块。
在第一部分对管理站点做了简单介绍,现在做详细讲解。在Django项目中默认启用Admin管理站点。
1)准备工作:创建管理员的用户名和密码。
python manage.py createsuperuser
按提示填写用户名、邮箱、密码。
2)使用:在应用的admin.py中注册模型类
例:打开booktest/admin.py文件,注册地区模型。
from django.contrib import admin
from booktest.models import * admin.site.register(AreaInfo)
3)登录后台,登录成功后,可以看到AreaInfos,可以进行增加、修改、删除、查询的管理。
列表页显示效果如下图:
控制管理页展示
类ModelAdmin可以控制模型在Admin界面中的展示方式,主要包括在列表页的展示方式、添加修改页的展示方式。
1)在booktest/admin.py中,注册模型类前定义管理类AreaAdmin。
class AreaAdmin(admin.ModelAdmin):
pass
管理类有两种使用方式:
- 注册参数
- 装饰器
注册参数:打开booktest/admin.py文件,注册模型类代码如下:
admin.site.register(AreaInfo,AreaAdmin)
装饰器:打开booktest/admin.py文件,在管理类上注册模型类,代码如下:
@admin.register(AreaInfo)
class AreaAdmin(admin.ModelAdmin):
pass
接下来介绍如何控制列表页、增加修改页展示效果。
3.1 列表页选项
页大小
每页中显示多少条数据,默认为每页显示100条数据,属性如下:
list_per_page=100
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
list_per_page = 10
2)在浏览器中查看区域信息的列表页面,效果如下图:
"操作选项"的位置
顶部显示的属性,设置为True在顶部显示,设置为False不在顶部显示,默认为True。
actions_on_top=True
底部显示的属性,设置为True在底部显示,设置为False不在底部显示,默认为False。
actions_on_bottom=False
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
actions_on_top = True
actions_on_bottom = True
2)在浏览器中刷新效果如下图:
列表中的列
属性如下:
list_display=[模型字段1,模型字段2,...]
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
list_display = ['id','atitle']
2)在浏览器中刷新效果如下图:
点击列头可以进行升序或降序排列。
将方法作为列
列可以是模型字段,还可以是模型方法,要求方法有返回值。
1)打开booktest/models.py文件,修改AreaInfo类如下:
class AreaInfo(models.Model):
...
def title(self):
return self.atitle
2)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
list_display = ['id','atitle','title']
3)在浏览器中刷新效果如下图:
方法列是不能排序的,如果需要排序需要为方法指定排序依据。
admin_order_field=模型类字段
1)打开booktest/models.py文件,修改AreaInfo类如下:
class AreaInfo(models.Model):
...
def title(self):
return self.atitle
title.admin_order_field='atitle'
2)在浏览器中刷新效果如下图:
列标题
列标题默认为属性或方法的名称,可以通过属性设置。需要先将模型字段封装成方法,再对方法使用这个属性,模型字段不能直接使用这个属性。
short_description='列标题'
1)打开booktest/models.py文件,修改AreaInfo类如下:
class AreaInfo(models.Model):
...
title.short_description='区域名称'
2)在浏览器中刷新效果如下图:
关联对象
无法直接访问关联对象的属性或方法,可以在模型类中封装方法,访问关联对象的成员。
1)打开booktest/models.py文件,修改AreaInfo类如下:
class AreaInfo(models.Model):
...
def parent(self):
if self.aParent is None:
return ''
return self.aParent.atitle
parent.short_description='父级区域名称'
2)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
list_display = ['id','atitle','title','parent']
3)在浏览器中刷新效果如下图:
右侧栏过滤器
属性如下,只能接收字段,会将对应字段的值列出来,用于快速过滤。一般用于有重复值的字段。
list_filter=[]
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
list_filter=['atitle']
2)在浏览器中刷新效果如下图:
搜索框
属性如下,用于对指定字段的值进行搜索,支持模糊查询。列表类型,表示在这些字段上进行搜索。
search_fields=[]
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
search_fields=['atitle']
2)在浏览器中刷新效果如下图:
中文标题
1)打开booktest/models.py文件,修改模型类,为属性指定verbose_name参数
class Area(models.Model):
atitle = models.CharField(max_length=24,verbose_name="标题")
aparent = models.ForeignKey(to='self', null=True)
2)在浏览器中刷新效果如下图:
3.2 编辑页选项
显示字段顺序
属性如下:
fields=[]
1)点击某行ID的链接,可以转到修改页面,默认效果如下图:
2)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
fields=['aParent','atitle']
3)刷新浏览器效果如下图:
在下拉列表中输出的是对象的名称,可以在模型类中定义str方法用于对象转换字符串。
1)打开booktest/models.py文件,修改AreaInfo类,添加str方法。
class AreaInfo(models.Model):
...
def __str__(self):
return self.atitle
2)刷新浏览器效果如下图:
分组显示
属性如下:
fieldset=(
('组1标题',{'fields':('字段1','字段2')}),
('组2标题',{'fields':('字段3','字段4')}),
)
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
# fields=['aParent','atitle'] fieldsets = (
('基本', {'fields': ['atitle']}),
('高级', {'fields': ['aparent']})
)
2)刷新浏览器效果如下图:
说明:fields与fieldsets两者选一使用。
关联对象
在一对多的关系中,可以在一端的编辑页面中编辑多端的对象,嵌入多端对象的方式包括表格、块两种。 类型InlineModelAdmin:表示在模型的编辑页面嵌入关联模型的编辑。子类TabularInline:以表格的形式嵌入。子类StackedInline:以块的形式嵌入。
1)打开booktest/admin.py文件,创建AreaStackedInline类。
class AreaStackedInline(admin.StackedInline):
model = Area # 关联子对象
extra = 2 # 额外编辑2个子对象
2)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
inlines = [AreaStackedInline]
3)刷新浏览器效果如下图:
可以用表格的形式嵌入。
1)打开booktest/admin.py文件,创建AreaTabularInline类。
class AreaTabularInline(admin.TabularInline):
model = Area#关联子对象
extra = 2#额外编辑2个子对象
2)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
inlines = [AreaTabularInline]
3)刷新浏览器效果如下图:
3.3 重写模板
1)在templates/目录下创建admin目录
2)打开当前环境中Django的目录,再向下找到admin的模板
3)在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' %}">{{ site_header|default:_('Django administration') }}</a></h1>
<hr>
<h1>自定义的管理页模板</h1>
<hr>
{% endblock %} {% block nav-global %}{% endblock %}
4)在浏览器中转到列表页面,刷新后如下图:
其它后台的模板可以按照相同的方式进行修改。
四、上传图片
在Django中上传图片包括两种方式:
- 在管理页面admin中上传图片
- 自定义form表单中上传图片
上传图片后,将图片存储在服务器上,然后将图片的路径存储在表中。
创建包含图片的模型类
将模型类的属性定义成models.ImageField类型。
1)打开booktest/models.py文件,定义模型类PicTest。
class PicTest(models.Model):
pic = models.ImageField(upload_to='booktest/')
2)生成迁移,执行迁移。
python manage.py makemigrations
python manage.py migrate
3)打开项目的settings.py文件,设置图片保存路径。
因为图片也属于静态文件,所以保存到static目录下。
MEDIA_ROOT=os.path.join(BASE_DIR,"static/media")
4)在static目录下创建media目录,再创建应用名称的目录,此例为booktest。
4.1 在管理页面admin中上传图片
1)打开booktest/admin.py文件,注册PicTest。
from django.contrib import admin
from booktest.models import * admin.site.register(PicTest)
2)运行服务器,进入后台管理,点击add按钮添加数据
3)选择图片,点击“save”按钮完成图片上传。
4)回到数据库命令行,查询表pictest中的数据如下图:
5)图片被保存到目录static/media/booktest/下,如下图:
4.2 自定义form表单中上传图片
1)打开booktest/views.py文件,创建视图pic_upload。
def pic_upload(request):
return render(request,'booktest/pic_upload.html')
2)打开booktest/urls.py文件,配置url。
url(r'^pic_upload/$', views.pic_upload),
3)在templates/booktest/目录下创建模板pic_upload.html。
在模板中定义上传表单,要求如下:
- form的属性enctype="multipart/form-data"
- form的method为post
- input的类型为file
<form method="post" action="/pic_handle/" enctype="multipart/form-data">
{%csrf_token%}
<input type="file" name="pic"/><br>
<input type="submit" value="上传">
</form>
4)打开booktest/views.py文件,创建视图pic_handle,用于接收表单保存图片。
request对象的FILES属性用于接收请求的文件,包括图片。
from django.conf import settings
from django.http import HttpResponse
...
def file_handler(request):
# 1.获取文件对象
photo = request.FILES["photo"]
# 2.创建一个文件
file_path = "%s/booktest/%s"%(settings.MEDIA_ROOT, photo.name)
with open(file_path, 'wb') as f:
# 3.将图片保存到文件中
for content in photo.chunks():
f.write(content)
# 4.将文件同步到数据库
Picture.objects.create(photo="booktest/%s"%photo.name) return HttpResponse("Ok")
5)打开booktest/urls.py文件,配置url。
url(r'^pic_handle/$', views.pic_handle),
6)运行服务器,在浏览器中上传图片。
7)图片上传目录如下图:
4.3显示图片
1)打开booktest/views.py文件,创建视图pic_show。
from booktest.models import PicTest
...
def pic_show(request):
pic=PicTest.objects.get(pk=1)
context={'pic':pic}
return render(request,'booktest/pic_show.html',context)
2)打开booktest/urls.py文件,配置url。
url(r'^pic_show/$', views.pic_show),
3)在templates/booktest/目录下创建模板pic_show.html。
<img src="/static/media/{{pic.pic}}"/>
4)运行服务器,在浏览器中查看图片
五、分页
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。
方法has_previous():如果有上一页返回True。
方法len():返回当前页面对象的个数。
示例
1)在booktest/views.py文件中创建视图page_test。
from django.core.paginator import Paginator
from booktest.models import AreaInfo
...
#参数pIndex表示:当前要显示的页码
def page_test(request,pIndex):
#查询所有的地区信息
list1 = AreaInfo.objects.filter(aParent__isnull=True)
#将地区信息按一页10条进行分页
p = Paginator(list1, 10)
#如果当前没有传递页码信息,则认为是第一页,这样写是为了请求第一页时可以不写页码
if pIndex == '':
pIndex = ''
#通过url匹配的参数都是字符串类型,转换成int类型
pIndex = int(pIndex)
#获取第pIndex页的数据
list2 = p.page(pIndex)
#获取所有的页码信息
plist = p.page_range
#将当前页码、当前页的数据、页码信息传递到模板中
return render(request, 'booktest/page_test.html', {'list': list2, 'plist': plist, 'pIndex': pIndex})
2)在booktest/urls.py文件中配置url。
url(r'^page(?P<pIndex>[0-9]*)/$', views.page_test),
3)在templates/booktest/目录下创建page_test.html模板文件。
<body>
显示当前页的地区信息:<br>
<ul>
{%for area in list%}
<li>{{area.id}}--{{area.atitle}}</li>
{%endfor%}
</ul>
<hr>
显示页码信息:当前页码没有链接,其它页码有链接<br>
{%for pindex in plist%}
{%if pIndex == pindex%}
{{pindex}}
{%else%}
<a href="/page{{pindex}}/">{{pindex}}</a>
{%endif%}
{%endfor%}
</body>
4)运行服务器,访问网址:
5)点击页码数字,效果如下图:
如果页码过多,自定义页码列表
view:
# 1.获取地区对象
areas = Area.objects.all()
# 2.创建分页对象
paginator = Paginator(areas,10)
# 3.获取page页的数据
try:
page = int(page)
except Exception as e:
page = 1
if page < 1:
page = 1
if page > paginator.num_pages:
page = paginator.num_pages
pageinfo = paginator.page(page)
# 页码列表(按每个页面11个页码)
# 总页码数小于11页
if paginator.num_pages < 11:
page_list = range(1, paginator.num_pages+1)
# 当前页码为前6页,显示1-11页
elif page <= 6:
page_list = range(1, 12)
# 当前页码为后6页,显示后11页
elif page > paginator.num_pages - 5:
page_list = range(paginator.num_pages-10,paginator.num_pages+1)
# 其他情况显示当前页码的前5页和后5页
else:
page_list = range(page-5,page+6)
# 4.渲染模板
return render(request,'book/areainfo.html',{"pageinfo":pageinfo, "page_list":page_list})
templates:
<!--数据-->
<ul>
{% for area in pageinfo %}
<li>{{ area.atitle }}</li>
{% endfor %}
</ul>
<!-- 分页 -->
{% if pageinfo.has_previous %}
<a href="/book/area_page{{ pageinfo.previous_page_number }}">上一页</a>
{% endif %}
{% for pindex in page_list %}
{% if pindex == pageinfo.number %}
{{ pindex }}
{% else %}
<a href="/book/area_page{{ pindex }}">{{ pindex }}</a>
{% endif %}
{% endfor %}
{% if pageinfo.has_next %}
<a href="/book/area_page{{ pageinfo.next_page_number }}">下一页</a>
{% endif %}
自定义分页器
class Pagination(object):
def __init__(self, current_page, all_count, base_url,params, per_page_num=8, pager_count=11, ):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param base_url: 分页中显示的URL前缀
:param pager_count: 最多显示的页码个数
""" try:
current_page = int(current_page)
except Exception as e:
current_page = 1 if current_page < 1:
current_page = 1 self.current_page = current_page self.all_count = all_count
self.per_page_num = per_page_num self.base_url = base_url # 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager self.pager_count = pager_count # 最多显示页码数
self.pager_count_half = int((pager_count - 1) / 2) import copy
params = copy.deepcopy(params)
params._mutable = True
self.params = params # self.params : {"page":77,"title":"python","nid":1} @property
def start(self):
return (self.current_page - 1) * self.per_page_num @property
def end(self):
return self.current_page * self.per_page_num def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示(11-1)/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1 # 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_start = self.all_pager - self.pager_count + 1
pager_end = self.all_pager + 1 else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1 page_html_list = []
self.params["page"] = 1
first_page = '<li><a href="%s?%s">首页</a></li>' % (self.base_url, self.params.urlencode(),)
page_html_list.append(first_page) if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
self.params["page"] = self.current_page - 1
prev_page = '<li><a href="%s?%s">上一页</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(prev_page) for i in range(pager_start, pager_end): self.params["page"] = i
if i == self.current_page:
temp = '<li class="active"><a href="%s?%s">%s</a></li>' % (self.base_url, self.params.urlencode(), i,)
else:
temp = '<li><a href="%s?%s">%s</a></li>' % (self.base_url, self.params.urlencode(), i,)
page_html_list.append(temp) if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
self.params["page"] = self.current_page + 1
next_page = '<li><a href="%s?%s">下一页</a></li>' % (self.base_url, self.params.urlencode(),)
page_html_list.append(next_page) self.params["page"] = self.all_pager
last_page = '<li><a href="%s?%s">尾页</a></li>' % (self.base_url, self.params.urlencode(),)
page_html_list.append(last_page) return ''.join(page_html_list)
六、auth模块
from django.contrib import auth
create_user():创建用户
auth.models.User.objects.create_user(username=username,password=password)
check_password(passwd):检查密码
set_password() :修改密码
class UpdatePassword(View):
def get(self, request):
return render(request, 'update_password.html') def post(self, request):
name = request.POST.get("name")
oldpwd = request.POST.get("oldpwd")
newpwd = request.POST.get("newpwd")
user = User.objects.get(username=name)
ret = user.check_password(oldpwd)
if ret:
user.set_password(newpwd)
user.save()
return redirect('/login')
else:
return redirect('/update_password')
authenticate():验证用户输入的用户名和密码是否相同
class LoginView(View): def get(self, request):
return render(request, 'login.html') def post(self, request):
name = request.POST.get("name")
pwd = request.POST.get("pwd")
user = auth.authenticate(username=name, password=pwd)
if user:
return redirect('/user_center')
else:
return redirect('/login')
login():登录
用户成功登录后,给用户附加上session id
class LoginView(View): def get(self, request):
return render(request, 'login.html') def post(self, request):
name = request.POST.get("name")
pwd = request.POST.get("pwd")
user = auth.authenticate(username=name, password=pwd)
if user:
auth.login(request, user)
return redirect('/user_center')
else:
return redirect('/login')
logout():注销用户
def logout(request):
auth.logout(request)
return redirect('/login')
is_authenticated()
def user_center(request):
user = request.user
if not user.is_authenticated(): # 没有登录跳转到登录
redirect('/login')
return render(request,'user_center.html')
这样虽然拦截了用户去登录,但登录后用户并没有回到之前访问的页面,比如访问自己的个人中心,购物车等页面,登录后自己还需要再打开连接。
django提供的此种情况的装饰器:login_requierd()
from django.contrib.auth.decorators import login_required @login_required
def user_center(request):
return render(request,'booktest/user_center.html')
若用户没有登录,则会跳转到django默认的 登录URL '/accounts/login/ ' (这个值可以在settings文件中通过LOGIN_URL进行修改)。并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。
在settings配置:
LOGIN_URL = '/login' # 我的登录地址是 '/login'
此时再去访问就不会找不到登录页面了,登录成功后还能返回之前访问的页面。
七、示例:省市区选择
1)打开booktest/views.py文件,定义视图area1,用于显示下拉列表。
#提供显示下拉列表的控件,供用户操作
def area1(request):
return render(request,'booktest/area1.html')
2)打开booktest/urls.py文件,配置url。
url(r'^area1/$', views.area1),
3)在templates/booktest/目录下创建area1.html。
<html>
<head>
<title>省市区列表</title>
<script type="text/javascript" src="/static/js/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
$(function(){
//页面加载完成后获取省信息,并添加到省select
$.get('/area2/',function(dic) {
pro=$('#pro')
$.each(dic.data,function(index,item){
pro.append('<option value='+item[0]+'>'+item[1]+'</option>');
})
});
//为省select绑定change事件,获取市信息,并添加到市select
$('#pro').change(function(){
$.get('/area3_'+$(this).val()+'/',function(dic){
city=$('#city');
city.empty().append('<option value="">请选择市</option>');
dis=$('#dis');
dis.empty().append('<option value="">请选择区县</option>');
$.each(dic.data,function(index,item){
city.append('<option value='+item[0]+'>'+item[1]+'</option>');
})
});
});
//为市select绑定change事件,获取区县信息,并添加到区县select
$('#city').change(function(){
$.get('/area3_'+$(this).val()+'/',function(dic){
dis=$('#dis');
dis.empty().append('<option value="">请选择区县</option>');
$.each(dic.data,function(index,item){
dis.append('<option value='+item[0]+'>'+item[1]+'</option>');
})
})
}); });
</script>
</head>
<body>
<select id="pro">
<option value="">请选择省</option>
</select>
<select id="city">
<option value="">请选择市</option>
</select>
<select id="dis">
<option value="">请选择区县</option>
</select>
</body>
</html>
4)打开booktest/views.py文件,定义视图area2,用于获取省信息。
from django.http import JsonResponse
...
#获取省信息
def area2(request):
list = AreaInfo.objects.filter(aParent__isnull=True)
list2 = []
for item in list:
list2.append([item.id, item.atitle])
return JsonResponse({'data': list2})
5)打开booktest/urls.py文件,配置url。
url(r'^area2/$', views.area2),
6)打开booktest/views.py文件,定义视图area3,用于根据编号获取对应的子级信息,如果传递的是省编号则获取市信息,如果传递的是市编号则获取区县信息。
#根据pid查询子级区域信息
def area3(request, pid):
list = AreaInfo.objects.filter(aParent_id=pid)
list2 = []
for item in list:
list2.append([item.id, item.atitle])
return JsonResponse({'data': list2})
7)打开booktest/urls.py文件,配置url。
url(r'^area3_(\d+)/$', views.area3),