因为 Django forms其强大的灵活性, 扩展性, 在Django的 CBV
中广泛的使用
模式1 Simple ModelForm
With Default Validators
在 GCBV
中, View 会根据 Model 自动生成 ModelForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView, UpdateView
from .models import Flavor
class FlavorCreateView(LoginRequiredMixin, CreateView):
model = Flavor
fields = ['title', 'slug', 'scoops_remaining']
class FlavorUpdateView(LoginRequiredMixin, UpdateView):
model = Flavor
fields = ['title', 'slug', 'scoops_remaining']
➤ FlavorCreateView and FlavorUpdateView are assigned Flavor as their model.
# 指定model -> Flavor
➤ Both views auto-generate a ModelForm based on the Flavor model.
# 视图 根据 Flavor 自动生成 ModelForm
➤ Those ModelForms rely on the default field validation rules of the Flavor model
使用默认的 字段验证
模式2 Custom Form Field Validators
in ModelForms
定义一个验证方法 validator
# core/validators.py
from django.core.exceptions import ValidationError
def validate_tasty(value):
"""Raise a ValidationError if the value doesn't start with the
word 'Tasty'.
"""
if not value.startswith('Tasty'):
msg = 'Must start with Tasty'
raise ValidationError(msg)
把验证方法加入到一个抽象model类中
# core/models.py
from django.db import models
from .validators import validate_tasty
class TastyTitleAbstractModel(models.Model):
title = models.CharField(max_length=255, validators=[validate_tasty])
class Meta:
abstract = True
在常规的model中, 继承定义了validator
的抽象类
,并使用
# flavors/models.py
from django.db import models
from django.urls import reverse
from core.models import TastyTitleAbstractModel
class Flavor(TastyTitleAbstractModel):
slug = models.SlugField()
scoops_remaining = models.IntegerField(default=0)
def get_absolute_url(self):
return reverse('flavors:detail', kwargs={'slug': self.slug})
这样在 model 保存的时候会验证 title
字段
如何在forms中使用验证方法, 如何增加验证到其他字段
# flavors/forms.py
from django import forms
from .models import Flavor
from core.validators import validate_tasty
class FlavorForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(FlavorForm, self).__init__(*args, **kwargs)
self.fields['title'].validators.append(validate_tasty)
self.fields['slug'].validators.append(validate_tasty)
# 给
class Meta:
model = Flavor
fields = "__all__" # 需要加上
# Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is prohibited; form FlavorForm needs updating
默认情况下 GCBV
中 基于model的视图会根据 model
属性自动生成 modelform
, 可以通过form_class
字段来指定
The default behavior of Django model-based edit views is to auto-generate the ModelForm based on the view’s model attribute
# flavors/views.py
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView, DetailView, UpdateView
from .models import Flavor
from .forms import FlavorForm
class FlavorActionMixin:
model = Flavor # 指定model
# fields = ['title', 'slug', 'scoops_remaining']
# django.core.exceptions.ImproperlyConfigured: Specifying both 'fields' and 'form_class' is not permitted.
@property
def success_msg(self):
return NotImplemented
def form_valid(self, form):
messages.info(self.request, self.success_msg)
return super(FlavorActionMixin, self).form_valid(form)
class FlavorCreateView(LoginRequiredMixin, FlavorActionMixin,CreateView):
success_msg = 'created'
# Explicitly attach the FlavorForm class
form_class = FlavorForm # 指定form, 不知定默认使用 model属性的Model, 生成ModelForm
class FlavorUpdateView(LoginRequiredMixin, FlavorActionMixin,UpdateView):
success_msg = 'updated'
# Explicitly attach the FlavorForm class
form_class = FlavorForm
class FlavorDetailView(DetailView):
model = Flavor
模式3 重写validation的 clean 过程
clean_field
form_valid 验证通过之后, 在 self.cleaned_data['field']
获取数据,定义clean_field
方法 并进行进一步的验证
# flavors/forms.py
from django import forms
from flavors.models import Flavor
class IceCreamOrderForm(forms.Form):
"""Normally done with forms.ModelForm. But we use forms.Form here
to demonstrate that these sorts of techniques work on every
type of form.
"""
slug = forms.ChoiceField(label='Flavor')
toppings = forms.CharField()
def __init__(self, *args, **kwargs):
super(IceCreamOrderForm, self).__init__(*args, **kwargs)
# We dynamically set the choices here rather than
# in the flavor field definition. Setting them in
# the field definition means status updates won't
# be reflected in the form without server restarts.
self.fields['slug'].choices = [
(x.slug, x.title) for x in Flavor.objects.all()
]
# NOTE: We could filter by whether or not a flavor
# has any scoops, but this is an example of
# how to use clean_slug, not filter().
def clean_slug(self):
slug = self.cleaned_data['slug']
if Flavor.objects.get(slug=slug).scoops_remaining <= 0:
msg = 'Sorry, we are out of that flavor.'
raise forms.ValidationError(msg)
return slug
clean
进行多个字段的比较,验证
def clean(self):
cleaned_data = super(IceCreamOrderForm, self).clean()
slug = cleaned_data.get('slug', '')
toppings = cleaned_data.get('toppings', '')
# Silly "too much chocolate" validation example
in_slug = 'chocolate' in slug.lower()
in_toppings = 'chocolate' in toppings.lower()
if in_slug and in_toppings:
msg = 'Your order has too much chocolate.'
raise forms.ValidationError(msg)
return cleaned_data
4 Hacking Form Fields (2 CBVs, 2 Forms, 1 Model)
共用一个 Model
# stores/models.py
from django.db import models
from django.urls import reverse
class IceCreamStore(models.Model):
title = models.CharField(max_length=100)
block_address = models.TextField()
phone = models.CharField(max_length=20, blank=True)
description = models.TextField(blank=True)
def get_absolute_url(self):
return reverse('store_detail', kwargs={'pk': self.pk})
在 Forms中不要重复 字段的定义
from django import forms
from .models import IceCreamStore
# bad example
class IceCreamStoreUpdateForm(forms.ModelForm):
# Don't do this! Duplication of the model field!
phone = forms.CharField(required=True)
# Don't do this! Duplication of the model field!
description = forms.TextField(required=True)
class Meta:
model = IceCreamStore
# good example
# Call phone and description from the self.fields dict-like object
class IceCreamStoreUpdateForm(forms.ModelForm):
class Meta:
model = IceCreamStore
def __init__(self, *args, **kwargs):
# Call the original __init__ method before assigning
# field overloads
super(IceCreamStoreUpdateForm, self).__init__(*args, **kwargs)
self.fields['phone'].required = True
self.fields['description'].required = True
使用继承简化代码
# stores/forms.py
from django import forms
from .models import IceCreamStore
class IceCreamStoreCreateForm(forms.ModelForm):
class Meta:
model = IceCreamStore
fields = ['title', 'block_address', ]
class IceCreamStoreUpdateForm(IceCreamStoreCreateForm):
def __init__(self, *args, **kwargs):
super(IceCreamStoreUpdateForm, self).__init__(*args, **kwargs)
self.fields['phone'].required = True
self.fields['description'].required = True
class Meta(IceCreamStoreCreateForm.Meta):
# show all the fields!
fields = ['title', 'block_address', 'phone', 'description', ]
在 Views 中使用
# stores/views
from django.views.generic import CreateView, UpdateView
from .forms import IceCreamStoreCreateForm, IceCreamStoreUpdateForm
from .models import IceCreamStore
class IceCreamCreateView(CreateView):
model = IceCreamStore
form_class = IceCreamStoreCreateForm
class IceCreamUpdateView(UpdateView):
model = IceCreamStore
form_class = IceCreamStoreUpdateForm
在View的展示中加入搜索 query
条件
两个model可以通过同一个filter 条件, 获取列表数据
# core/views.py
class TitleSearchMixin:
def get_queryset(self):
# Fetch the queryset from the parent's get_queryset
queryset = super(TitleSearchMixin, self).get_queryset()
# Get the q GET parameter
q = self.request.GET.get('q')
if q:
# return a filtered queryset
return queryset.filter(title__icontains=q)
# No q is specified so we return queryset
return queryset
在不同的app的视图中使用, 加入搜索Mixins
# add to flavors/views.py
from django.views.generic import ListView
from .models import Flavor
from core.views import TitleSearchMixin
class FlavorListView(TitleSearchMixin, ListView):
model = Flavor
# add to stores/views.py
from django.views.generic import ListView
from .models import Store
from core.views import TitleSearchMixin
class IceCreamStoreListView(TitleSearchMixin, ListView):
model = Store
这样在对应的html中,渲染
{ # form to go into stores/store_list.html template # }
< form action="" method="GET" >
< input type="text" name="q" / >
< button type="submit">search< /button >
< /form >
{ # form to go into flavors/flavor_list.html template # }
< form action="" method="GET" >
< input type="text" name="q" / >
< button type="submit" >search< /button >
< /form >