Django Form

简单使用

  1. 自定义一个django.forms.Form子类
  2. 根据forms的类型定义需要的form的标签
  3. 可以指定参数,如label和max_length
  4. 视图中使用is_valid()方法,form全部通过,返回True
  5. 使用cleaned_data属性,这是is_valid之后的数据
# web/forms.py

from django import forms


class USER(forms.Form):
    name = forms.CharField(max_length=32, label="用户名")
    email = forms.EmailField(max_length=128)


# web/views.py

from django.shortcuts import render
from django.http import HttpResponse
from web.forms import USER


# Create your views here.

def register(request):

    if request.method == "GET":
        form = USER()
        return render(request, "web/register.html", {"form": form})

    if request.method == "POST":
        form = USER(request.POST)

        if form.is_valid():
            print(form.cleaned_data)

    return HttpResponse("ok!")

<!-- web/register.html -->

<!DOCTYPE html>
<html lang="en">
<head>
    
    <title>register</title>
</head>
<body>
<form action="{% url "web:register" %}" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="提交">
</form>
</body>
</html>

这样我们用GET方式访问web/register就会生成形如这样的表单:

<form action="/web/register" method="post">
    <input type="hidden" name="csrfmiddlewaretoken" value="LpAyJTwviENuwiy92DEkOCXAwxte8rGDbBMSgSXvbdOtrnSa81Rd18ZWCdiO7Hyl">
    <label for="id_name">用户名:</label><input type="text" name="name" maxlength="32" required="" id="id_name">
<label for="id_email">Email:</label><input type="email" name="email" maxlength="128" required="" id="id_email">
    <input type="submit" value="提交">
</form>

注意:form标签和 submit是手动生成的!!

form字段

从上面可以看出,要生成什么input标签,都是我们通过定义类属性来实现的。该类属性就相当于model的字段:

字段 对应input 参数 空值 错误
BooleanField checkbox False required
CharField text max_length、min_length 、strip、empty_value empty_value参数 required、max_length、min_length
ChoiceField select choices = [("value", "show in page"), ] 空字符串 required、invalid_choice
DateTimeField text input_formats 时间格式 None required、invalid
DateField text input_formats 时间格式 None required、invalid
EmailField email max_length、min_length 和 empty_value empty_value参数 required、invalid
IntegerField number or text max_value 和 min_value None required、invalid、max_value、min_value
FileField file max_length 和 allow_empty_file None required、invalid、missing、empty、max_length
ImageField 需要Pillow file None required、invalid、missing、empty、invalid_image

详见官网:内置 Field 类

form字段的参数

这里指的是通用参数

参数 说明
required 空值时ValidationError 异常,可指定False
label 指定label标签
label_suffix 指定label后缀,如user:变为user=
initial 指定默认值
widget 指定部件(处理 HTML 的渲染),点击这里查看
help_text 在字段标签后面加上span标签作为提示
error_messages 一个字典,key为该字段可以触发的错误,value为提示信息
disabled 是否可以禁用
validators 指定验证器,点击这里查看验证器
localize 实现表单数据输入和渲染输出的本地化

form的api

如:


from django import forms


class UserForm(forms.Form):
    name = forms.CharField(max_length=32, label="用户名")

u = UserForm()

那么u有什么api呢??

  1. .is_bound
    有无绑定表单实例,UserForm({”name": "abc"})为True,否则为False
  2. .clean()
    运行自定义的钩子函数进行验证,如何自定义见下文。
  3. .is_valid()
    验证数据
  4. .cleaned_data
    已经经过检验的数据
  5. .errors
    获取错误信息的html
    .errors.as_data() 获得错误信息字典,如{'email': ['错误邮箱格式']}
    .errors.as_json() 获得错误信息的json数据,如{"email": [{"message": "u9519u8befu90aeu7bb1u683cu5f0f", "code": "invalid"}]}
  6. .add_error(field, error)
    手动添加,会在cleaned_data中删除字段。error 参数可以是一个字符串,但最好是 ValidationError 的实例,如何抛出见这里
  7. .has_error(field, code=None)
    本方法返回一个布尔值,表示一个字段是否有特定错误 code 的错误。如果 code 是 None,如果字段包含任何错误,它将返回 True。
  8. .has_changed()
    是否与初始数据发生变化,form的initial参数指定,也可以在实例化时指定:u = UserForm(initial={'name': 'lczmx!'})
  9. .fields
    访问字段
  10. .as_p().as_ul().as_table()
    贴出源码:
    def as_table(self):
    	"Return this form rendered as HTML <tr>s -- excluding the <table></table>."
    	return self._html_output(
    		normal_row='<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
    		error_row='<tr><td colspan="2">%s</td></tr>',
    		row_ender='</td></tr>',
    		help_text_html='<br><span class="helptext">%s</span>',
    		errors_on_separate_row=False,
    	)
    
    def as_ul(self):
    	"Return this form rendered as HTML <li>s -- excluding the <ul></ul>."
    	return self._html_output(
    		normal_row='<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>',
    		error_row='<li>%s</li>',
    		row_ender='</li>',
    		help_text_html=' <span class="helptext">%s</span>',
    		errors_on_separate_row=False,
    	)
    
    def as_p(self):
    	"Return this form rendered as HTML <p>s."
    	return self._html_output(
    		normal_row='<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>',
    		error_row='%s',
    		row_ender='</p>',
    		help_text_html=' <span class="helptext">%s</span>',
    		errors_on_separate_row=True,
    	)
    
    .as_table()是__str__的返回值,所以是默认方式
    <!-- 正常 -->
    <tr><th><label for="id_name">用户名:</label></th><td><input type="text" name="name" maxlength="32" required id="id_name"></td></tr>
    
    <!-- 异常 -->
    <tr><th><label for="id_name">用户名:</label></th><td><ul class="errorlist"><li>Ensure this value has at most 2 characters (it has 5).</li></ul><input type="text" name="name" value="lczmx" maxlength="2" required id="id_name"></td></t
    

自定义form样式

参见:自定义部件实例

  1. auto_id
    设置字段id

    • Ture自动生成
    • False 不生成
    • 'field_%s' 形如:'field_age'、'field_name'

    但这是参数:f = ContactForm(auto_id=False)

  2. 错误信息类名

    rom django import forms
    
    class ContactForm(forms.Form):
    	error_css_class = 'error'
    	required_css_class = 'required'
    
  3. css

    • 方法1:
    class CommentForm(forms.Form):
    	name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'}))
    	url = forms.URLField()
    	comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'}))
    	
    
    • 方法二:
    class CommentForm(forms.Form):
    	name = forms.CharField()
    	url = forms.URLField()
    	comment = forms.CharField()
    
    	name.widget.attrs.update({'class': 'special'})
    	comment.widget.attrs.update(size='40')
    
    • 对于ModelForm:
    class CommentForm(forms.ModelForm):
    	def __init__(self, *args, **kwargs):
    		super().__init__(*args, **kwargs)
    		self.fields['name'].widget.attrs.update({'class': 'special'})
    		self.fields['comment'].widget.attrs.update(size='40')
    
  4. 更细分
    可以为每个字段(包括label)提供样式,即将form拆开渲染。

    • {% for field in form %} 获得field,一下都是根据field的。
    • {{ field.label }} 字段的label参数
    • {{ field.label_tag }} label标签 包含label_suffix参数
    • {{ field.id_for_label }}该字段的 ID
    • {{ field.value }} 字段的vlaue
    • {{ field.html_name }} 字段的name
    • {{ field.help_text }} 与该字段关联的帮助文本。
    • {{ field.errors }} 输出一个 <ul class="errorlist"> ,其中包含这个字段的所有验证错误信息。你可以使用 {% for error in field.errors %} 循环来自定义错误信息的显示。在这种情况下,循环中的每个对象是包含错误信息的字符串。
    • {{ field.is_hidden }} 如果是隐藏字段,这个属性为 True ,否则为 False
    • {{ field.field }} 访问 Field 的属性, BoundField的API,另外 BoundField

    详见:遍历表单字段

自定义验证方法

要自定义验证方法,那么就要先指定验证的流程,然后我们才知道要操作什么数据,返回什么数据。

  1. .is_valid方法
    return self.is_bound and not self.errors,即是否绑定数据,以及是否有errors
  2. .errors方法
    is_valid调用的是errors方法,源码:
    def errors(self):
      """Return an ErrorDict for the data provided for the form."""
      if self._errors is None:
        self.full_clean()
    return self._errors
    
    可以看到,其实form就验证一次,没有error的话调用full_clean方法
  3. .full_clean方法
    该方法主要是调用这三个方法验证:
    self._clean_fields()
    self._clean_form()
    self._post_clean()
    
    • self._clean_fields()
      主要工作:
      a. 获得当前的值(空的时候为初始化的值)
      b. 将值转换为python格式
      c. 检验是否符合required参数
      d. 找到对应的验证器进行验证,通过返回对应值, 保存到cleaned_data,否则抛出ValidationError
      f. 执行clean_xx构子函数(我们定义的),通过返回对应值保存到cleaned_data(自动),否则抛出ValidationError
    • self._clean_form()
      调用我们自定义的clean钩子函数
    • self._post_clean()
      调用_post_clean钩子函数
          def _post_clean(self):
         """
         An internal hook for performing additional cleaning after form cleaning
         is complete. Used for model validation in model forms.
         """
         pass
      
      暂时不知道怎么用。

自定义验证器

该部分内容见使用验证器

验证某一字段

  1. 定义clean_xxx方法
  2. self.cleaned_data["xxx"]中取值
  3. 检验
    • 通过,返回值
    • 不通过,抛出django.core.exceptions.ValidationError,code参数默认invalid
from django import forms
from django.core.exceptions import ValidationError


class USER(forms.Form):
    name = forms.CharField(max_length=32, label="用户名")

    def clean_name(self):
        val = self.cleaned_data["name"]
        if not val.startswith("lcz"):
            raise ValidationError("必须以lcz开头", code="invalid")
        # 通过
        return val

验证全部字段

  1. 定义clean方法
  2. 执行父类的clean方法得到cleaned_data
  3. 验证cleaned_data中自己想要验证的字段
  4. 不通过就抛出异常,否则不用做

抛出的异常在: .errors["__all__"],可通过.non_field_errors()获取,因为:源码:return self.errors.get("__all__", self.error_class(error_class='nonfield'))

from django import forms
from django.core.exceptions import ValidationError


class USER(forms.Form):
    name = forms.CharField(max_length=32, label="用户名")

    def clean(self):
        cleaned_data = super().clean()
        name = cleaned_data.get("name")

        if name:
            # Only do something if both fields are valid so far.
            if "mx" not in name:
                raise ValidationError("没有mx")

ModelForm

重点,由于前后端分离,直接生成HTML的用途并不是很大,但是通过model验证POST的数据还是很有用途的。

流程:

  1. 定义model
  2. 定义ModelForm类(继承django.forms.ModelForm
  3. class Meta指定哪个类,已经哪些字段
  4. 在views中使用ModelForm,然后调用save方法(save方法隐式验证)

Form字段和Model字段

常用:

模型字段 表单字段
AutoField 不呈现在表单中
BigAutoField 不呈现在表单中
IntegerField IntegerField
BigIntegerField IntegerField 将 min_value 设置为-9223372036854775808,将 max_value 设置为9223372036854775807。
FloatField FloatField
CharField CharField 将 max_length 设置为模型字段的 max_length ,如果模型中设置了 null=True ,会将 empty_value 设置为 None 。
TextField CharField 设置中 widget=forms.Textarea
BinaryField CharField ,如果在模型字段上的 editable 被设置为 True ,则不在表单中显示。
BooleanField BooleanField, 或 NullBooleanField (如果 null=True )。
DateTimeField DateTimeField
EmailField EmailField
DateField DateField
TimeField TimeField
FileField FileField
FilePathField FilePathField
ImageField ImageField
URLField URLField
UUIDField UUIDField
ForeignKey ModelChoiceField (见下文)
ManyToManyField ModelMultipleChoiceField (见下文)

全部对应关系见:字段类型

  • ForeignKey
    是一个ChoiceField,值是选项是一个模型的 QuerySet
  • ManyToManyField
    是一个MultipleChoiceField,其选项为一个模型 QuerySet
    如: authors = models.ManyToManyField(Author)在ModelForm中就相当于:authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())

这两个如何使用,见下文

简单使用

定义model和modelform:

from django.db import models
from django.forms import ModelForm, ChoiceField

TITLE_CHOICES = (
    ('v1', 'Mr.'),
    ('v2', 'Mrs.'),
    ('v3', 'Ms.'),
)


class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3, choices=TITLE_CHOICES)
    birth_date = models.DateField(blank=True, null=True)

    def __str__(self):
        return self.name


class Book(models.Model):
    name = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)


class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ['name', 'title', 'birth_date']


class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = ['name', 'authors']

在view 中使用:

# web/views.py
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from django.core.serializers.json import DjangoJSONEncoder
from django.core.exceptions import ValidationError
from web.models import BookForm


# Create your views here.

class ValidationEncoder(DjangoJSONEncoder):
    def default(self, val):
        if isinstance(val, ValidationError):
            return str(val)
        super().default(val)


def register(request):
    if request.method == "GET":
        form = BookForm()
        return render(request, "web/register.html", {"form": form})

    if request.method == "POST":
        form = BookForm(request.POST)
        try:
            form.save()
        except ValueError:
            return JsonResponse(form.errors.as_data(), encoder=ValidationEncoder)
    return HttpResponse("ok!")
 

meta

meta类可以为ModelForm提供约束和修饰

  1. fields:要用ModelForm验证的字段,当值为"__all__"时为全部字段
  2. exclude:除了哪些字段不验证
  3. model:指定是哪个Model 的ModelForm
  4. widgets:字典,{'name': Textarea(attrs={'cols': 80, 'rows': 20}), },自定义widgets。
  5. error_messages:错误信息提示
    error_messages = {
    		'name': {
    			'max_length': "This writer's name is too long.",
    		},
    	}
    
  6. help_texts:帮助信息
     help_texts = {
    			'name': 'Some useful help text.',
    		}
    
  7. labels:input标签的label内容
     labels = {
    			'name': 'Writer',
    		}
    
  8. field_classes设置表单字段类型:field_classes = {'slug': MySlugFormField,}

验证

验证ModelForm主要分两步:

  1. 表单本身的验证:见这里或上文
  2. 验证模型实例:见这里

模型的验证(Model.full_clean())紧跟在表单的clean()方法调用之后。(表单的验证流程在上文)
我们可以和form一样自定义clean。
关于错误信息:

  1. 在 表单字段 级别或者 表单 Meta 级别定义的错误信息优先级总是高于在 模型字段 级别定义的。
  2. 在 模型字段 上定义的错误信息只有在 模型验证步骤引发ValidationError时才会使用,并且没有在表单级定义相应的错误信息
    模型字段有内置的default_error_messages类属性
  3. ModelForm自定义错误信息
    from django.core.exceptions import NON_FIELD_ERRORS
    from django.forms import ModelForm
    
    class ArticleForm(ModelForm):
    	class Meta:
    		error_messages = {
    			NON_FIELD_ERRORS: {
    				'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
    			}
    		}
    

操作数据

增加和修改数据都需要调用.save()方法,调用 save() 将通过检查form.errors来实现验证。如果表单验证不过,则会抛出ValueError

  1. 增加

    • 普通字段
      form = BookForm(request.POST)
      try:
      	form.save()
       except ValueError:
      	 return HttpResponse("error")
      
    • 多对多字段
      可以直接保存,但是也可以让其先验证,然后增加一些其它字段,再保存。
      调用 save() 的时候使用 commit=False ,那么它会返回一个尚未保存到数据库的对象
      f = AuthorForm(request.POST)
      # 调用 save() 的时候使用 commit=False ,那么它会返回一个尚未保存到数据库的对象
      new_author = f.save(commit=False)
      # 增加一些其它字段
      new_author.some_field = 'some_value'
      # 保存多对多的表单数据
      new_author.save()
      f.save_m2m()
      
  2. 修改
    通过指定instance参数实现

    a = Article.objects.get(pk=1)
    f = ArticleForm(request.POST, instance=a)
    f.save()
    

ModelForm的内容很多,本文写了部分内容,更多可以查看官网:从模型创建表单

本文来自博客园,作者:忞翛,转载请注明原文链接:https://www.cnblogs.com/lczmx/p/15204015.html

原文地址:https://www.cnblogs.com/lczmx/p/15204015.html