Forms的基本范例

因为 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 >
Buy me a 肥仔水!