跟我一起Django - 06 模板和表单处理

http://www.cnblogs.com/ganiks/

6. 模板和表单处理
1 模板
1.1 理解Contexts
1.2 模板语言语法
1.2.1 模板过滤器
1.2.2 标签Tag
1.2.3 Blocks和Extends
跳出当前模板和其他模板交互
1.2.4 include
2 表单
2.1 定义表单
2.1.1 基于模型的表单
2.1.2 保存ModelForm
2.1.3 区别于Model
2.1.4 继承表单
2.2 填写表单
2.3 验证和清理
2.4 显示表单
2.4.1 显示全部表单
2.4.2 逐个显示表单
2.4.3 Widget
2.4.4 重写一个变量的默认widget

1 模板

Django的模板系统绝不仅仅是用于生成HTML,还有 log文件、Email正文、CSV文件等任何文本格式文件。

1.1 理解Contexts

Django 把传递给一个渲染模板的信息称之为 context, 实质上就是包含键值对的类似字典的 Context对象

准备Contexts有两种方式:

一种是上一篇中用到的,将Contexts作为参数传递

render_to_response("person/detail.html", {"person":person})

直接将数据作为render_to_response的参数传递;
或者在使用通用视图时把extra_context参数加上

   return object_list(request,
        queryset = Person.objects.filter(last__istartswith=last_name)
    }

第二种方式是通过 context处理器, 这个东东类似中间件,可以定义各种函数,在模板渲染之前来把键值对附加到所有context上去。
这也是认证框架这样的特性,为什么能保证特定的数据在全站范围里都能访问到的原因。

def breadcrumb_processor(request):
    return {
        'breadcrumb': request.path.split('/')
    }

通常处理器都放在根目录的 context_processors.py文件中或是app的目录中;
另外还需要在 settings.py中激活才可以使用, 通常是在一个叫做TEMPLATE_CONTEXT_PROCESSORS 的元组中, 类似中间件的激活。

1.2 模板语言语法

跟其他一些非XML的 模板语言如Smarty相比, Django的模板语言没有要保持 XHTML兼容性的意思,
而是用特殊的字符来把(模板变量以及逻辑命令)和静态内容(HTML)分开来。

跟其他模板语言一样,Django模板系统也有 单独命令 和 模块级命令 , 分别是{{ variable }}, {% command %}

下面的例子渲染context内容:{"title_text":"My WebPage", "object_list": ["One", "Two", "Three"]}

<html>
    <head>
        <title>{{ title_text }}</title>
    </head>
    <body>
        <ul>
        {% for item in object_list %}
            <li>{{ item }}</li>
        {% endfor %}
        </ul>
    </body>
</html>

陷阱:Django在模板中输出context变量的时候,会隐式调用 unicode方法, 所以对象以及其他非字符串变量会被尽量转换成 Unicode字符串。
如果你要输出没有定义 unicode 方法的对象,在模板里是看不到它们的,因为Python表示对象用的是 < > ,正好在html中啥也不是,故不显示。

>>> print object()
<object object at 0x40448>

1.2.1 模板过滤器

类似 UNIX 管道

  • string|lower
  • person.is_avaliable|yesno:"Yes,No"

1.2.2 标签Tag

if, ifequal, for, endfor, block, include, extends ...

{% ifequal object_list|length 10 %}
{% if object_list|lengthis 10 %}

模块级命令如 if for , 可以修改它们的局部 context, 很实用

如 for 提供了 {{ forloop }}局部变量, 用法有 forloop.first .last .counter .counter0

跳出当前模板和其他模板交互
通过模板继承和模板包含来完成代码的组合和重用
继承通过 blocks extends 实现;包含通过 include 实现

1.2.3 Blocks和Extends

  • {% extends %} 必须在模板的顶部调用,并告知渲染引擎这个模板是从一个更高级的模板继承而来
  • {% blocks %} 是一个模块级标签,一个模板中可以定义多个block, 预备让那些要扩展它的模板去填充的小节

举个例子,三层网站布局

  • /

    • /section1/
    • /section2/

      • /section1/page1/
      • /section1/page2/
#base.html
<html><head><title>{% block title %}My Web Site{% endblock %}</title></head></html>

<div id="content">{% block content %}{% endblock %}</div>

section1.html
{% extends "base.html" %}
{% block title %} Section1 {% endblock %}

page1.html
{% extends "section1.html" %}
{% block content %} This Page 1 {% endblock %}

page2.html
{% extends "section1.html" %}
{% block content %} This Page 2 {% endblock %}

1.2.4 include

很简单,类似PHP的include,实现代码重用

http://www.cnblogs.com/ganiks/p/django-template-and-forms.html

2 表单

Django提供了 forms 库来把框架里的三个主要的组件联系在一起:

  • 模型里定义的数据库字段
  • 模板里显示的HTML表单标签
  • 验证用户输入和显示错误信息的能力

2.1 定义表单

表单处理的核心类Form 其实跟 Model 看上去是基本相同的, 它们都是处理 变量对象集合, 只是代表的意义不同,一个是Web表单,一个是数据表

有了Form类,我们不光可以轻松处理跟Model模型 100%匹配的表单,还可以隐藏或是省略特定变量,或是把多个model里的变量放在一个表单,或是处理和数据库存储毫无关系的表单, 非常灵活。

from django import newforms as forms

class PersonForm(forms.Form):
    first = forms.CharField(max_length=100, required=True)
    last = forms.CharField(max_length=100, required=True)
    middle = forms.CharField(max_length=100)

2.1.1 基于模型的表单

Django允许你使用Form变形 ModelForm 类为任何 Model类或实例取得一个Form子类,
一个ModelForm和普通Form基本一样,但是包含了一个Meta嵌套类(类似Model里的Meta),内含一个必须的属性 model

下面重新定义下上面的 Form

from django import newforms as forms
from mysite.myapp.models import Person

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person

完美体现了Django的DRY don't repeat yourself 的原则

2.1.2 保存ModelForm

基于模型创建的表单跟手动生成的表单有一个很重要的区别是, save方法

from mysite.myapp.forms import PersonForm

form = PersonForm({'first':'John', 'last':'Doe'})
new_person = form.save()

你经常会需要在数据,从表单提交后,存储到数据库之前,修改它们;

而最好还是在后一个时间段修改,因为这时候你修改的是Python值,而不是POST数据。

为了能让在save之前修改, save方法提供了一个可选的commit参数(默认是True), 控制你是否真的更新数据库。

from mysite.myapp.forms import PersonForm

form = PersonForm({'first':'John', 'last':'Doe'})
new_person = form.save(commit=False)
new_person.middle = 'Danger'
new_person.save()

如果使用 commit=False 来延迟保存的ModelForm包含了相关对象, Django会给表单(不是Model对象的结果)添加一个额外的方法 save_m2m, 让你正确安排时间的顺序。

form = PersonForm(input_including_related_objects)

new_person = form.save(commit=False)
new_person.middle = 'Danger'
new_person.save()
form.save_m2m()

如果没有最后的 save_m2m(), related objects会出问题的。

2.1.3 区别于Model

继承模型的表单是非常方便,但是有时候我们需要排除一些变量,并不是需要所有的变量,不需要重新写一个Form子类出来,有好几种方法:

from django import newforms as forms
from mysite.myapp.models import Person

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person
        exclude = ('middle', )

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person
        fields = ('first', 'last')

这样一来,忽略的变量就不会被save方法保存,所以忽略它时要确保DB中的定义是 null=True的,否则会报错。

有时候,还需要重写forms层里的Field子类,验证和显示特定的变量,如下重新定义了first字段的长度

class PersonForm(forms.ModelForm):
     first = forms.CharField(max_length=10)
     class Meta:
         model = Person

关于“关系表单变量” relationship form fields

  • ModelChoiceField -- ForeignKey
  • ModelMultipleChoiceField -- ManyToManyField

还记得吗?当时Model定义外键和多对多关系的时候,使用了 limit_choices_to参数, 其实等效的方法可以在表单层变量里自定义一个 queryset 参数, 用来接收一个 特定的 QuerySet 对象, 如下例子Person模型有一个指向其他Person对象的没有限制的父ForeignKey:

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person

class SmithChildForm(forms.ModelForm):
    parent = forms.ModelChoiceField(queryset=Person.objects.filter(last='Smith'))
    class = Meta:
        model = Person

2.1.4 继承表单

跟继承模型一样

from django import newforms as forms

class PersonForm(forms.Form):
    first = forms.CharField()
    last = forms.CharField()

class Person2Form(forms.Form):
    first = forms.CharField()
    last = forms.CharField()

class AgedPersonForm(PersonForm):
    age = forms.IntergerField()

class MixedPerson(PersonForm, PersonForm2):
    # mixed extends

2.2 填写表单

Django的表单库中有2种表单

  • 绑定的 bound, 和数据有关系
  • 没绑定的 unbound, 没有数据

表单有一个特性, 可以随便往表单的数据字典里添加额外的键值对, 表单会自动无视那些和它们定义无关的输入

from mysite.myapp.forms import PersonForm

def process_form(request):
    post = request.POST.copy()
    form = PersonForm(post)

还可以创建一个虽然未绑定,但是模板打印时载入显示了初始值的表单, 这个命名构造函数参数 initial 是一个字典

每个表单变量也都有一个类似的参数,允许指定它自己的默认值,不过如果有冲突的话, 表单级的 initial 会覆盖变量级的

from django import newforms as forms
from django.shotcuts import render_to_response

class PersonForm(forms.Form):
    first = forms.CharField()
    last = forms.CharField(initial='Smith')#表单定义时,变量级initial参数
    middle = forms.CharField()

def process_form(request):
    if not reuqest.POST:
        form = PersonForm(initial={'first':'John'}) #创建表单实例时, 表单级initial参数,这里如果也定义了last, 则会覆盖上面的Smith
        return render_to_response('myapp/form.html', {'form':form})

使用实例层initial参数的好处在于它的值可以在表单创建的时候才被构造出来, 这样一来,你可以引用在表单或是模型定义的时候还不知道的信息,特别是请求对象里的信息,看下面的例子

from django.shotcuts import get_object_or_404, render_to_response
from mysite.myapp.models import Person, PersonForm

# Views's URL /person/<id>/children/add

def add_relative(request, **kwargs):
    if not request.POST:
        relative = get_object_or_404(Person, pk=kwargs['id']
        form = PersonForm(initial={'last': relative.last})
        return render_to_response('person/form.html', {'form':form})

这例子中 parent 的 last 值是后来得到的,通过 initial 赋给form, 孩子自动填写好父亲的姓氏

http://www.cnblogs.com/ganiks/p/django-template-and-forms.html

2.3 验证和清理

要让表单运行验证程序,可以显式使用 is_valid 方法

if request.POST:
    form = PersonForm(request.POST)
    if form.is_valid:
        new_person = form.save()
        ... ...

执行完验证后,表单对象会得到两个新属性“之一”:

  • errors -- 包含错误信息的字典
  • cleaned_data -- 原来绑定到表单的值的干净版本
  • 干净数据意义在于, 输入的数据需要规范化 --- 从一种或多种可能的输入格式转换为一个统一的输出格式,以方便验证和数据库存储
  • 因为 request.POST 中存储的格式一般都是字符串, 经过干净之后
  • 数字变量的字符串 -- int、long
  • 日期变量的字符串 -- datetime

2.4 显示表单

每个Django表单变量都知道自己在HTML标签里要怎么显示, 这种行为是通过 widget 实现的(后面介绍)

first = forms.CharField(max_length=100, required=True)

<tr>
  <th>
    <label for="id_first">First:</label>
  </th>
  <td>
    <input id="id_first" type="text" name="first" maxlength="100" />
  </td>
</tr>
pf = PersonForm(auto_id=False, label_suffix='')

<tr>
  <th>
    <label>First</label>
  </th>
  <td>
    <input type="text" name="first" maxlength="100" />
  </td>
</tr>
pf = PersonForm(auto_id='%s_id', label_suffix='?')

<tr>
  <th>
    <label for="first_id">First?</label>
  </th>
  <td>
    <input id="first_id" type="text" name="first" maxlength="100" />
  </td>
</tr>

2.4.1 显示全部表单

打印表单有多重方法

  • as_table 默认
  • as_ul
  • as_p

2.4.2 逐个显示表单

2.4.3 Widget

每个Django表单变量都知道自己在HTML标签里要怎么显示, 这种行为是通过 widget 实现的, 这个widget子类(比如TextInput)接受了一个 attrs字典,能直接映射到HTML标签的属性上去。

    middle = forms.CharField(max_length=100,
        widget=forms.TextInput(attrs={'size':3})

<input id="id_middle" maxlength="100" type="text" name="middle" size="3" />

2.4.4 重写一个变量的默认widget

来自定义一个widget LargeTextarea, 默认拥有 40行和100列

forms.py

from django import newforms as forms

class LargeTextareaWidget(forms.Textarea):
    def __init__(self, *args, **kwargs):
        kwargs.setdefault('attrs', {}).update({'rows':40, 'cols':100})
        super(LargeTextareaWidget, self).__init__(*args, **kwargs))

这里的 setdefault对字典用到一个技巧,在给定的键存在情况下回返回现有的值, 若给定的键不存在,则返回提供的值;
用在这里,是确保 kwargs 关键字参数字典一定共用 attrs 字典, 不管原来的构造函数参数是什么, 可以 update 来更新attrs字典添加我们需要的默认值。

views.py

from django import newsforms as forms
from mysite.myapp.forms import LargeTextareaWidget

class ContentForm(forms.Form):
    name = forms.CharField()
    markup = forms.ChoiceField(choices=[('markdown', 'Markdown'),('textile', 'Textile')])

    text = forms.Textarea(widget=LargeTextareaWidget)

是不是还有优化空间?
Django就是Python, 有需要的时候, 很容易把各种类和对象替换出去, 从而达到更灵活的自定义。

forms.py

from django import newforms as forms

class LargeTextareaWidget(forms.Textarea):
    def __init__(self, *args, **kwargs):
        kwargs.setdefault('attrs', {}).update({'rows':40, 'cols':100})
        super(LargeTextareaWidget, self).__init__(*args, **kwargs))

class LargeTextarea(forms.Field):
    widget = LargeTextareaWidget

现在可以更方便的使用这个自定义的widget
views.py

from django import newsforms as forms
from mysite.myapp.forms import LargeTextareaWidget

class ContentForm(forms.Form):
    name = forms.CharField()
    markup = forms.ChoiceField(choices=[('markdown', 'Markdown'),('textile', 'Textile')])

    #text = forms.Textarea(widget=LargeTextareaWidget)
    text = LargeTextarea()
作者: ganiks 
出处: http://www.cnblogs.com/ganiks/ 
本作品由 Ganiks 创作, 欢迎转载,但任何转载必须保留完整文章,在显要地方显示署名以及原文链接。如您有任何疑问,请给我留言。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值