【Django 笔记】常用功能--文件上传、上传图片、分页、省市县选择(实例

笔记主要基于官方文档,从中提取要点和记录笔记,关键处包含了官方文档链接。详见官方文档。

官方文档:Django documentation 

博客推荐:Django2.2教程

目录

1.文件上传

1.1.简单文件上传

1.2.通过模型来处理上传的文件

1.3.同时上传多个文件

1.4.上传文件处理器Handlers

1.5.上传图片举例

2.分页

实例:省市县选择


1.文件上传

可参考官方文档 视图层 文件上传部分: 概览 | 文件对象 | 存储 API | 管理文件 | 自定义存储

Django在处理文件上传时,文件数据被打包封装在 request.FILES 中。

1.1.简单文件上传

  1. 定义包含 FileField 的form表单模型并在模板中使用
  2. 定义接收文件的视图
  3. 定义处理上传文件的视图

(以下为官方文档例子,根据自己项目稍微改动即可运行)

(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中上传图片包括两种方式:

  1. 通过管理页面admin中上传图片
  2. 网站的用户自定义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 对象

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文档)

class Page(object_list, number, paginator)[源代码]

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)

  1. 查询出所有省级地区的信息。
  2. 按每页显示10条信息进行分页,默认显示第一页的信息,下面并显示出页码。
  3. 点击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 }}">&lt;上一页</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 }}">下一页&gt;</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-----

发布了50 篇原创文章 · 获赞 10 · 访问量 6587

猜你喜欢

转载自blog.csdn.net/qq_23996069/article/details/104953252
今日推荐