Two Scoops of Django 1.8 学习笔记六

11. Form Fundamentals 表单基本原理

表单中最重要的事情是校验传入的数据

11.1 用表单校验传入数据

import csv
import StringIO

from django import forms

from .models import Purchase, Seller

class PurchaseForm(forms.ModelForm):

    class Meta:
        model = Purchase

    def clean_seller(self):
        seller = self.cleaned_data["seller"]
        try:
            Seller.objects.get(name=seller)
        except Seller.DoesNotExist:
            msg = "{0} does not exist in purchase #{1}.".format(
                seller,
                self.cleaned_data["purchase_number"]
            )
            raise forms.ValidationError(msg)
        return seller

def add_csv_purchases(rows):

    rows = StringIO.StringIO(rows)

    records_added = 0
    errors = []
    # Generate a dict per row, with the first CSV row being the keys.
    for row in csv.DictReader(rows, delimiter=","):

        # Bind the row data to the PurchaseForm.
        form = PurchaseForm(row)
        # Check to see if the row data is valid.
        if form.is_valid():
            # Row data is valid so save the record.
            form.save()
            records_added += 1
        else:
            errors.append(form.errors)

    return records_added, errors

11.2 在HTML的表单中使用POST方法

<form action="{% raw %}{% url "flavor_add" %}{% endraw %}" method="POST">

11.3 针对需要修改数据的表单使用CSRF保护

{% raw %}{% csrf_token %}{% endraw %}

通过AJAX提交数据

用AJAX的时候也要用到CSRF,你需要设置叫做X-CSRFToken的HTTP header

第一步,获得csrftoken的cookie

// using jQuery
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');

还可以通过 https://github.com/js-cookie/js-cookie/ 来代替上面的方法

var csrftoken = Cookies.get('csrftoken');

最后在AJAX请求上设置HTTP头

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

11.4 了解如何添加Django表单实例属性

from django import forms

from .models import Taster

class TasterForm(forms.ModelForm):

    class Meta:
        model = Taster

    def __init__(self, *args, **kwargs):
        # set the user as an attribute of the form
        self.user = kwargs.pop('user')
        super(TasterForm, self).__init__(*args, **kwargs)

在设置super()之前,我们把kwargs中的user拿了出来,赋予了self.user,看看view:

from django.views.generic import UpdateView

from braces.views import LoginRequiredMixin

from .forms import TasterForm
from .models import Taster

class TasterUpdateView(LoginRequiredMixin, UpdateView):
    model = Taster
    form_class = TasterForm
    success_url = "/someplace/"

    def get_form_kwargs(self):
        """This method is what injects forms with their keyword arguments."""
        # grab the current set of form #kwargs
        kwargs = super(TasterUpdateView, self).get_form_kwargs()
        # Update the kwargs with the user_id
        kwargs['user'] = self.request.user
        return kwargs

11.5 知道表单校验是如何工作的

调用form.is_valid()的时候,背后发生了这些事情:

  1. 如果表单绑定了数据,form.is_valid()调用form.full_clean()方法
  2. form.full_clean()通过表单字段进行迭代,并且每个字段都验证自身

    a. 数据写入到字段的时候被强制进入到了Python中,通过to_python()方法或者抛出ValidationError异常

    b. 数据的验证依赖具体的字段规则,包括自定义的校验器。失败抛出ValidationError异常

    c. 如果有任何自定义的clean_()方法存在于表单中,在这个时候他们都会被调用

  3. form.full_clean()执行form.clean()方法

  4. 如果他是个ModelForm的实例,form._post_clean()做下面的事情:

    a. 给模型实例设置ModelForm数据,而不管form.is_valid()是否是True或者False

    b. 调用模型的clean()方法,作为参考,通过ORM保存模型实例不调用模型的clean()方法

11.5.1 ModelForm 数据先被存到表单中,再存到模型实例中

在一个ModelForm中,表单数据的存储分为两步:

  1. 首先,表单数据存在表单实例中
  2. 然后,表单数据存在模型实例中

只有使用form.save()方法,数据才会被存到模型实例中。我们可以利用这种分离作为一个有用的功能。

举个例子,也许你需要捕捉表单提交失败的细节,储存满足用户的表单数据以及预期的模型实例变化。
一个简洁的方式如下,首先我们创建一个表单失败历史的模型:

# core/models.py
from django.db import models

class ModelFormFailureHistory(models.Model):
    form_data = models.TextField()
    model_data = models.TextField()

第二步,我们添加个FlavorActionMixin的类

# flavors/views.py
import json

from django.contrib import messages
from django.core import serializers

from core.models import ModelFormFailureHistory

class FlavorActionMixin(object):

    @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)

    def form_invalid(self, form):
        """Save invalid form and model data for later reference."""
        form_data = json.dumps(form.cleaned_data)
        model_data = serializers.serialize("json",
                    [form.instance])[1:-1]
        ModelFormFailureHistory.objects.create(
            form_data=form_data,
            model_data=model_data
        )
        return super(FlavorActionMixin,
                    self).form_invalid(form)

如果你还记得,验证不良数据的表单失败后会调用form_invalid()。
在这个例子中,干净的表单数据和最后的数据会作为一个ModelFormFailureHistory记录储存在数据库中

11.6 Form.add_error()

from django import forms

class IceCreamReviewForm(forms.Form):
    # Rest of tester form goes here
    ...

    def clean(self):
        cleaned_data = super(TasterForm, self).clean()
        flavor = cleaned_data.get("flavor")
        age = cleaned_data.get("age")

        if flavor == 'coffee' and age < 3:
            # Record errors that will be displayed later.
            msg = u"Coffee Ice Cream is not for Babies."
            self.add_error('flavor', msg)
            self.add_error('age', msg)

        # Always return the full collection of cleaned data.
        return cleaned_data

其他有用的表单方法