11 | 表单基础


        100%的Django项目应该使用表单(Forms)。

        95%的Django项目应该使用模型表单(ModelForms)。

        实际中的91%的Django项目使用模型表单。

        80%的模型表单需要普通的逻辑。

        20%的模型表单需要复杂的逻辑。

        —来自pydanny的统计TM

Django的表单十分强大,并且作为保持你数据清洁的一部分,Django在任何时候都知道如何使用表单来处理来自你应用程序之外的数据。

有些边缘情况会让你有一点苦恼。如果你明白表单的结构是怎么组成的并且如何调用它们,大多数的边缘情况可以轻易的解决。

最重要的事情是明白Django的表单的作用,它是被用来验证所有的传入数据。


11.1 通过Django的表单验证所有的输入数据

Django的表单是一个奇妙的框架被用来设计验证Python的字典类型。虽然大多数时间我们使用它们去验证传入的包含POST的HTTP请求,但是并没有限制仅仅以这种方式去使用它们。

举个例子,比方说我们有一个Django的程序,它通过从另一个项目中获取CSV文件来更新自己的模型(model),要处理这类事件,如下的代码并不罕见(尽管不是一个简单的例子):

  1. BAD EXMAPLE 11.1
  2. import csv
  3. import StringIO
  4. from .models import Purchase
  5. def add_csv_purchases(rows):
  6. rows = StringIO.StringIO(rows)
  7. records_added = 0
  8. # Generate a dict per row, with the first CSV row being the keys
  9. # 每行生成一个字典, 第一个CSV行作为键
  10. for row in csv.DictReader(rows, delimiter=",")
  11. # DON'T DO THIS: Tossing unvalidated data into your model.
  12. # 不要这么做:你的模型中放入了未验证的数据。
  13. Purchase.objects.create(**row)
  14. records_added += 1
  15. return records_added


事实上,你没有看到的是,我们并没有检查如果销售者(sellers)作为一个字符串存储在购买模型中(Purchase model)的这种情况,会成为有效的销售者。我们能够添加验证码到我们的add_csv_purchases()函数,但是我们很清楚,随着时间的推移保持复杂的验证码像需求和改变数据一样易于理解是困难的。

一个更好的去通过Django的表单去验证传入数据的方法,如下:

  1. EXMAPLE 11.1
  2. import csv
  3. import StringIO
  4. from django import forms
  5. from .models import Purchase, Seller
  6. class PurchaseForm(forms.ModelForm):
  7. class Meta:
  8. model = Purchase
  9. def clean_sellers(self):
  10. seller = self.cleaned_data["seller"]
  11. try:
  12. Seller.objects.get(name=seller)
  13. except Seller.DoesNotExist:
  14. msg = "{0} does not exist in purchase #{1}.".format(
  15. seller,
  16. self.cleaned_data["purchase_number"]
  17. )
  18. raise froms.ValidationError(msg)
  19. return seller
  20. def add_csv_purchases(rows):
  21. rows = StringIO.StringIO(rows)
  22. records_added = 0
  23. errors = []
  24. # Generate a dict per row, with the first CSV row being the keys
  25. # 每行生成一个字典, 第一个CSV行作为键
  26. for row in csv.DictReader(rows, delimiter=",")
  27. # Bind the row data to the PurchaseForm.
  28. # 在购买表单中绑定行数据
  29. form = PurchaseForm(row)
  30. # Check to see if the row data is valid
  31. # 检查行数据是否有效
  32. if form.is_valid():
  33. # Row data is valid so save the record
  34. # 行数据有效,所以存储到记录中
  35. form.save()
  36. records_added += 1
  37. else:
  38. errors.append(form.errors)
  39. return records_added, errors

这个例子相当出色的地方是,我们没有因传入数据而调试自己的验证系统,我们使用了Django内置的良好的数据测试框架。

提示:关于 代码 参数

Arnaud Limbourg指出,Django官方文档推荐传递一个代码参数到ValidationError,如下:
EXAMPLE 11.2 ValidationError(_('Invalid value'), code='invalid')
在我们的例子中我们并没有包含这个参数,但是如果你想要使用的话你可以加到你的代码里。

Django核心开发Marc Tamlyn说,“从个人角度,我感觉Django的文档有一点小强硬,这表现在建议他们每一处都要以最优方法来使用代码,虽然它应该在第三方应用中被鼓励。然而当你希望检查错误的性质时这无疑是对任何情况的最优方法——这比检查验证错误信息可好多了,因为错误受到了拷贝变化。”

引用自:

  1. >>[www.2scoops.co/1.8-raising-validationerror/]($11-www.2scoops.co-1.8-raising-validationerror)