基于How To Tango With Django 1.9的重新实践(7)——Forms

到目前为止我们仅仅是通过视图和模板来表现数据.在本章,我们将会学习如何通过web表单来获取数据.Django包含一些表单处理功能,它使在web上收集用户信息变得简单.通过Django’s documentation on forms我们知道表单处理功能包含以下:

  1. 显示一个HTML表单自动生成的窗体部件(比如一个文本字段或者日期选择器).
  2. 用一系列规则检查提交数据.
  3. 验证错误的情况下将会重新显示表单.
  4. 把提交的表单数据转化成相关的Python数据类型.

使用Django表单功能最大的好处就是它可以节省你的大量时间和HTML方面的麻烦.这部分我们将会注重如何通过表单让用户增加目录和页面.

8.1 基本流程

基本步骤包括创建表单和允许用户通过表单输入数据.

  1. 在Django应用目录创建forms.py目录来存储和表单相关的类.
  2. 为每个使用表单的模块创建ModelForm类.
  3. 定制你的表单.
  4. 创建或修改表单的视图 - 包括展示表单,存储表单数据,当用户输入错误数据(或者根本没有输入)时显示错误标志.
  5. 创建或修改你表单的模板.
  6. 为新视图增加urlpattern映射(如果你创建了一个新的).

这个流程将会比先前的都复杂些,我们创建的视图也会非常复杂.但是孰能生巧,如果多练几次就非常好掌握了.

8.2 页面和目录表单

首先,我们需要在rango应用目录里黄建叫做forms.py文件.尽管这步我们并不需要,我们可以把表单放在models.py里,但是这将会使我们的代码简单易懂.

8.2.1 创建ModelForm类

在rango的forms.py模块里我们将会创建一些继承自ModelForm的类.实际上,ModelForm是一个帮助函数,它允许你在一个已经存在的模型里创建Django表单.因为我们定义了两个模型(Category和Page),我们将会分别为它们创建ModelForms.

在rango/forms.py添加下面代码

from django import forms
from rango.models import Page, Category

class CategoryForm(forms.ModelForm):
    name = forms.CharField(max_length=128, help_text="Please enter the category name.")
    views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
    likes = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
    slug = forms.CharField(widget=forms.HiddenInput(), required=False)

    # An inline class to provide additional information on the form.
    class Meta:
        # Provide an association between the ModelForm and a model
        model = Category
        fields = ('name',)


class PageForm(forms.ModelForm):
    title = forms.CharField(max_length=128, help_text="Please enter the title of the page.")
    url = forms.URLField(max_length=200, help_text="Please enter the URL of the page.")
    views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)

    class Meta:
        # Provide an association between the ModelForm and a model
        model = Page

        # What fields do we want to include in our form?
        # This way we don't need every field in the model present.
        # Some fields may allow NULL values, so we may not want to include them...
        # Here, we are hiding the foreign key.
        # we can either exclude the category field from the form,
        exclude = ('category',)
        #or specify the fields to include (i.e. not include the category field)
        #fields = ('title', 'url', 'views')

TODO(leifos):值得注意的是Django1.7+需要通过fields指定包含的字段,或者通过exclude指定排除的字段.

Django为我们提供了许多定制表单的方法.在上面的例子中,我们指定了我们想要展示字段的窗口部件.例如在我们的PageForm类中,我们为title字段定义forms.CharField,为url字段定义forms.URLField.两个字段都为用户提供文本输入.注意字段里包含max_length参数 - 它定义字段最大长度.可以返回第6章或者rango的models.py文件查看.

可以看到在每个表单都包含浏览和喜欢的IntegerField字段.我们可以在参数里设置widget=forms.Hiddeninput()来隐藏窗口组件,设置initial=0来设置默认值为0.这是不用用户去自己设置字段为0的一种方法.然而可以在PageForm看到,尽管我们隐藏了字段,但是我们还是得在表单里包含字段.如果fields排除了views,那么表单将不包含字段(尽管已经定义了)而且将不会返回给模型0值.由于模型建立的不同有可能会引起一个错误.如果在模型里我们把这些字段定义为default=0那么我们可以自动的返回默认值 - 从而避免not null错误.在这种情况下就不需要隐藏字段了.在表单里我们也包含了slug字段并设置为widget=forms.HiddenInput()值,这里我们并没给他设置初始值或者默认值,而是设置为不需要(required=False).这是因为我们的模型将会负责填充字段.实际上,当你定义模型和表单时一定要注意表单一定要包含和传递所有的数据.

除了CharField和IntegerField组件还有许多.例如,Django提供了EmailField(e-mail地址入口),ChoiceField(输入按钮)和DateField(日期/时间入口).有许多其他不同种类字段可以使用,他们可以为你检查执行错误(例如是否提供了一个有效的整数?).强烈建议你看一下official Django documentation on widgets来定制自己的组件.

或许继承ModelForm最大的作用就是需要定义我们要给哪个模型提供表单.我们通过Meta类来实现.在Meta类设置model属性为我们需要使用的模型.例如CategoryForm类引用Category模型.这对Django创建我们想要的模型表单至关重要.它还可以帮助我们在存储和展示表单数据时获取错误.

我们也可以用Meat类来定义我们希望包括的表单字段.用fields元组来定义所需包含的字段.

7.2.2 创建和增加目录视图

创建CategoryForm类以后,我们需要创建一个新的视图来展示表单并传递数据.在rango/views.py中增加如下代码.

from rango.forms import CategoryForm

def add_category(request):
    # A HTTP POST?
    if request.method == 'POST':
        form = CategoryForm(request.POST)

        # Have we been provided with a valid form?
        if form.is_valid():
            # Save the new category to the database.
            form.save(commit=True)

            # Now call the index() view.
            # The user will be shown the homepage.
            return index(request)
        else:
            # The supplied form contained errors - just print them to the terminal.
            print form.errors
    else:
        # If the request was not a POST, display the form to enter details.
        form = CategoryForm()

    # Bad form (or form details), no form supplied...
    # Render the form with error messages (if any).
    return render(request, 'rango/add_category.html', {'form': form})

新的add_category()视图增加几个表单的关键功能.首先,检查HTTP请求方法是GET还是POST.我们根据不同的方法来进行处理 - 例如展示一个表单(如果是GET)或者处理表单数据(如果是POST) -所有表单都是相同URL.add_category()视图处理以下三种不同情况:

  • 为添加目录提供一个新的空白表单;
  • 保存用户提交的数据给模型,并转向到Rango主页;
  • 如果发生错误,在表单里展示错误信息.

Django表单处理数据是用过用户浏览器的HTTPPOST请求实现.它不仅可以存储表单数据,而且还能对每个表单字段自动生成错误信息.这就意味着Django将不会存储表单的错误信息以保护数据库的数据完整性.例如如果目录名为空的话将会返回不能为空的错误.

注意到render()中将会调用add_category.html模板,这个模板包含表单和页面.

7.2.3 创建增加目录模板

让我们创建templates/rango/add_category.html文件.

<!DOCTYPE html>
<html>
    <head>
        <title>Rango</title>
    </head>

    <body>
        <h1>Add a Category</h1>

        <form id="category_form" method="post" action="/rango/add_category/">

            {% csrf_token %}
            {% for hidden in form.hidden_fields %}
                {{ hidden }}
            {% endfor %}

            {% for field in form.visible_fields %}
                {{ field.errors }}
                {{ field.help_text }}
                {{ field }}
            {% endfor %}

            <input type="submit" name="submit" value="Create Category" />
        </form>
    </body>

</html>

好吧,这些代码是干什么的?可以看到在<body>标签中我们设置了一个<form>元素.再来看看<form>中的元素,所有表单中的数据将会用POST请求发送给/rango/add_category/(method属性大小写不敏感,所以可以写成POST或是post - 两者功能一样).在表单里我们有两个循环 - 一个控制表单隐藏字段,另一个则是可见字段 - 可见字段的设置是在ModelForm的Meta类中设置fields属性来实现的.这些循环帮助我们生成HTML标记.对于表单的可见字段,我们也设置一个特殊区域来展示错误信息,同时设置帮助文本告诉用户那需要输入什么.

可能你也注意到了代码{% csrf_token %},这是跨站请求伪造令牌,有助于保护我们提交表单的HTTPPOST方法的安全.Django框架要求使用CSRFtoken.如果忘记在你的表单里包含CSRF令牌,有可能会在提交表单时遇到错误.查看 official Django documentation on CSRF tokens 以获取更多信息.

7.2.4 映射增加目录视图

现在我们需要映射add_category()视图.在模板里我们使用/rango/add_category/URL来提交.所以我们需要修改rango/urls.py的urlpattterns

urlpatterns = patterns('',
    url(r'^$', views.index, name='index'),
    url(r'^about/$', views.about, name='about'),
    url(r'^add_category/$', views.add_category, name='add_category'), # NEW MAPPING!
    url(r'^category/(?P<category_name_slug>[\w\-]+)/$', views.category, name='category'),)

7.2.5 修改主页内容
作为最后一步,让我们在首页里加入链接.修改rango/index.html文件,在</body>前添加如下代码.

<a href="/rango/add_category/">Add a New Category</a><br />

7.2.6 Demo

现在试一试!启动Django服务,进入http://127.0.0.1:8000/rango/.用新加的链接跳转到增加目录页面,然后增加页面.下图是增加目录和首页截图.

Exercises

Now that you’ve worked through the chapter, consider the following questions, and how you could solve them.

  • What would happen if you don’t enter in a category name on the add
    category form?
    这里写图片描述

  • What happens when you try to add a category that already exists?

这里写图片描述

  • What happens when you visit a category that does not exist? A hint for a potential solution to solving this problem can be found below.

这里写图片描述

  • In the section above where we implemented our ModelForm classes, we repeated the max_length values for fields that we had previously defined in the models chapter.This is bad practice as we are repeating ourselves! How can you refactor your code so that you are not repeating the max_lengthvalues?
  • If you have not done so already undertake part four of the official Django Tutorial to reinforce what you have learnt here.
  • Now let users add pages to each category, see below for some example code and hints.

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值