Django 高级
1. 静态文件管理
2. 中间件
3. 上传图片
4. 分页
5. Ajax
1. 静态文件管理
项目中的 CSS、图片、JS 文件等都是静态文件。
配置静态文件
在 settings 文件中定义静态内容:
STATIC_URL = '/static_virtual/' # 逻辑路径(供模板使用)
STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), # 物理路径 ]
在项目根目录下创建 static 目录,再创建当前应用名称的目录:
DjangoDemo/static/hero_book/ # 根目录/static/应用目录
模板引用静态文件
注意:因为请求静态文件实际上不需要走 Django 处理,因此不需要考虑 urls.py 的正则匹配规则,直接请求在服务器的真实路径即可。
{# 硬编码 #} <img src="/static/hero_info/example.jpg" /> {# 解析 static 逻辑路径 #} {% load static from staticfiles %} <img src="{ % static "my_app/myexample.jpg" %}" alt="My image"/>
执行结果:
- 硬编码图片显示失败,因未对应逻辑路径。
- 解析成功,此时 src="/static_virtual/hero_info/example.jpg"。
2. 中间件
Django 中间件是一个轻量级、底层的插件系统,可以介入 Django 的请求和响应处理过程,修改 Django 的输入或输出。
激活:添加到 Django 配置文件 settings.py 中的 MIDDLEWARE_CLASSES 元组中。
每个中间件组件是一个独立的Python类,可以定义下面方法中的一个或多个:
- _init _():无需任何参数,服务器响应第一个请求的时候调用一次,用于确定是否启用当前中间件。
- process_request(request):执行视图之前被调用,在每个请求上调用,返回 None 或 HttpResponse 对象。
- process_view(request, view_func, view_args, view_kwargs):调用视图之前被调用,在每个请求上调用,返回 None 或 HttpResponse 对象。
- process_template_response(request, response):在视图刚好执行完毕之后被调用,在每个请求上调用,返回实现了 render 方法的响应对象。
- process_response(request, response):所有响应返回浏览器之前被调用,在每个请求上调用,返回 HttpResponse 对象。
- process_exception(request,response,exception):当视图抛出异常时调用,在每个请求上调用,返回一个 HttpResponse 对象。
使用中间件,可以干扰整个处理过程,每次请求中都会执行中间件的这个方法。
示例:自定义异常处理
1)与 settings.py 同级目录下创建 myexception.py 文件,定义类 MyException,实现 process_exception 方法:
from django.http import HttpResponse
class MyException(): def process_exception(request,response, exception): return HttpResponse(exception.message)
2)将类 MyException 注册到 settings.py 中间件中:
MIDDLEWARE_CLASSES = ( 'hero_book.myexception.MyException', ... )
3)定义视图,并发生一个异常信息,则会运行自定义的异常处理。
3. 上传图片
当 Django 在处理文件上传的时候,文件数据被保存在 request.FILES。
- FILES 中的每个键为 <input type="file" name="" /> 中的 name。
- 注意:FILES 只有在请求的方法为 POST 且提交的 <form> 带有 enctype="multipart/form-data" 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象。
使用模型处理上传文件
将属性定义成 models.ImageField 类型:
pic=models.ImageField(upload_to='save_path/')
- 注意:如果属性类型为 ImageField 需要安装 Pilow 包。
图片存储路径
- 在项目根目录下创建 media 文件夹(静态文件保存目录,又与开发本身的静态文件区分子目录)。
- 图片上传后,会被保存到 “/static/media/图片文件”。
打开 settings.py 文件,增加 MEDIA_ROOT 项:
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ] MEDIA_ROOT = os.path.join(BASE_DIR, "static/media")
使用方式 1
使用 Django 后台管理,遇到 ImageField 类型的属性会出现一个 file 框,完成文件上传。
使用方式 2
手动上传的模板代码:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>文件上传</title> 6 </head> 7 <body> 8 <form method="post" action="/hero_book/upload_handler/" enctype="multipart/form-data"> 9 {% csrf_token %} 10 <input type="file" name="picture" /> 11 <input type="submit" value="上传"> 12 </form> 13 </body> 14 </html>
手动上传的视图代码:
1 def upload_handler(request): 2 received_pic = request.FILES["picture"] # 获取上传的图片 3 save_name = "%s/%s" % (settings.MEDIA_ROOT, received_pic) # 定义图片在服务器的保存目录 4 with open(save_name, "wb") as f: # 使用文件流保存图片 5 for c in received_pic.chunks(): 6 f.write(c) 7 # 返回显示图片的页面 8 return HttpResponse("<img src='/static/media/%s' />" % received_pic)
4. 分页
Django 提供了一些类实现管理数据分页,这些类位于 django/core/paginator.py 中。
Paginator 对象
Paginator(列表, int)
- 返回:分页对象。
- 参数:列表数据、每面数据的条数。
属性
- count:对象总数。
- num_pages:页面总数。
- page_range:页码列表。从 1 开始,例如 [1, 2, 3, 4]。
方法
- page(num):下标以 1 开始,如果提供的页码不存在,抛出 InvalidPage 异常。
异常 Exception
- InvalidPage:当向 page() 传入一个无效的页码时抛出。
- PageNotAnInteger:当向 page() 传入一个不是整数的值时抛出。
- EmptyPage:当向 page() 提供一个有效值,但是那个页面上没有任何对象时抛出。
Page 对象
创建对象
- Paginator 对象的 page() 方法返回 Page 对象,不需要手动构造。
属性
- object_list:当前页上所有对象的列表。
- number:当前页的序号,从 1 开始。
- paginator:当前 Page 对象相关的 Paginator 对象。
方法
- has_next():如果有下一页返回 True。
- has_previous():如果有上一页返回 True。
- has_other_pages():如果有上一页或下一页返回 True。
- next_page_number():返回下一页的页码,如果下一页不存在,抛出 InvalidPage 异常。
- previous_page_number():返回上一页的页码,如果上一页不存在,抛出 InvalidPage 异常。
- len():返回当前页面对象的个数。
- 迭代页面对象:访问当前页面中的每个对象。
范例:展示英雄列表
views.py:
1 from django.conf import settings 2 from django.core.paginator import Paginator 3 4 5 def hero_list(request, p_index): # p_index 为页面点击的页码数 6 if p_index == "": # url没带页码数,则显示第一页数据 7 p_index = 1 8 hero_info = HeroInfo.objects.all() 9 paginator = Paginator(hero_info, 2) 10 page = paginator.page(int(p_index)) 11 return render(request, "hero_book/hero_list.html", {"page": page})
urls.py:
url(r'^hero_list/(\d*)/?$', views.hero_list),
hero_list.html:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>英雄列表</title> 6 </head> 7 <body> 8 <ul> 9 {% for hero in page %} 10 <li>{{ hero.name }}</li> 11 {% endfor %} 12 </ul> 13 <hr/> 14 {% for page_index in page.paginator.page_range %} 15 {% if page_index == page.number %} {# 当前页码不需要链接 #} 16 {{page_index}} 17 {% else %} 18 <a href="/hero_book/hero_list/{{page_index}}/">{{page_index}}</a> 19 {% endif %} 20 {% endfor %} 21 </body> 22 </html>
5. Ajax
使用视图通过上下文向模板中传递数据,需要先加载完成模板的静态页面,再执行模型代码,生成最终的 html 返回给浏览器,这个过程将页面与数据集成到了一起,扩展性差。
改进方案:通过 ajax 的方式获取数据,通过dom操作将数据呈现到界面上。
推荐使用框架的 ajax 相关方法,不使用 XMLHttpRequest 对象,因为操作麻烦且不容易查错。
jquery 框架中提供了 $.ajax、$.get、$.post 方法,用于进行异步交互。
由于 csrf 的约束,推荐使用 $.get。
范例:实现省市区的选择
最终实现效果如下图:
在 models.py 中定义模型
- 对于地区信息,属于一对多关系,使用一张表,存储所有的信息。
- 类似的表结构还应用于分类信息,可以实现无限级分类。
1 class AreaInfo(models.Model): 2 3 atitle = models.CharField(max_length=20) 4 aParent = models.ForeignKey('self', null=True, blank=True)
- 访问关联对象:
上级对象:area.aParent
下级对象:area.areainfo_set.all()
生成对应表结构
方式 1:使用迁移
python manage.py makemigrations
python manage.py migrate
方式 2:直接在数据库新增表结构
create table book_info_areainfo( id int primary key, title varchar(20), parea_id int, foreign key(parea_id) references book_info_areainfo(id) -- 自关联 );
views.py
1 from django.shortcuts import render 2 from django.http.response import JsonResponse 3 from .models import BookInfo, HeroInfo, AreaInfo 4 5 6 # 首页:省市区ajax联动 7 def area(request): 8 return render(request, "hero_book/area.html") 9 10 # 省级数据 11 def area_handler(request): 12 province_list = AreaInfo.objects.filter(parea__isnull=True) # 返回省级数据 13 new_list = [] 14 for li in province_list: 15 # 将省级数据的id和名称加进新列表 16 new_list.append([li.id, li.title]) 17 return JsonResponse({"data": new_list}) 18 19 20 # 市级数据 21 def city(request, id): 22 city_list = AreaInfo.objects.filter(parea_id=id) # 根据省级编号获取对应的市级属 23 new_list = [] 24 for item in city_list: 25 #[{id:1, title:北京},{id:2, title:天津},...] 26 new_list.append({"id": item.id, "title": item.title}) 27 return JsonResponse({"data": new_list})
urls.py
url(r'^area/$', views.area), url(r'^province/$', views.area_handler), url(r'^city(\d+)/$', views.city),
area.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>省市区ajax联动</title> 6 <script src="/static/hero_info/jquery-1.12.4.min.js"></script> 7 <script> 8 $(function(){ 9 // 查询省级信息 10 pro=$('#province') // 获取省级的下拉框,将获取到的模型数据添加进去 11 // "/hero_book/province/"为请求地址,function(dic)为回调函数,处理返回的数据。dic: {data :[[], [], []]} 12 $.get("/hero_book/province/", function(dic){ 13 $.each(dic.data, function(index, item){ // [id ,title] 14 // 返回的是列表,所以用item+索引方式获取编号和名称 15 pro.append("<option value='" + item[0] + "'>" + item[1] + "</option>") 16 }); 17 }); 18 19 // 查询市级信息 20 $('#province').change(function(){ 21 //city(\d+)/ 22 // select的value指:select中选中的option的value 23 $.get('/hero_book/city'+$(this).val()+"/", function(list){ 24 city = $("#city"); 25 // 每次需要先清空上一次的省选择结果,再重新添加option数据 26 city.empty().append('<option value="">请选择市</option>') 27 $("#district").empty().append('<option value="">请选择区县</option>') 28 // {data:#[{id:1, title:北京},{id:2, title:天津},...]} 29 $.each(list.data, function(index, item){ 30 // {id:1, title:"北京"} 31 // 返回的是json,所以用item.的方式获取编号和名称 32 city.append("<option value='" + item.id + "'>" + item.title + "</option>"); 33 }); 34 }); 35 }); 36 37 // 查询区级信息(可复用city视图函数) 38 $('#city').change(function(){ 39 //city(\d+)/ 40 $.get('/hero_book/city'+$(this).val()+"/", function(list){ 41 dis = $("#district"); 42 // 每次需要先清空上一次的省选择结果,再重新添加option数据 43 dis.empty().append('<option value="">请选择区县</option>') 44 // {data:#[{id:1, title:...},{id:2, title:...},...]} 45 $.each(list.data, function(index, item){ 46 // {id:1, title:"..."} 47 // 返回的是json,所以用item.的方式获取编号和名称 48 dis.append("<option value='" + item.id + "'>" + item.title + "</option>"); 49 }); 50 }); 51 }); 52 }); 53 </script> 54 </head> 55 <body> 56 <select id="province"> 57 <option value="">请选择省</option> 58 </select> 59 60 <select id="city"> 61 <option value="">请选择市</option> 62 </select> 63 64 <select id="district"> 65 <option value="">请选择区县</option> 66 </select> 67 </body> 68 </html>