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 肥仔水!