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"/>

执行结果:

  1. 硬编码图片显示失败,因未对应逻辑路径。
  2. 解析成功,此时 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>

 

 

posted @ 2021-04-25 17:20  Juno3550  阅读(219)  评论(0编辑  收藏  举报