Django 之 Form 组件

常用功能

From 组件主要有以下几大功能:

  • 生成 HTML 标签
  • 验证用户数据(显示错误信息)
  • HTML Form 提交保留上次提交数据
  • 初始化页面显示内容

小试牛刀

下面我们通过 Form 组件来生成 HTML 标签和验证用户提交的数据以及保留上次提交的数据。

  1. 创建 Form 类 form_verify.py
from django import forms
from django.forms import fields
class FormVerify(forms.Form):
    user = fields.CharField(        # 字符串形式
        max_length=32,              # 最长不能超过 32 字节
        required=True,              # 不能为空
        # 错误提示,支持自定义错误信息(默认为英文)
        error_messages = {
            'required': '不能为空',
            'max_length': '最长不超过32个字节'
        })

    pwd = fields.CharField(min_length=16, max_length=32, required=True, error_messages={
        'required': '不能为空',
        'max_length': '最长不超过32个字节',
        'min_length': '最短不能少于16个字节'
    })
    
    age = fields.IntegerField(required=True, error_messages={
        'required': '不能为空',
        'invalid': '只能是整数'      # 凡是格式错误,都是用 invalid 
    })
    email = fields.EmailField(required=True, error_messages={
        'required': '不能为空',
        'invalid': '邮箱格式错误'
    })

首先我们在form_verify.py中定义了一个 Form 类 FormVerify,其中有 4 个字段 user、pwd、age、email,分别对应 form 表单中的键。每个字段都有其对应的规则。如:user 必须是字符串格(CharField),最长不能超过 32 个字节(max_length),且不能为空(required=True)等。

每个字段中还有一个 error_messages 参数,里面包含每个字段可能产生的错误信息,默认为英文,支持自定义。

  1. 视图函数 views.py
from django.shortcuts import render, redirect
from app import models
from app.form_verify import InfoVerify    # 导入自定制的 Form 类

def index(request):
    # get 请求
    if request.method == 'GET':
        obj = FormVerify()    # 创建空白表单实例将其放在模板上下文中渲染
        return render(request, 'index.html', {'obj': obj})
        
    # post 请求
    else:
        obj = FormVerify(request.POST)      # 生成一个 FormVerify 实例对象,参数为 request.POST,这一步也叫做绑定数据到表单

        # 验证用户输入的是否合法
        if obj.is_valid():
            print('验证成功', obj.cleaned_data)
            return redirect('https://www.github.com')

        # 验证失败,将错误类型返回给前端:obj.errors
        else:
            print('错误信息', obj.errors)
            return render(request, 'index.html', {'obj': obj})   
        
# 验证成功
# {'user': 'jun', 'pwd': '11111111111111111111', 'age': 18, 'email': '982562616@qq.com'}

# 错误信息 <ul class="errorlist"><li>pwd<ul class="errorlist"><li>Ensure this value has at least 16 characters (it has 3).</
# li></ul></li><li>age<ul class="errorlist"><li>Enter a whole number.</li></ul></li></ul>

在视图函数中,首先我们引入了InfoVerify,当用户使用链接访问 127.0.0.1:8080/app/index/ 时,走的是 get 请求。会创建一个空的表单实例并将其放在模板上下文中渲染,然后返回index.html给用户。

当用户填写完数据后提交数据时,走的是post请求。我们通过is_valid() 方法,验证用户输入的数据是否合法(即是否与我们设置的要求一致)。若一致则重定向到https://www.github.com,否则返回错误信息。

  1. 配置模板index.html

首先我们自己来自定义 input 框,当没有按照格式输入时,即会提示错误信息:

<form action="{% url 'index' %}" method="post">
    {% csrf_token %}
    <p><input type="text" placeholder="用户名" name="user"> {{ obj.errors.user.0 }}</p>
    <p><input type="text" placeholder="密码" name="pwd"> {{ obj.errors.pwd.0 }}</p>
    <p><input type="text" placeholder="年龄" name="age"> {{ obj.errors.age.0 }}</p>
    <p><input type="text" placeholder="邮箱" name="email"> {{ obj.errors.email.0 }}</p>
    <p><input type="submit" value="提交"></p>
    <button onclick="subForm()">Ajax 提交</button>
</form>

form%20%E7%BB%84%E4%BB%B6%E9%AA%8C%E8%AF%81.gif

  1. 生成 HTML,模板文件index.html

在实际开发中,很多时候前后端都是分离的,要使得前端 form 表单中的键(user、age..)总是与后端自定义的字段保持一致,会很麻烦。我们可以通过 Form 组件自动生成 form 表单文件,所有信息都已经封装在 FormVerify 的对象 obj 中。

<form id="fm" action="{% url 'index' %}" method="post">
    <p>{{ obj.user }}{{ obj.errors.user.0 }}</p>
    <p>{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
    <p>{{ obj.age }}{{ obj.errors.age.0 }}</p>
    <p>{{ obj.email }}{{ obj.errors.email.0 }}</p>
    <p><input type="submit" value="提交"></p>
    <button onclick="subForm()">Ajax 提交</button>
</form>

form%20%E7%BB%84%E4%BB%B6%E9%AA%8C%E8%AF%812.gif

Form 类

创建 Form 类时涉及到 字段插件,字段用于对用户请求数据的验证,插件用于自动生成 HTML。所有表单类都继承 django.forms.Form 或 django.forms.ModelForm

绑定与未绑定的表单实例

  • 未绑定的表单没有与其关联的数据,当渲染给用户时,是空的或包含默认值
  • 绑定的表单拥有已提交的数据,因此可以使用 is_valid() 来判断数据是否合法。如果渲染了一张非法的绑定的表单,将包含内联的错误信息,告知用户要纠正哪些数据。
  • 使用 is_bound 属性可以查看一张表单是否绑定数据
>>> obj = MyForm()
>>> obj.is_bound
False

字段数据

无论表单提交了什么数据,一旦调用了 is_valid()验证成功,已验证的数据将会被存储到 form.cleaned_data 字典中,这样就将数据转化为 python 数据类型。

from django.core.mail import send_mail
from django.shortcuts import render, redirect, HttpResponse

def index(request):
    obj = MyForm(request.POST)
    if obj.is_valid():
        subject = obj.cleaned_data['subject']
        message = form.cleaned_data['message']
        sender = form.cleaned_data['sender']
        cc_myself = form.cleaned_data['cc_myself']

        recipients = ['info@example.com']
        if cc_myself:
            recipients.append(sender)

        send_mail(subject, message, sender, recipients)    # 发现邮件
        return HttpResponseRedirect('/thanks/')

表单渲染

Form 组件可以生成一个 HTML 表单,但是它没法生成外层的 <form>标签 以及 submit 控件,需要自己手动提供。

对于 label、input 还有其他输出选项:

  • {{ form.as_table }} :将所有字段渲染成 tr 标签形式
  • {{ form.as_p }} :将所有字段渲染成 p 标签形式
  • {{ form.as_ul }} :将所有字段渲染成 li 标签形式

注意,必须提供外层的 tableul 元素。

Django 内置字段

Field
    required=True        # 是否可以为空
    widget=None            # HTML 插件
    label=None            # 用于生成 label 标签或显示内容
    initial=None         # 初始值
    help_text=''        # 帮助信息(显示在标签旁)
    error_message=None    # 错误信息
    show_hidden_initial=False    # 是否在当前插件后面再加一个隐藏的默认值的插件(可用于检验两次输入是否一致)
    validators=[]        # 自定义验证规则
    localize=False        # 是否支持本地化
    disabled=False        # 是否可编辑
    label_suffix=None    # label 内容后缀、

# 以下字段继承 Field,因此也继承其属性
CharField(Field)
    max_length=None        # 最大长度
    min_length=None        # 最小长度
    strip=True            # 是否移除用户输入空白

IntegerField(Field)
    max_value=None        # 最大值
    min_value=None        # 最小值

FloatField(IntegerField)
    ...

DecimalField(IntegerField)
    max_value=None        # 最大值
    min_value=None        # 最小值
    max_digits=None        # 总长度
    decimal_places=None    # 小数位长度

BaseTemporalField(Field)
    input_formats=None        # 时间格式化

DateField(BaseTemporalField)    # 格式: 2019-01-24
TimeField(BaseTemporalField)    # 格式:15:39
DateTimeField(BaseTemporalField)    # 格式:2019-01-24 15:39

DurationField(Field)        # 时间间隔:%d %H:%M:%S.%f

RegexField(CharField)
    regex                # 自定制正则表达式
    max_length=None        # 最大长度
    min_length=None        # 最小长度
    error_message=None        # 错误信息

EmailField(CharField)    # 邮箱字段,继承 CharField
    

FileField(Field)
    allow_empty_file=False        # 是否允许空文件

ImageField(FileField)
    # 需要安装 PIl 模块,pip3 install Pillow
    # FileField、ImageField 在 form 表单提交时:enctype="multipart/form-data"
    # 视图函数中:obj = MyForm(request.POST, request.File) 才能获取到文件对象

URLField(Field)
BooleanField(Field)
NullBooleanField(BooleanField)

# 选择框
ChoiceField(Field)
    choices=()        # 选框中的选项,如 choices=((0, '北京'), (1, '上海'))
    required=True        # 是否必填
    widget=None        # 插件,默认为 select 插件
    label=None        # label 内容
    initial=None        # 初始值
    help_text=''        # 帮助提示

ModelChoiceField(ChoiceField)
    # 导入模块 django.forms.models.ModelChoiceField
    queryset        # 查询数据库中的数据
    empty_label     # 默认空显示内容
    to_field_name=None        # HTML 中value 的值对应的字段
    limit_choice_to=None        # ModelForm 中对 queryset 二次筛选

ModelMultipleChoiceField(ModelChoiceField)
     # 导入模块 django.forms.models.ModelMultipleChoiceField

TypeChoiceField(ChoiceField)
    coerce=lambda val:val        # 对选中的值进行一次转换
    empty_value=''                # 空值的默认值

TypeMultipleChoiceField(MultipleChoiceField)
    coerce=lambda val:val        # 对选中的值进行一次转换
    empty_value=''                # 空值的默认值

ComboField(Field)
    fields=()            # 使用多个验证,如:验证长度以及邮箱格式
    # fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])

MultiValueField(Field)
    # 抽象类,子类中可实现聚合多个字典去匹配一个值,要配合 MultiWidget 使用

SplitDateTimeField(MultiValueField)
    input_date_formats=None        # 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None        # 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
   
# 文件选项,目录想文件显示在页面中,不常用,一般不允许显示在页面中
FilePathField(ChoiceField)    
    path        # 文件夹路径
    match=None    # 正则匹配
    recursive=False        # 递归下面的文件夹
    allow_files=True        # 允许文件
    allow_folders=False        # 允许文件夹
    required=True            # 是否为空
    widget=None           
    label=None
    initial=None
    help_text=''

GenericIPAdress
    protocol='both'        # 支持ipv4、ipv6 两种协议
    unpack_ipv4=False        # 解析 ipv4,若是:ffff:192.0.2.1,可解析为 192.0.2.1,需要注意的是 protocol 必须是 both


SlugField(CharField)        # 数字、字母、下划线、减号(连字符)

UUIDField(CharField)        # uuid 类型

Tips: 如果表单中包含 URLField、EmailField 或其他整数字段类型,Django 将使用 url、email 和 number HTML5 输入类型。默认情况下,浏览器会对这些字段进行自己的验证,若想禁用只需在 form 标签上添加 novalidate 属性即可,或在字段上指定一个不同的控件,如 TextInput

Django 常用内置插件

通过 widget 参数,再配合以下插件可转换为其他字段:

TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget

常用选择插件

选择框分为 input 标签的 radio 单选框、CheckBox 多选框、以及下拉框 select 标签的单选和多选。

from django import forms
from django.forms import fields
from django.forms import widgets

def test(request):

    obj = InfoVerify(request.POST)

    return render(request, 'test.html', {'obj': obj})

# radio 单选框,值为字符串
# 方法一
gender = fields.CharField(
     initial = 2,
     widget = widgets.RadioSelect(choices=((1, '男'), (2, '女'),))
)


# 方法二
gender = fields.ChoiceField(
    choices=((1, '男'), (2, '女'),),
    initial= 1,
    widget = widgets.RadioSelect

)

# CheckBox
# 单选,值为字符串
gender = fields.CharField(
    widget= widgets.CheckboxInput()
)

# 多选,值为列表
hobby = fields.MultipleChoiceField(
    initial= [2, ],
    choices = ((1, '爬山'), (2, '游泳'),),
    widget = widgets.CheckboxSelectMultiple
)


# select 单选,值为字符串
# 方法一
province = fields.ChoiceField(
    choices=((1, '湖南省'), (2, '湖北省'),),
    initial=2,
    widget=widgets.Select
)

# 方法二
province = fields.CharField(
    initial=2,
    widget=widgets.Select(choices=((1, '湖南省'), (2, '湖北省'),))

# 多选,值为列表
hobby = fields.MultipleChoiceField(
    choices=((1, '爬山'), (2, '游泳'),),
    initial=[1,],
    widget=widgets.SelectMultiple
)

20190124170609.png

20190124173254.png

20190124174856.png

在使用选择标签时,choices 的选项/数据可以从数据库中获取,但是因为是静态字段,因此每次增加一条数据就需要手动重启一次服务才会刷新显示在页面上。然而开发中,不可能频繁地重启服务端。我们可以通过自定义构造方法,来实时刷新增加/删除的数据。

方法一

  1. 定义 Form、以及构造方法 form_verify.py
from django import forms
from django.forms import fields
from django.forms import widgets
from app import models

class InfoVerify(forms.Form):
    user = fields.ChoiceField(
        # choices=((0, 'rose'), (1, 'lila'), (2, 'john'), ),    # 手动添加数据
        widget = widgets.Select,
        initial=2
    )


    # 定义构造方法
    def __init__(self, *args, **kwargs):
        super(InfoVerify, self).__init__(*args, **kwargs)

        # 拷贝所有静态字段将其赋值给 self.fields
        # 自动从数据库获取数据,value_list 获得是一个元组,类似于 ((0, 'rose'), (1, 'lila'),)
        self.fields['user'].widget.choices = models.PersonInfo.objects.all().values_list('id', 'username')
  1. 视图函数 views.py
def test(request):
    obj = InfoVerify()
    return render(request, 'test.html', {'obj': obj})

# test.html

{{obj.user}}

方法二

使用 Django 提供的 ModelChoiceFieldModelMultipleChoiceField 字段来实现。但是需要在models.py 中构造 __str__() 方法,因此不推荐使用。

# models.py
class PersonInfo(models.Model):
    username = models.CharField(max_length=32)
    email = models.EmailField()


def __str__(self):
    return self.username

# form_verify.py

from django.forms.models import ModelChoiceField

class InfoVerify(forms.Form):
    user = ModelChoiceField(
        queryset=models.PersonInfo.objects.all(),
        to_field_name='username'
    )

# test.html

{{obj.user}}

Form 组件拓展

简单拓展

自定义验证规则

在 Form 组件自带的正则验证基础上进行拓展:

# forms.py

from django.forms import fields
from django.forms import Form
from django.forms import widgets
from django.core.validators import RegexValidator

class MyForm(Form):
    # 方法一
    username = fields.CharField(
        # 自定义验证规则
        validators = [RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须159开头')]
    )
    
    # 方法二
    # username = fields.RegexField(r'^[0-9]+$', error_messages={'invalid': '请输入数字'})

username 字段必须是数字,且必须是 159 开头,其中 validators 是固定名字。

基于源码流程深度拓展

Form 组件验证流程

  • 首先是正则表达式验证字段是否合法(如:检查 CharField 输入的是否是字符串等)
  • 循环所有字段,执行 clean_字段名() 方法,返回 cleaned_data

源码寻找顺序:

is_valid ——> errors ——> full_clean ——> self.clean_fields()
full_clean
    self._clean_fields()    # 对定义的字段函数进行验证
    self._clean_form()      # clean 整体验证
    self._post_clean()      # 最后自定义验证,一般不适用,需要自己填写错误验证
  1. 自定义方法,单独验证某个字段

forms.py

from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from app import models
from django.forms import Form
from django.forms import fields
from django.forms import widgets


class AjaxForm(Form):
    username = fields.CharField()
    user_id = fields.IntegerField(
        widget=widgets.Select(choices=[(0, 'rose'), (1, 'lila'), (2, 'tom'), ])
    )

    # 自定义方法 clean_ 字段名
    # 返回值必须 self.cleaned_data['username']
    # 如果出错: raise ValidationError('用户名已存在')
    # 自定义方法拓展 form 组件,与源码执行顺序相同,只是重写方法而已
    def clean_username(self):
        v = self.cleaned_data['username']
        print('------------', v)        # ------------ john
        
        # 验证用户名唯一性:判断数据库中用户名的个数,若大于 0,则表示有相同的用户名
        if models.UserInfo.objects.filter(username=v).count():
            raise ValidationError('用户名已存在')
        return v

    def clean_user_id(self):

        return self.cleaned_data['user_id']
  1. 自定义方法,整体验证

使用 clean() 方法对整体数据进行验证,Django 内部提供了异常处理:

from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from app import models
from django.forms import Form
from django.forms import fields
from django.forms import widgets


class AjaxForm(Form):
    username = fields.CharField()
    email = fields.EmailField()

    # 自定义方法 clean_ 字段名
    # 返回值必须 self.cleaned_data['username']
    # 如果出错: raise ValidationError('用户名已存在')
    # 自定义方法拓展 form 组件,与源码执行顺序相同,只是重写方法而已
    def clean_username(self):
        v = self.cleaned_data['username']
        
        # 验证用户名唯一性:判断数据库中用户名的个数,若大于 0,则表示有相同的用户名
        if models.UserInfo.objects.filter(username=v).count():
            raise ValidationError('用户名已存在')
        return v        # 一定要返回,否则出现 ValueError: The given username must be set,具体请看 _clean_fields() 方法源码

    def clean_email(self):

        return self.cleaned_data['email']
    
# django 预留的钩子函数,当运行完 clean_字段名() 后,执行此函数
    def clean(self):
        value_dict = self.cleaned_data      # 获取所有通过验证的数据
        username = value_dict.get('username')
        email = value_dict.get('email')
        if models.UserInfo.objects.filter(username=username, email=email).count():
            raise ValidationError('用户名和邮箱都存在')      # 前端取时,用 __all__ 取
            
            # 前端取:console.log(arg.message.__all__[0])

    # clean() 方法执行完毕后,执行此方法,没有返回值,一般不用
    def _post_clean(self):
        pass

示例

以 Ajax 方式提交,并拓展验证数据是否合法,若不合法则显示错误信息:

  1. Form 组件配置,forms.py
from django.core.exceptions import ValidationError
from app import models
from django.forms import Form
from django.forms import fields

class AjaxForm(Form):
    username = fields.CharField(max_length=32, label='用户名')
    email = fields.EmailField(max_length=32, label='邮箱')

    # 自定义验证规则,匹配数据库中数据是否存在,单独验证某个字段
    def clean_username(self):       # 检查 username 
        username = self.cleaned_data['username']
        if models.UserInfo.objects.filter(username=username).count():
            raise ValidationError('用户名存在')
        return username

    def clean_email(self):      # 检查 email
        email = self.cleaned_data['email']
        if models.UserInfo.objects.filter(email=email).count():
            raise ValidationError('邮箱已存在')
        return email

    # 整体验证
    # def clean(self):
    #     value_dict = self.cleaned_data
    #     username = value_dict.get('username')
    #     email = value_dict.get('email')
    #     if models.UserInfo.objects.filter(username=username, email=email).count():
    #         raise ValidationError('用户名和邮箱都存在')
  1. 视图函数 views.py
from django.shortcuts import render, HttpResponse
from app.forms import AjaxForm
from django.forms.utils import ErrorDict

def ajax(request):
    """Ajax 提交"""
    if request.method == 'GET':
        obj = AjaxForm()
        return render(request, 'ajax.html', {'obj': obj})

    # POST 请求
    else:
        obj = AjaxForm(request.POST)
        ret = {'status':False, 'message':None}
        if obj.is_valid():      # 判断数据是否合法
            ret['status'] = 'True'
            return HttpResponse(json.dumps(ret))
        else:           # 不合法,返回错误信息
            print(type(obj.errors))
            print(obj.errors)
            # <class 'django.forms.utils.ErrorDict'>
            # <ul class="errorlist"><li>username<ul class="errorlist"><li>用户名存在</li></ul></li><li>email<ul class="errorlist"><li>邮
# 箱已存在</li></ul></li></ul>

            ret['message'] = obj.errors     # obj.errors 不是 Python 基本数据类型,不能被 dumps(),ret 是字典可以
            return HttpResponse(json.dumps(ret))
  1. 路由 urls.py
from django.urls import path
from app import views

urlpatterns = [
    path('ajax/', views.ajax, name= 'ajax'),
]
  1. 模块 templates/ajax.html
{% load static %}       // 加载静态文件 static
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Ajax 测试页面</title>
</head>
<body>
    <h1>Ajax测试页面</h1>
    <form action="{% url 'ajax' %}" method="POST" id="fm">
        {% csrf_token %}
        <p>{{ obj.username.label }} {{ obj.username }}</p>
        <p>{{ obj.email.label }} {{ obj.email }}</p>
        <input type="button" id="btn" value="提交">
    </form>

    <script src="{% static 'jquery-3.1.1.js' %}"></script>
    <script>
        $(function () {
            $('#btn').click(function () {
                $.ajax({
                    url: '/app/ajax/',
                    type: 'POST',
                    data: $('#fm').serialize(),
                    dataType: 'JSON',
                    success:function (arg) {
{#                        console.log(arg);#}
                        // 根据后台判断的数据是否合法,合法则跳转到 'https://map.baidu.com/'
                        if (arg.status == 'True'){
                            window.location.href = 'https://map.baidu.com/'
                        }

                        // 数据不合法则显示错误信息
                       else {
                            console.log(arg);

                            if (arg.message.username && arg.message.email){
                                var e1 = arg.message.username[0];   // 用户名已存在
                                var e2 = arg.message.email[0];      //  邮箱已存在

                                var span1 = $('<span></span>');
                                span1.html(e1);
                                $('#id_username').after(span1);

                                var span2 = $('<span></span>');
                                span2.html(e2);
                                $('#id_email').after(span2);
                            }

                            else if (arg.message.username){
                                var e1 = arg.message.username[0];   // 用户名已存在

                                var span1 = $('<span></span>');
                                span1.html(e1);
                                $('#id_username').after(span1);
                            }

                            else {
                                var e2 = arg.message.email[0];      //  邮箱已存在
                                var span2 = $('<span></span>');
                                span2.html(e2);
                                $('#id_email').after(span2);
                            }
{#                            console.log(arg.message.__all__[0])#}   //  整体验证错误信息取值

                        }
                    }

                })
            })
        })
    </script>
</body>
</html>

当用户访问 127.0.0.1:8080/app/ajax/ 时,渲染出 form 表单。用户输入用户名、邮箱后以 Ajax 方式提交到后台,后台判断数据是否合法。若合法则页面跳转到 'https://map.baidu.com/'。否则创建 span 标签,将错误信息显示在页面上。

  1. 配置信息 settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
....
STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'app', 'static'),
)

TEMPLATE_DIRS = (os.path.join(BASE_DIR,  'templates'),)

Django 序列化

Django 序列化是将数据库中检索到数据返回给前端,尤其是 Ajax 请求返回的一般是 Json 格式。

几种常见数据序列化方法:

  • models.Databases.objects.all():返回 QuerySet 对象,需要用 serializers 模块序列化。前端需要反序列化 JSON.parse(arg.message)
  • .value() :返回的内部是一个个的字典,使用 list() 即可将 QuerySet 转换为列表,再使用 json.dumps() 序列化即可。
  • .value_list() :返回的内部是一个个的元组,使用 list() 即可将 QuerySet 转换为列表,再使用 json.dumps() 序列化即可。
  1. 路由系统 urls.py
from django.urls import path
from app import views

urlpatterns = [
    path('xulihua/', views.xulihua, name='xulihua'),
    path('get_data/', views.get_data, name='get_data'),
]
  1. 视图函数 views.py
from django.shortcuts import render, HttpResponse, redirect
from django.core import serializers     # 将对象序列化字符串
import json
from app import models


def xulihua(request):
    return render(request, 'xulihua.html')

def get_data(request):
     res = {'status': True, 'message': None}
    try:
        # serializers 只能序列化 queryset
        # 数据是 QuerySet 对象
        # user_list = models.UserInfo.objects.all()
        # res['message'] = serializers.serialize('json', user_list)   # 将对象序列化为 json 格式字符串
        # print(ret['message'])
        # print(res)

        # <QuerySet [<UserInfo: rose>, <UserInfo: lila>]>
        # {'status': True, 'message': '[{"model": "app.userinfo", "pk": 1, "fields": {"username": "rose", "email": "xxxxx@qq.com"}}, {"model": "app.userinfo", "pk": 2, "fields": {"username": "lila", "email": "123@gmail.com"}}]'}


        # 数据是字典
        # user_list = models.UserInfo.objects.values('id', 'username')
        # print(user_list)    # <QuerySet [{'id': 1, 'username': 'rose'}, {'id': 2, 'username': 'lila'}]>
        # print(list(user_list))      # [{'id': 1, 'username': 'rose'}, {'id': 2, 'username': 'lila'}]
        # res['message'] = list(user_list)

        # 数据是元组
        user_list = models.UserInfo.objects.values_list('id', 'username')
        print(user_list)         # < QuerySet[(1, 'rose'), (2, 'lila')] >
        print(list(user_list))      # [(1, 'rose'), (2, 'lila')]
        
        res['message'] = list(user_list)
       
    except Exception as e:
        res['message'] = False
        print(e)

    result = json.dumps(res)            # 序列化为 json 字符串
    return HttpResponse(result)

在后台获取数据,并将其序列化为 JSON 字符串,返回给前端,在前端渲染成相应标签元素。

  1. 前端 xulihua.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>序列化</title>
</head>
<body>
    <h1>用户列表</h1>

    <table id="tb"></table>

    <script src="{% static 'jquery-3.1.1.js' %}"></script>
    <script>
        $(function () {
            initData();         // 执行 initData() 函数
        });

        function initData() {
            $.ajax({
                url: '/app/get_data',
                type: 'GET',
                dataType: 'JSON',
                success:function (arg) {
                    if (arg.status){
                        // 后台获取的数据为 QuerySet 对象时,需要在前端反序列化
{#                        var v = JSON.parse(arg.message);#}
{#                        console.log(v);#}

                    // 后台获取的数据为 dict、tuple 时,不需要反序列化
                        console.log(arg.message);
                    }
                }
            })
        }
    </script>
</body>
</html>

访问 127.0.0.1:8080/app/xulihua/ 时,会往后台发送两个请求,一个为 xulihua/,一个为 get_data/(由 Ajax 发起,用 Chrome — 检查 — Network 即可查看)。

get_data.html

{% for row in user_list %}
    <tr>
        <td>{{ row.id }}</td>
        <td>{{ row.username }}</td>
        <td>{{ row.email }}</td>
    </tr>
{% endfor %}

xulihux.gif


总结

  • serializers 模块只能序列化 QuerySet 对象,不能序列化 Python 基本数据类型,前端需要反序列化
  • value/value_list 使用 list() 函数转换为列表,即可 json.dumps(),不需要反序列化。
  • 最好是在后台获取数据后序列化为 json 字符串,再在前端渲染为 HTML。不要在后台生成 HTML 文档后再返回给前端。

Django 文件上传

img = request.FILES.get('img')

img.read()      # 读取文件,适合小文件
img.chunks()    # 按块返回文件,通过 for 循环迭代,将大文件写入服务器中
img.multiple_chunks()       # 根据 img 大小,返回 true、false。大于 2.5M(默认)返回 true,否则返回 false。因此可以用来决定使用 read 还是 chunks

if img.multiple_chunks() ==  False:
    img.read()
else:
    img.chunks()
    
img.name        # 获取上传文件文件名,包含后缀名
img.size        # 获取上传文件文件大小,字节

方法一

通过 form 表单中,input 标签的 file 完成:

  1. 前端 uploader.html
<!DOCTYPE html>
<html lang="en">  
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/app01/uploader/" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <input type="text" name="username">
        <input type="file" name="img">
        <input type="submit" value="提交">
    </form>
</body>
</html>
  1. 视图函数 views.py
from django.shortcuts import render, HttpResponse
import os

# static 文件夹路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
static_dirs = os.path.join(BASE_DIR, 'static')
print(static_dirs)  # E:\PycharmProjects\university_test\day61\app01\static

def uploader(request):
    if request.method == 'GET':
        return render(request, 'uploader.html')
    else:
        try:
            username = request.POST.get('username')
            img = request.FILES.get('img', None)        # 没有文件上传默认为 None
            if not img:
                return HttpResponse('没有文件上传')
            
            # 把上传的文件存入 static 文件夹中
            f = open(os.path.join(static_dirs, img.name), 'wb')
            for chunk in img.chunks(chunk_size=1024):       # 把文件以块的方式写入
                f.write(chunk)
            f.close()
            return HttpResponse('上传成功')
        except Exception as e:
            print(e)
            return HttpResponse(e)

上传的文件存在 request.FILES 文件对象中,包含文件大小 img.name,文件大小 img.size 。获取文件名后在服务器创建一个同名文件,再以块img.chunks() 的方式循环读取写入文件中。

  1. 以 Form 组件方式上传文件:
from django.shortcuts import render, HttpResponse
import os
from django.forms import fields
from django import forms

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
static_dirs = os.path.join(BASE_DIR, 'static')

# Form 组件
class UploaderForm(forms.Form):
    username = fields.CharField()
    img = fields.FileField()


def uploader(request):
    if request.method == 'GET':
        obj = UploaderForm()
        return render(request, 'uploader.html', {'obj': obj})
    else:
        obj = UploaderForm(request.POST, request.FILES)
        if obj.is_valid():
            username = obj.cleaned_data['username']
            img = obj.cleaned_data['img']

            try:
                if not img:
                    return HttpResponse('没有文件上传')
                f = open(os.path.join(static_dirs, img.name), 'wb')
                for chunk in img.chunks(chunk_size=1024):
                    f.write(chunk)
                f.close()
                return HttpResponse('上传成功')
            except Exception as e:
                print(e)
                return HttpResponse(e)
        else:
            return HttpResponse('文件类型错误!')

uploader.gif

转载于:https://www.cnblogs.com/midworld/p/11380506.html

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
<p> <span style="font-size:14px;color:#E53333;">限时福利1:</span><span style="font-size:14px;">购课进答疑群专享柳峰(刘运强)老师答疑服务</span> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:14px;"></span> </p> <p> <span style="font-size:14px;color:#337FE5;"><strong>为什么需要掌握高性能的MySQL实战?</strong></span> </p> <p> <span><span style="font-size:14px;"><br /> </span></span> <span style="font-size:14px;">由于互联网产品用户量大、高并发请求场景多,因此对MySQL的性能、可用性、扩展性都提出了很高的要求。使用MySQL解决大量数据以及高并发请求已经是程序员的必备技能,也是衡量一个程序员能力和薪资的标准一。</span> </p> <p> <br /> </p> <p> <span style="font-size:14px;">为了让大家快速系统了解高性能MySQL核心知识全貌,我为你总结了</span><span style="font-size:14px;">「高性能 MySQL 知识框架图」</span><span style="font-size:14px;">,帮你梳理学习重点,建议收藏!</span> </p> <p> <br /> </p> <p> <img alt="" src="https://img-bss.csdnimg.cn/202006031401338860.png" /> </p> <p> <br /> </p> <p> <span style="font-size:14px;color:#337FE5;"><strong>【课程设计】</strong></span> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <span style="font-size:14px;">课程分为四大篇章,将为你建立完整的 MySQL 知识体系,同时将重点讲解 MySQL 底层运行原理、数据库的性能调优、高并发、海量业务处理、面试解析等。</span> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <span style="font-size:14px;"></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>一、性能优化篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括经典 MySQL 问题剖析、索引底层原理和事务与锁机制。通过深入理解 MySQL 的索引结构 B+Tree ,学员能够从根本上弄懂为什么有些 SQL 走索引、有些不走索引,从而彻底掌握索引的使用和优化技巧,能够避开很多实战中遇到的“坑”。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>二、MySQL 8.0新特性篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括窗口函数和通用表表达式。企业中的许多报表统计需求,如果不采用窗口函数,用普通的 SQL 语句是很难实现的。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>三、高性能架构篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括主从复制和读写分离。在企业的生产环境中,很少采用单台MySQL节点的情况,因为一旦单个节点发生故障,整个系统都不可用,后果往往不堪设想,因此掌握高可用架构的实现是非常有必要的。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>四、面试篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">程序员获得工作的第一步,就是高效的准备面试,面试篇主要从知识点回顾总结的角度出发,结合程序员面试高频MySQL问题精讲精练,帮助程序员吊打面试官,获得心仪的工作机会。</span> </p>
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页