笔记主要基于官方文档,从中提取要点和记录笔记,关键处包含了官方文档链接。详见官方文档。
官方文档:Django documentation
博客推荐:Django2.2教程
目录
1.文件上传
可参考官方文档 视图层 文件上传部分: 概览 | 文件对象 | 存储 API | 管理文件 | 自定义存储
Django在处理文件上传时,文件数据被打包封装在 request.FILES 中。
1.1.简单文件上传
- 定义包含 FileField 的form表单模型并在模板中使用
- 定义接收文件的视图
- 定义处理上传文件的视图
(以下为官方文档例子,根据自己项目稍微改动即可运行)
(1)定义包含 FileField 的form表单模型并在模板中使用(在当前app内新建一个forms.py
文件):
# forms.py
from django import forms
class UploadFileForm(forms.Form):
"""简单文件上传表单模型"""
title = forms.CharField(max_length=50)
file = forms.FileField()
- 处理这个表单的视图将通过 request.FILES <django.http.HttpRequest.FILES> 获取到文件数据。可以用
request.FILES['file']
来获取上传文件的具体数据,其中的键值‘file’是根据file = forms.FileField()
的变量名来的。 - request.FILES 是包含了表单中每个django.forms.FileField 类、
ImageField
类,及其子类键值的字典 。所以数据可以通过request.FILES['file'] 获取到
注:
request.FILES
只有在请求方法为POST,并且提交请求的<form>
具有enctype="multipart/form-data"
属性时才有效。 否则,request.FILES将为空。
(2)定义接收、处理文件的视图
# views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import UploadFileForm
# /upload_file
def upload_file(request):
"""定义接收文件的视图"""
# ----简单文件上传----
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES) # 注意获取数据的方式
if form.is_valid():
handle_uploaded_file(request.FILES['file'])
return HttpResponse('upload_file ok') # 反馈一个 ok
else:
form = UploadFileForm()
return render(request, 'booktest/upload_file.html', {'form': form})
def handle_uploaded_file(f):
"""处理上传文件"""
save_path = '%s/booktest/upload_file.jpg' % (settings.MEDIA_ROOT,) # settings.MEDIA_ROOT拿到madie的路径
with open(save_path, 'wb+') as destination:
for chunk in f.chunks():
destination.write(chunk)
注意我们必须将 request.FILES
传入到表单的构造方法中,只有这样文件数据才能绑定到表单中。
form = UploadFileForm(request.POST, request.FILES)
使用 UploadedFile.chunks()
而不是 read()
是为了确保即使是大文件又不会将我们系统的内存占满。
其他文件:
# 应用下的urls.py
path('upload_file', views.upload_file, name='upload_file'), # 接收、处理上传文件
# setting.py
# 配置媒体文件上传目录和访问路径
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/media')
# upload_file.html
...
<form method="post" enctype="multipart/form-data" action="/upload_file">
{% csrf_token %}
{{ form }} <br/>
<input type="submit" value="上传">
</form>
...
1.2.通过模型来处理上传的文件
- 先设计
FileField
模型类及其表单 - 定义接收、处理文件的视图
- 定义模版等...
如果想要在 FileField
上的 Model
保存文件,通过模型层的model来指定上传文件的保存方式,使用 ModelForm
会让这一过程变得简单。当调用 form.save()
时,文件对象将会被保存在相应的 FileField
的 upload_to(路径相对MEDIA_ROOT而言)
参数所指定的地方(需要先设计好含FileField
的
模型类):
(1)先设计FileField
模型类及其表单,并做好迁移:
# models.py
class ModelWithFileField(models.Model):
"""通过模型处理上传的文件的模型"""
title = models.CharField(max_length=200)
file = models.FileField(upload_to='booktest/%Y/%m/%d') # 格式化为时间目录
# forms.py
# 通过模型创建表单
from .models import ModelWithFileField
from django.forms import ModelForm
class ModelFormWithFileField(ModelForm):
class Meta:
model = ModelWithFileField
fields = "__all__"
# fields = ['title', 'file']
(2)定义接收、处理文件的视图
# views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField # 导入表单
# upload_file_by_modelform
def upload_file_by_modelform(request):
# -------通过模型来处理上传的文件---------
if request.method == 'POST':
form = ModelFormWithFileField(request.POST, request.FILES)
if form.is_valid():
# 这么做就可以了,文件会被保存到Model中upload_to参数指定的位置
form.save()
return HttpResponse('通过模型来处理上传的文件 ok')
else:
form = ModelFormWithFileField()
return render(request, 'booktest/upload_file_by_modelform.html', {'form': form})
(3)其他文件
# settings.py
MEDIA_ROOT = os.path.join(BASE_DIR,'static/images/')
MEDIA_URL = '/media/'
# urls.py
...
path('upload_file_by_modelform', views.upload_file_by_modelform,
name='upload_file_by_modelform'),
...
# upload_file_by_modelform.html
...
<form method="post" enctype="multipart/form-data" action="/upload_file_by_modelform">
{% csrf_token %}
{{ form }} <br/>
<input type="submit" value="上传">
</form>
...
如果要手动构造指定对象,还可以简单地把文件对象直接从request.FILES赋值给模型.但需要注意,如有其它字段必须允许为空,否则将引发异常!:
# views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField # 导入表单
from .models import ModelWithFileField
def upload_file_by_modelform(request):
# ----------手动制定对象------------
if request.method == 'POST':
form = ModelFormWithFileField(request.POST, request.FILES)
if form.is_valid():
instance = ModelWithFileField(file=request.FILES['file']) # 从request.FILES将文件数据指定到模型字段
instance.save()
return HttpResponse('ok')
else:
form = ModelFormWithFileField()
return render(request, 'booktest/upload_file_by_modelform.html', {'form': form})
1.3.同时上传多个文件
如果要使用一个表单字段同时上传多个文件,需设置字段的 widget 的 multiple属性为True,在django表单中设置小部件HTML的属性如下(也可参考官方文档):
# forms.py
from .models import ModelWithFileField
from django.forms import ModelForm
class ModelFormWithFileField(ModelForm):
class Meta:
model = ModelWithFileField
fields = "__all__"
# 在上面模型的基础上,创建的forms表单添加HTML属性multiple
widgets = {
'file': forms.ClearableFileInput(attrs={'multiple': True})
}
在视图中存储多个文件则需要通过getlist方法获取多个文件的列表,然后循环存储到数据并保存即可:
# views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField # 导入表单
from .models import ModelWithFileField
# /upload_file_by_modelform_multiple
def upload_file_by_modelform_multiple(request):
if request.method == 'POST':
form = ModelFormWithFileField(request.POST, request.FILES)
if form.is_valid():
for i in request.FILES.getlist('file'): # 获取文件列表
print(type(i))
instance = ModelWithFileField(file=i)
instance.save()
return HttpResponse('ok')
else:
form = ModelFormWithFileField()
return render(request, 'booktest/upload_file_by_modelform_multiple.html', {'form': form})
(注:官方文档的方法不知道怎么用?)
1.4.上传文件处理器Handlers
以下是官方文档的说法:
当一个用户上传文件时,Django 会把文件数据传递给 upload handler —— 这是一个很小的类,它用来在上传时处理文件数据。上传处理模块最初定义在
FILE_UPLOAD_HANDLERS
里,默认为:["django.core.files.uploadhandler.MemoryFileUploadHandler", "django.core.files.uploadhandler.TemporaryFileUploadHandler"]
MemoryFileUploadHandler
和TemporaryFileUploadHandler
提供 Django 默认文件上传行为,小文件读入内存,大文件存在磁盘上。
通常,如果上传文件小于2.5MB,Django会把整个内容存到内存。如果上传的文件很大,Django会把它写入一个临时文件,储存在你的系统临时目录中。在类Unix的平台下,Django会生成一个文件,名称类似于/tmp/tmpzfp6I6.upload。
你可以编写自定义的 handlers 来自定义 Django 如何处理文件。查看 Writing custom upload handlers 来了解你如何自定义或者完全替换上传行为。
1.5.上传图片举例
在Django中上传图片包括两种方式:
- 通过管理页面admin中上传图片
- 网站的用户自定义form表单中上传图片(如上传头像)
上传图片后,将图片存储在服务器上,然后将图片的路径存储在表中。
配置上传文件保存目录
(1)新建上传文件保存目录:可以选择在 static 目录下新建目录 media 。
(2)配置上传文件保存目录:在配置文件中,设置上传文件的保存目录
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/media')
通过后台管理页面上传图片
(1)设计模型类。在模型类中,定义一个属性,字段类型为 ImagesField 。(ImagesField 是FileField的子类,参考模型类字段。参数upload_to=是指定上传到那个目录,目录是相对与media的)
# models.py
#...
class PicTest(models.Model):
"""上传图片"""
goods_pic = models.ImageField(upload_to='booktest')
(2)生成迁移、执行迁移生成表格:python manage.py makemigrations、python manage.py migrate(如果迁移没成功,在数据库django_migrations 表中,把自己app相关的迁移记录删了,通常这些记录的app列是应用名,name列的记录类似 0001_initial 。把工程中0001_initial.py文件删了。再重新迁移)
python manage.py makemigrations
python manage.py migrate
(3)注册模型类:在admin.py文件,导入并注册模型类
# ...
admin.site.register(PicTest)
在后台管理页即可看见可管理相关的模型类,点击增加,可以上传图片。查看表中记录了相对的路径。
用户自定义form表单上传图片
(1)定义用户上传图片的页面并显示,是一个自定义的表单。表单提交方式为POST。用entype指定编码类型为 multipart/form-data 。如:
<form method="post" enctype="multipart/form-data" action="/upload_handle">
{% csrf_token %}
<input type="file" name="pic"><br/>
<input type="submit" value="上传">
</form>
(2)定义上传、接收上传文件的视图函数。
request 对象有一个FILES属性,类似于字典,通过 request.FILES 可以根据上传图片的name获取上传文件的处理对象。
在django中,上传文件不大于2.5M,文件放在内存中。上传文件大于2.5M,文件内容写到一个临时文件中。对应下面两个类。
Django处理上传文件的两个类:
FILE_UPLOAD_HANDLERS = ("django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler")
1)获取上传文件的处理对象
2)创建一个文件
3)在数据库中保存上传记录
4)返回
如下:(一个上传视图、一个处理视图)
# /show_upload
def show_upload(request):
'''显示上传图片页面'''
return render(request, 'booktest/upload_pic.html')
# /upload_handle
def upload_handle(request):
'''上传图片处理'''
# 1.获取上传文件的处理对象
pic = request.FILES['pic']
print(pic.name)
# 2.创建一个文件
save_path = '%s/booktest/%s' % (settings.MEDIA_ROOT, pic.name) # settings.MEDIA_ROOT拿到madie的路径
with open(save_path, 'wb') as f:
# 3.获取上传文件的内容并写到创建的文件中
for content in pic.chunks():
f.write(content)
# 4.在数据库中保存上传记录(PicTest为数据库模型类名
PicTest.objects.create(goods_pic='booktest/%s' % pic.name)
# 5.返回
return HttpResponse('ok')
在浏览器访问上传,会看到文件上传到指定目录。
2.分页
官方文档:分页
更多web应用工具参考:常用的 Web 应用程序工具
Django提供了数据分页的类,这些类被定义在django/core/paginator.py中。
-
类Paginator用于对列进行一页n条数据的分页运算。
-
类Page用于表示第m页的数据。
Paginator
类的构造方法是:class Paginator(object_list, per_page, orphans=0, allow_empty_first_page=True)[源代码]
必选参数:
object_list:一个列表,元组,查询集,或其他具有count()或len__()方法的可分割对象。为了保持一致的分页,查询集应该是有序的,例如使用order_by()子句,或者使用模型上的默认顺序。
per_page:页面上要包含的项的最大数目;
Page 对象:通常不会手动实例化Page对象 - 你会使用方法Paginator.page()。(点击查看Django文档)
Paginator类实例对象:Paginator 对象
- 方法_init_(列表,int):返回分页对象,第一个参数为列表数据,第二个参数为每页数据的条数。
- 属性count:返回对象总数。
- 属性num_pages:返回页面总数。
- 属性page_range:返回页码列表,从1开始,例如[1, 2, 3, 4]。
- 方法page(m):返回Page类实例对象,表示第m页的数据,下标以1开始。
Page类实例对象:Page 对象
- 调用Paginator对象的page()方法返回Page对象,不需要手动构造。
- 属性object_list:返回当前页对象的列表。
- 属性number:返回当前是第几页,从1开始。
- 属性paginator:当前页对应的Paginator对象。
- 方法has_next():如果有下一页返回True。
- 方法has_previous():如果有上一页返回True。
- 方法len():返回当前页面对象的个数。
- previous_page_number():返回前一页的页码
- next_page_number():返回下一页的页码
- ...
实例:查询出所有省级地区的信息,省标题显示在页面上。
(相关模型 AreaInfo 包含两个字段:地区名称atitle 和 自关联属性aParent ;详见https://blog.csdn.net/qq_23996069/article/details/104910791 中3.2小结最后的例子 models.py)
- 查询出所有省级地区的信息。
- 按每页显示10条信息进行分页,默认显示第一页的信息,下面并显示出页码。
- 点击i页链接的时候,就显示第i页的省级地区信息。
# views.py
# /show_area
# 前端访问的时候,需要传递页码
from django.core.paginator import Paginator
def show_area(request, pageindex):
'''分页'''
# 1.查询出所有省级地区的信息
areas = AreaInfo.objects.filter(aParent__isnull=True)
# 2. 分页,每页显示10条
paginator = Paginator(areas, 10)
# print(paginator.num_pages) # 返回页面总数。
# print(paginator.page_range) # 返回页码列表
# 3. 获取第pindex页的内容
if pageindex == '':
# 默认取第一页的内容
pageindex = 1
else:
pageindex = int(pageindex)
# page是Page类的实例对象
page = paginator.page(pageindex)
# 4.使用模板
return render(request, 'booktest/show_area.html', {'page': page})
show_area.html
...
<ul>
{# 遍历获取每一条数据 #}
{# {% for area in page.object_list %}#}
{% for area in page %}
<li>{{ area.atitle }}</li>
{% endfor %}
</ul>
{# 判断是否有上一页 #}
{% if page.has_previous %}
<a href="/show_area{{ page.previous_page_number }}"><上一页</a>
{% endif %}
{% for pageindex in page.paginator.page_range %}
{# 判断是否是当前页,当前页不用超链接 #}
{% if pageindex == page.number %}
{{ pageindex }}
{% else%}
<a href="/show_area{{ pageindex }}">{{ pageindex }}</a>
{% endif %}
{% endfor %}
{# 判断是否有下一页 #}
{% if page.has_next %}
<a href="/show_area{{ page.next_page_number }}">下一页></a>
{% endif %}
...
urls.py
re_path(r'^show_area(?P<pageindex>\d*)$', views.show_area, name='show_area')
实例:省市县选择:
实现以下功能:
本示例在Django中使用jquery的ajax进行数据交互。 jquery框架中提供了$.ajax、$.get(发起ajax get请求)、$.post方法,用于进行异步交互,由于Django中默认使用CSRF约束,并且只是获取信息,不修改信息,推荐使用$.get。
1)将jquery文件拷贝到static/js目录。
2)views.py中定义视图:
# /areas
def areas(request):
'''省市县选中案例'''
return render(request, 'booktest/areas.html')
# /prov
def prov(requrest):
'''获取所有省级地区的信息'''
# 1.获取所有省级地区的信息
areas = AreaInfo.objects.filter(aParent__isnull=True)
# 2.遍历areas并拼接出json数据:atitle id(需要标题和id)
areas_list = []
for area in areas:
areas_list.append((area.id, area.atitle)) # 将信息元组添加到列表
# 3.返回json数据
return JsonResponse({'data':areas_list})
def city(request, pid):
'''获取pid的下级地区的信息'''
# 1.获取pid对应地区的下级地区
# area = AreaInfo.objects.get(id=pid)
# areas = area.areainfo_set.all()
areas = AreaInfo.objects.filter(aParent__id=pid)
# 2.变量areas并拼接出json数据:atitle id
areas_list = []
for area in areas:
areas_list.append((area.id, area.atitle))
# 3.返回数据
return JsonResponse({'data': areas_list})
3)urls.py中配置url:
path('areas', views.areas, name='areas'), # 省市县选择案例
path('prov', views.prov, name='prov'), # 获取所有省级地区的信息
path('city<pid>', views.city, name='city'), # 获取省下面的市的信息
path('dis<pid>', views.city, name='dis'), # 获取市下面的县的信息(用同一个视图就可以
4)创建模板areas.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>省市县选择案例</title>
{% load static %}
<script src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
<script>
$(function () {
// 发起一个ajax请求 /prov,获取所有省级地区的信息
// 获取信息,使用get
// 涉及到信息修改,使用post
//向prov视图发起get请求
$.get('/prov', function (data) {
// 回调函数
// 获取返回的json数据
res = data.data //拿到的是数组(二维)
// 获取prov下拉列表框
prov = $('#prov')
// 遍历res数组,获取每一个元素:[地区id, 地区标题]
/*
for(i=0; i<res.length; i++){
id = res[i][0]
atitle = res[i][1]
// 拼接字符串用来添加到下拉列表框
option_str = '<option value="'+id + '">'+ atitle+ '</option>'
// 向prov下拉列表框中追加元素
prov.append(option_str)
}
*/
//$.each()也可以实现遍历
$.each(res, function (index, item) {
{#console.log(index) //下标#}
{#console.log(item) //元素#}
id = item[0]
atitle = item[1]
// 拼接字符串用来添加到下拉列表框
option_str = '<option value="'+id + '">'+ atitle+ '</option>'
// 向prov下拉列表框中追加元素
prov.append(option_str)
})
})
// 绑定prov下拉列表框的change事件,获取省下面的市的信息
$('#prov').change(function () {
// 发起一个ajax请求 /city,获取省下面市级地区的信息
// 获取点击省的id
prov_id = $(this).val()
{#$.get('/city?prov_id='+prov_id,...)#}
$.get('/city'+prov_id, function (data) {
// 获取返回的json数据
res = data.data
// 获取city下拉列表框
city = $('#city')
// 先清空city下拉列表框
city.empty().append('<option>---请选择市---</option>')
// 获取dis下拉列表框
dis = $('#dis')
// 清空dis下拉列表框
dis.empty().append('<option>---请选择县---</option>')
// 变量res数组,获取每一个元素:[地区id, 地区标题]
// 遍历取值添加到city下拉列表框中
$.each(res, function (index, item) {
id = item[0]
atitle = item[1]
option_str = '<option value="'+id + '">'+ atitle+ '</option>'
// 向city下拉列表框中追加元素
city.append(option_str)
})
})
})
// 绑定city下拉列表框的change事件,获取市下面的县的信息
$('#city').change(function () {
// 发起一个ajax请求 /dis,获取市下面县级地区的信息
// 获取点击市的id
city_id=$(this).val()
$.get('/dis'+city_id, function (data) {
// 获取返回的json数据
res = data.data
// 获取dis下拉列表框
dis = $('#dis')
// 清空dis下拉列表框
dis.empty().append('<option>---请选择县---</option>')
// 变量res数组,获取每一个元素:[地区id, 地区标题]
// 遍历取值添加到dis下拉列表框中
$.each(res, function (index, item) {
id = item[0]
atitle = item[1]
option_str = '<option value="'+id + '">'+ atitle+ '</option>'
// 向dis下拉列表框中追加元素
dis.append(option_str)
})
})
})
})
</script>
</head>
<body>
<select id="prov">
<option>---请选择省---</option>
</select>
<select id="city">
<option>---请选择市---</option>
</select>
<select id="dis">
<option>---请选择县---</option>
</select>
</body>
</html>
运行服务,在服务器访问:http://127.0.0.1:8000/areas
-----end-----