• Django Form


    一、在Django 中构建一个表单

    Form 类

    我们已经计划好了我们的 HTML 表单应该呈现的样子。在Django 中,我们的起始点是这里:

    forms.py
    from django import forms
    
    class NameForm(forms.Form):
        your_name = forms.CharField(label='Your name', max_length=100)
    

    它定义一个Form 类,只带有一个字段(your_name)。我们已经对这个字段使用一个友好的标签,当渲染时它将出现在<label> 中(在这个例子中,即使我们省略它,我们指定的label还是会自动生成)。

    字段允许的最大长度通过max_length 定义。它完成两件事情。首先,它在HTML 的<input> 上放置一个maxlength="100" (这样浏览器将在第一时间阻止用户输入多于这个数目的字符)。它还意味着当Django 收到浏览器发送过来的表单时,它将验证数据的长度。

    Form 的实例具有一个is_valid() 方法,它为所有的字段运行验证的程序。当调用这个方法时,如果所有的字段都包含合法的数据,它将:

    • 返回True
    • 将表单的数据放到cleaned_data 属性中。

    完整的表单,第一次渲染时,看上去将像:

    <label for="your_name">Your name: </label>
    <input id="your_name" type="text" name="your_name" maxlength="100">
    

    注意它不包含 <form> 标签和提交按钮。我们必须自己在模板中提供它们。

    视图

    发送给Django 网站的表单数据通过一个视图处理,一般和发布这个表单的是同一个视图。这允许我们重用一些相同的逻辑。

    要操作一个通过URL发布的表单,我们要在视图中实例表单。

    views.py
    from django.shortcuts import render
    from django.http import HttpResponseRedirect
    
    from .forms import NameForm
    
    def get_name(request):
        # if this is a POST request we need to process the form data
        if request.method == 'POST':
            # create a form instance and populate it with data from the request:
            form = NameForm(request.POST)
            # check whether it's valid:
            if form.is_valid():
                # process the data in form.cleaned_data as required
                # ...
                # redirect to a new URL:
                return HttpResponseRedirect('/thanks/')
    
        # if a GET (or any other method) we'll create a blank form
        else:
            form = NameForm()
    
        return render(request, 'name.html', {'form': form})
    

    如果访问视图的是一个GET 请求,它将创建一个空的表单实例并将它放置到要渲染的模板的上下文中。这是我们在第一次访问该URL 时预期发生的情况。

    如果表单的提交使用POST 请求,那么视图将再次创建一个表单实例并使用请求中的数据填充它:form =NameForm(request.POST)这叫做”绑定数据至表单“(它现在是一个绑定的表单)。

    我们调用表单的is_valid() 方法;如果它不为True,我们将带着这个表单返回到模板。这时表单不再为空(未绑定),所以HTML 表单将用之前提交的数据填充,然后可以根据要求编辑并改正它。

    如果is_valid() 为True,我们将能够在cleaned_data 属性中找到所有合法的表单数据。在发送HTTP 重定向给浏览器告诉它下一步的去向之前,我们可以用这个数据来更新数据库或者做其它处理。

    模板

    我们不需要在name.html 模板中做很多工作。最简单的例子是:

    <form action="/your-name/" method="post">
        {% csrf_token %}
        {{ form }}
        <input type="submit" value="Submit" />
    </form>
    

    根据{{ form }},所有的表单字段和它们的属性将通过Django 的模板语言拆分成HTML 标记 。

    表单和跨站请求伪造的防护

    Django 原生支持一个简单易用的跨站请求伪造的防护当提交一个启用CSRF 防护的POST 表单时,你必须使用上面例子中的csrf_token 模板标签。然而,因为CSRF 防护在模板中不是与表单直接捆绑在一起的,这个标签在这篇文档的以下示例中将省略。

    HTML5 输入类型和浏览器验证

    如果你的表单包含URLFieldEmailField 或其它整数字段类型,Django 将使用urlemail和 number 这样的HTML5 输入类型。默认情况下,浏览器可能会对这些字段进行它们自身的验证,这些验证可能比Django 的验证更严格。如果你想禁用这个行为,请设置form 标签的novalidate 属性,或者指定一个不同的字段,如TextInput

    现在我们有了一个可以工作的网页表单,它通过Django Form 描述、通过视图处理并渲染成一个HTML <form>

    这是你入门所需要知道的所有内容,但是表单框架为了便利提供了更多的内容。一旦你理解了上面描述的基本处理过程,你应该可以理解表单系统的其它功能并准备好学习更多的底层机制。

    二、Django Form 类详解

    所有的表单类都作为django.forms.Form 的子类创建,包括你在Django 管理站点中遇到的ModelForm

    模型和表单

    实际上,如果你的表单打算直接用来添加和编辑Django 的模型,ModelForm 可以节省你的许多时间、精力和代码,因为它将根据Model 类构建一个表单以及适当的字段和属性。 

    绑定的和未绑定的表单实例

    绑定的和未绑定的表单 之间的区别非常重要:

    • 未绑定的表单没有关联的数据。当渲染给用户时,它将为空或包含默认的值。
    • 绑定的表单具有提交的数据,因此可以用来检验数据是否合法。如果渲染一个不合法的绑定的表单,它将包含内联的错误信息,告诉用户如何纠正数据。

    表单的is_bound 属性将告诉你一个表单是否具有绑定的数据。

    字段详解

    考虑一个比上面的迷你示例更有用的一个表单,我们可以用它来在一个个人网站上实现“contact me”功能:

    forms.py
    from django import forms
    
    class ContactForm(forms.Form):
        subject = forms.CharField(max_length=100)
        message = forms.CharField(widget=forms.Textarea)
        sender = forms.EmailField()
        cc_myself = forms.BooleanField(required=False)
    

    我们前面的表单只使用一个字段your_name,它是一个CharField在这个例子中,我们的表单具有四个字段:subjectmessagesender 和cc_myself共用到三种字段类型:CharFieldEmailField 和 BooleanField完整的字段类型列表可以在表单字段中找到。

    窗口小部件

    每个表单字段都有一个对应的Widget 类,它对应一个HTML 表单Widget,例如<input type="text">

    在大部分情况下,字段都具有一个合理的默认Widget。例如,默认情况下,CharField 具有一个TextInput Widget,它在HTML 中生成一个<input type="text">如果你需要<textarea>,在定义表单字段时你应该指定一个合适的Widget,例如我们定义的message字段。

    字段的数据

    不管表单提交的是什么数据,一旦通过调用is_valid() 成功验证(is_valid() 返回True),验证后的表单数据将位于form.cleaned_data 字典中。这些数据已经为你转换好为Python 的类型。

    此时,你依然可以从request.POST 中直接访问到未验证的数据,但是访问验证后的数据更好一些。

    在上面的联系表单示例中,cc_myself 将是一个布尔值。类似地,IntegerField 和FloatField 字段分别将值转换为Python 的intfloat

    下面是在视图中如何处理表单数据:

    views.py
    from django.core.mail import send_mail
    
    if form.is_valid():
        subject = form.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/')
    

    提示

    关于Django 中如何发送邮件的更多信息,请参见发送邮件

    有些字段类型需要一些额外的处理。例如,使用表单上传的文件需要不同地处理(它们可以从request.FILES 获取,而不是request.POST)。如何使用表单处理文件上传的更多细节,请参见绑定上传的文件到一个表单

    三、使用表单模板

    你需要做的就是将表单实例放进模板的上下文。如果你的表单在Context 中叫做form,那么 {{ form }} 将正确地渲染它的<label> 和<input>元素。

    表单渲染的选项

    表单模板的额外标签

    不要忘记,表单的输出不 包含<form> 标签,和表单的submit 按钮。你必须自己提供它们。

    对于<label>/<input> 对,还有几个输出选项:

    • {{ form.as_table }} 以表格的形式将它们渲染在<tr> 标签中
    • {{ form.as_p }}  将它们渲染在<p> 标签中
    • {{ form.as_ul }} 将它们渲染在<li> 标签中

    注意,你必须自己提供<table> 或<ul> 元素。

    下面是我们的ContactForm 实例的输出{{ form.as_p }}

    <p><label for="id_subject">Subject:</label>
        <input id="id_subject" type="text" name="subject" maxlength="100" /></p>
    <p><label for="id_message">Message:</label>
        <input type="text" name="message" id="id_message" /></p>
    <p><label for="id_sender">Sender:</label>
        <input type="email" name="sender" id="id_sender" /></p>
    <p><label for="id_cc_myself">Cc myself:</label>
        <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>
    

    注意,每个表单字段具有一个ID 属性并设置为id_<field-name>,它被一起的label 标签引用。它对于确保屏幕阅读软件这类的辅助计算非常重要。你还可以自定义label 和 id 生成的方式

    更多信息参见 输出表单为HTML

    手工渲染字段

    我们没有必要非要让Django 来分拆表单的字段;如果我们喜欢,我们可以手工来做(例如,这样允许重新对字段排序)。每个字段都是表单的一个属性,可以使用{{ form.name_of_field }} 访问,并将在Django 模板中正确地渲染。例如:

    {{ form.non_field_errors }}
    <div class="fieldWrapper">
        {{ form.subject.errors }}
        <label for="{{ form.subject.id_for_label }}">Email subject:</label>
        {{ form.subject }}
    </div>
    <div class="fieldWrapper">
        {{ form.message.errors }}
        <label for="{{ form.message.id_for_label }}">Your message:</label>
        {{ form.message }}
    </div>
    <div class="fieldWrapper">
        {{ form.sender.errors }}
        <label for="{{ form.sender.id_for_label }}">Your email address:</label>
        {{ form.sender }}
    </div>
    <div class="fieldWrapper">
        {{ form.cc_myself.errors }}
        <label for="{{ form.cc_myself.id_for_label }}">CC yourself?</label>
        {{ form.cc_myself }}
    </div>
    

    完整的<label> 元素还可以使用label_tag() 生成。例如:

    <div class="fieldWrapper">
        {{ form.subject.errors }}
        {{ form.subject.label_tag }}
        {{ form.subject }}
    </div>
    

    渲染表单的错误信息

    当然,这个便利性的代价是更多的工作。直到现在,我们没有担心如何展示错误信息,因为Django 已经帮我们处理好。在下面的例子中,我们将自己处理每个字段的错误和表单整体的各种错误。注意,表单和模板顶部的{{ form.non_field_errors }} 查找每个字段的错误。

    使用{{ form.name_of_field.errors }} 显示表单错误的一个清单,并渲染成一个ul。看上去可能像:

    <ul class="errorlist">
        <li>Sender is required.</li>
    </ul>
    

    这个ul 有一个errorlist CSS 类型,你可以用它来定义外观。如果你希望进一步自定义错误信息的显示,你可以迭代它们来实现:

    {% if form.subject.errors %}
        <ol>
        {% for error in form.subject.errors %}
            <li><strong>{{ error|escape }}</strong></li>
        {% endfor %}
        </ol>
    {% endif %}
    

    非字段错误(以及使用form.as_p() 时渲染的隐藏字段错误)将渲染成一个额外的CSS 类型nonfield 以助于和字段错误信息区分。例如,{{ form.non_field_errors }} 看上去会像:

    <ul class="errorlist nonfield">
        <li>Generic validation error</li>
    </ul>
    
    Changed in Django 1.8:

    添加上面示例中提到的nonfield CSS 类型。

    参见Forms API 以获得关于错误、样式以及在模板中使用表单属性的更多内容。

    迭代表单的字段

    如果你为你的表单使用相同的HTML,你可以使用{% for %} 循环迭代每个字段来减少重复的代码:

    {% for field in form %}
        <div class="fieldWrapper">
            {{ field.errors }}
            {{ field.label_tag }} {{ field }}
        </div>
    {% endfor %}
    

    {{ field }} 中有用的属性包括:

    {{ field.label }}
    字段的label,例如Email address
    {{ field.label_tag }}

    包含在HTML <label> 标签中的字段Label。它包含表单的label_suffix例如,默认的label_suffix 是一个冒号:

    <label for="id_email">Email address:</label>
    
    {{ field.id_for_label }}
    用于这个字段的ID(在上面的例子中是id_email)。如果你正在手工构造label,你可能想使用它代替label_tag如果你有一些内嵌的JavaScript 并且想避免硬编码字段的ID,这也是有用的。
    {{ field.value }}
    字段的值,例如someone@example.com
    {{ field.html_name }}
    输入元素的name 属性中将使用的名称。它将考虑到表单的前缀。
    {{ field.help_text }}
    与该字段关联的帮助文档。
    {{ field.errors }}
    输出一个<ul class="errorlist">,包含这个字段的验证错误信息。你可以使用{% for error in field.errors %}自定义错误的显示。 这种情况下,循环中的每个对象只是一个包含错误信息的简单字符串。
    {{ field.is_hidden }}
    如果字段是隐藏字段,则为True,否则为False作为模板变量,它不是很有用处,但是可以用于条件测试,例如:
    {% if field.is_hidden %}
       
    {% endif %}
    
    {{ field.field }}
    表单类中的Field 实例,通过BoundField 封装。你可以使用它来访问Field 属性,例如{% char_field.field.max_length %}

    迭代隐藏和可见的字段

    如果你正在手工布局模板中的一个表单,而不是依赖Django 默认的表单布局,你可能希望将<input type="hidden"> 字段与非隐藏的字段区别对待。例如,因为隐藏的字段不会显示,在该字段旁边放置错误信息可能让你的用户感到困惑 —— 所以这些字段的错误应该有区别地来处理。

    Django 提供两个表单方法,它们允许你独立地在隐藏的和可见的字段上迭代:hidden_fields() 和visible_fields()下面是使用这两个方法对前面一个例子的修改:

    
    {% for hidden in form.hidden_fields %}
    {{ hidden }}
    {% endfor %}
    
    {% for field in form.visible_fields %}
        <div class="fieldWrapper">
            {{ field.errors }}
            {{ field.label_tag }} {{ field }}
        </div>
    {% endfor %}
    

    这个示例没有处理隐藏字段中的任何错误信息。通常,隐藏字段中的错误意味着表单被篡改,因为正常的表单填写不会改变它们。然而,你也可以很容易地为这些表单错误插入一些错误信息显示出来。

    可重用的表单模板

    如果你的网站在多个地方对表单使用相同的渲染逻辑,你可以保存表单的循环到一个单独的模板中来减少重复,然后在其它模板中使用include 标签来重用它:

    # In your form template:
    {% include "form_snippet.html" %}
    
    # In form_snippet.html:
    {% for field in form %}
        <div class="fieldWrapper">
            {{ field.errors }}
            {{ field.label_tag }} {{ field }}
        </div>
    {% endfor %}

    其他:
    from django.forms import Form
    from django.forms import fields
    from django.forms import widgets
    
    
    class UserForm(Form):
        username = fields.CharField(label='用户名')
        password = fields.CharField(label='密码', widget=widgets.PasswordInput)
        confirm_password = fields.CharField(label='确认密码', widget=widgets.PasswordInput)
        email = fields.EmailField(label='邮箱')
        gender = fields.ChoiceField(label='性别', choices=((1, ''), (2, '')), initial=1, widget=widgets.RadioSelect)
        age = fields.IntegerField(label='年龄')
        birthday = fields.DateField(label='生日', widget=widgets.DateInput(attrs={'type': 'date'}))
        salary = fields.DecimalField(label='收入')
        hobby = fields.MultipleChoiceField(label='爱好', choices=((1, '唱歌'), (2, '跳舞'), (3, '游泳')), initial=(1, 2), widget=widgets.CheckboxSelectMultiple)
        introduce = fields.CharField(label='介绍', widget=widgets.Textarea)
        is_agree = fields.BooleanField(label='是否同意', initial=True)
    自定义错误信息:
    email = fields.EmailField(label='邮箱', error_messages={'required': '必填', 'invalid': '邮箱格式不正确'})

    验证:

    from django.forms import Form
    from django.forms import fields
    from django.core.validators import RegexValidator
    from django.core.exceptions import ValidationError
    
    
    class UserForm(Form):
        username = fields.CharField(label='用户名', max_length=10, validators=[RegexValidator(r'^w+$', '数字或字母或_'), ], error_messages={'required': '必填', 'max_length': '最多10个字符'})
        
        def clean_username(self):
            username = self.cleaned_data['username']
            # 数据库 判断是否唯一
            if True:
                raise ValidationError('用户名已经存在')
            return username
    from django.forms import Form
    from django.forms import fields
    from django.forms import widgets
    from django.core.exceptions import ValidationError
    
    
    class UserForm(Form):
        password = fields.CharField(label='密码', widget=widgets.PasswordInput, error_messages={'required': '必填'})
        confirm_password = fields.CharField(label='确认密码', widget=widgets.PasswordInput, error_messages={'required': '必填'})
    
        def clean(self):
            password = self.cleaned_data.get('password', '')
            confirm_password = self.cleaned_data.get('confirm_password', '')
            if password != confirm_password:
                self.add_error('confirm_password', ValidationError('两次输入不一致'))
            return self.cleaned_data

     form 数据更新问题:

    class UserForm(Form):
        name = fields.ChoiceField()
    
        def __init__(self, *args, **kwargs):
            super(UserForm, self).__init__(*args, **kwargs)
            self.fields['name'].choices = models.UserInfo.objects.values_list('id', 'name')

    modelform 使用:

    from .models import *
    from django.forms import ModelForm
    from django.forms import widgets as Fwidgets
    from django.forms import fields
    class DeptModelForm(ModelForm):
    is_rmb = fields.CharField(widget=Fwidgets.CheckboxInput()) # 额外字段
    class Meta: model = Dept fields = '__all__' # fields = ['name', ] # exclude = ['name', ] widgets = { 'dept_desc': Fwidgets.Textarea(attrs={'class': 'form-control', 'rows': '10', 'cols': '20'}) }
    def add(request):if request.method == 'GET':
            model_form = DeptModelForm()
        else:
            model_form = DeptModel(data=request.POST)
            if model_form.is_valid():
                model_form.save()
        return render(request, 'add.html', {'model_form': model_form})

     注:模型中定义的verbose_name,blank等 均是为 modelform 提供的设置项

  • 相关阅读:
    linux串口
    在demo板上用串口和AT指令调试GPRS模块
    发送短信
    html
    JavaScript
    frp
    sunke推荐
    ubus
    2021-8
    缓存一致性协议
  • 原文地址:https://www.cnblogs.com/liuxiaowei/p/8004651.html
Copyright © 2020-2023  润新知