Django Web应用程序(四)

19.1 让用户能够输入数据

19.1.1 添加新主题

创建基于表单的页面的方法几乎与前面创建网页一样: 定义URL,编写一个视图函数并编写一个模板。一个主要差别是,需要导入包含表单的模块 forms.py

1. 用于添加主题的表单
让用户输入并提交信息的页面都是表单,表单的很多工作都是由Django自动完成的,比如:

  • 用户输入信息时,我们需要进行验证,确认提供的信息是正确的数据类型,且不是恶意的信息。
  • 对这些有效信息进行处理,并将其保存到数据库的合适地方

在Django中,创建表单的最简单方式是使用ModelForm, 它根据我们定义的模型中的信息自动创建表单。创建一个名为forms.py文件,并将其存储到models.py所在的目录中, 并在其中编写第一个表单

# forms.py
from django import forms

from .models import Topic

class TopicForm(forms.ModelForm):
    class Meta:
        model = Topic
        fields = ['text']
        labels = {'text': ''}

首先导入了模块forms以及要使用的模型Topic,我们定义了一个名为TopicForm的类,它继承了forms.ModelForm。
最简单的ModelForm版本只包含一个内嵌的 Meta 类,它告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。根据模型Topic创建一个表单,该表单只包含字段text,labels代码让Django不要为字段text生成标签。

2. URL 模式 new_topic
当用户要添加新主题时,我们将切换到http://localhost:8000/new_topic/,添加URL模式到learning_logs/urls.py

# urls.py
from django.conf.urls import url

from . import views

app_name = 'learning_logs'

urlpatterns = [
    # index
    url(r'^$', views.index, name='index'),
    
    # show all topics
    url(r'^topics/$', views.topics, name='topics'),
    
    # show detail info for specific topic
    url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
    
    # add new topic page
    url(r'^new_topic/$', views.new_topic, name='new_topic'),
]

3.视图函数new_topic()
函数new_topic()需要处理两种情况:刚进入new_topic网页(在这种情况下,它应显示一个空表单);对提交的表单数据进行处理,并将用户重定向到网页topics

# views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse

from .models import Topic
from .forms import TopicForm

# Create your views here.

def index(request):
    return render(request, 'learning_logs/index.html')
    
def topics(request):
    """show all topics"""
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)
    
def topic(request, topic_id):
    topic = Topic.objects.get(id=topic_id)
    entries = topic.entry_set.order_by('date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)

def new_topic(request):
    """添加新主题"""
    if request.method != 'POST':
        # 未提交数据: 创建一个新表单
        form = TopicForm()
    else:
        # POST提交的数据, 对数据进行处理
        form = TopicForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('learning_logs:topics'))
            
    context = {'form': form}
    return render(request, 'learning_logs/new_topic.html', context)

如果请求方法是POST, 对提交的表单数据进行处理。我们使用用户输入的数据(它们存储在request.POST中)创建一个TopicForm实例,这样对象form将包含用户提交的信息。
要将提交的信息保存到数据库,必须通过检查确定它们是有效的。函数is_valid()自动验证避免了我们去做大量的工作。如果所有字段都有效,我们就可调用save(),将表单中的数据写入数据库。保存数据后,就可离开这个页面,使用reverse()获取topics的URL。

4. 模板 new_topic

# new_topic.html
{% extends "learning_logs/base.html" %}

{% block content %}
  <p>Add a new topic:</p>
  
  <form action="{% url 'learning_logs:new_topic' %}" method='post'>
      {% csrf_token %}
      {{ form.as_p }}
      <button name="submit">add topic</button>
  
  </form>
{% endblock content %}

实参action告诉服务器将提交的表单数据发送到哪里,这里我们将它发送回视图函数new_topic()。
Django使用模板标签{% csrf_token %}来防止攻击者利用表单来获取对服务器未经授权的访问。 为了显示表单,我们只需要包含模板变量{{ form.as_p }},就可让Django自动创建显示表单所需的全部字段。修饰符as_p让Django以段落格式渲染所有表单元素,这是一种整洁地显示表单的简单方式。
另外,Django 不会为表单创建提交按钮。

5. 链接到页面 new_topic
接下来,我们在页面topics中添加一个到页面new_topic的链接

# topics.html
{% extends "learning_logs/base.html" %}

{% block content %}

<p>Topics</p>

<ul>
  {% for topic in topics %}
    <li>
        <a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
    </li>
  {% empty %}
    <li>No topics have been added yet.</li>
  {% endfor %}    
</ul>

<a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>

{% endblock content %}
用于添加新主题的页面

19.1.2 添加新条目

现在用户可以添加新主题了,但他们还想添加新条目。我们将再次定义URL,编写视图和模板,并了解到添加新条目的网页。但在此之前,我们需要在forms.py中再添加一个类。
** 1. 用于添加新条目的表单**

# forms.py
from django import forms

from .models import Topic, Entry

class TopicForm(forms.ModelForm):
    class Meta:
        model = Topic
        fields = ['text']
        labels = {'text': ''}

class EntryForm(forms.ModelForm):
    class Meta:
        model = Entry
        fields = ['text']
        labels = {'text': ''}
        widgets = {'text': forms.Textarea(attrs={'cols':80})}

我们定义了属性widgets, widget是HTML表单元素,如单行文本框,多行文本趋于或下拉了表。通过设置属性widgets,可覆盖Django选择的默认小部件。通过让Django使用forms.Textarea,我们定制了'text'的输入小部件,将文本区域的宽度设置为80列,而不是默认的40列。

2. URL模式new_entry
在用于添加新条目的页面的URL模式中,需要包含实参topic_id, 因此条目必须与特定的主题相关联。我们将它添加到learning_logs/urls.py中

from django.conf.urls import url

from . import views

app_name = 'learning_logs'

urlpatterns = [
    # index
    url(r'^$', views.index, name='index'),
    
    # show all topics
    url(r'^topics/$', views.topics, name='topics'),
    
    # show detail info for specific topic
    url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
    
    # add new topic page
    url(r'^new_topic/$', views.new_topic, name='new_topic'),
    
    # add new entry page
    url(r'new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'),
]

代码(?P<topic_id>\d+)捕获一个数字值,并将其存储在变量topic_id中。 请求的URL与这个模式匹配时,Django将请求和主题ID发送给函数new_entry()

** 3. 视图函数new_entry()**

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse

from .models import Topic
from .forms import TopicForm, EntryForm

# Create your views here.

def index(request):
    return render(request, 'learning_logs/index.html')
    
def topics(request):
    """show all topics"""
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)
    
def topic(request, topic_id):
    topic = Topic.objects.get(id=topic_id)
    entries = topic.entry_set.order_by('date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)

def new_topic(request):
    """添加新主题"""
    if request.method != 'POST':
        # 未提交数据: 创建一个新表单
        form = TopicForm()
    else:
        # POST提交的数据, 对数据进行处理
        form = TopicForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('learning_logs:topics'))
            
    context = {'form': form}
    return render(request, 'learning_logs/new_topic.html', context)

def new_entry(request, topic_id):
    """在特定的主题中添加新条目"""
    topic = Topic.objects.get(id=topic_id)
    
    if request.method != 'POST':
        # 未提交数据,创建一个空表单
        form = EntryForm()
    else:
        # POST提交的数据,对数据进行处理
        form = EntryForm(data=request.POST)
        if form.is_valid():
            new_entry = form.save(commit=False)
            new_entry.topic = topic
            new_entry.save()
            return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id]))
            
    context = {'topic': topic, 'form': form}
    return render(request, 'learning_logs/new_entry.html', context)

调用save()时,我们传递了实参commit=False,让Django创建一个新的条目对象,并将其存储到ew_entry中,但不将它保存到数据库中。我们将new_entry的属性topic设置为在这个函数开头从数据库中获取的主题,然后调用save()。

4. 模板new_entry

# new_entry.html
{% extends "learning_logs/base.html" %}

{% block content %}

<p><a href="{url 'learning_logs:topic' topic.id}">{{ topic }}</a></p>

<p>Add a new entry:</p>
<form action="{% url 'learning_logs:new_entry' topic.id %}" method='post'>
    {% csrf_token %}
    {{ form.as_p }}
    <button name="submit">add entry</button>

</form>

{% endblock content %}

5. 链接到页面new_entry

# topic.html
{% extends 'learning_logs/base.html' %}

{% block content %}

<p>Topic: {{ topic }}</p>

<p>Entries:</p>
<p>
    <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a>
</p>
<ul>
  {% for entry in entries %}
  <li>
    <p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
    <p>{{ entry.text|linebreaks }}</p>
  </li>
  
  {% empty %}
  <li>
    There are no entries for this topic yet.
  </li>
  {% endfor %}
</ul>

{% endblock content %}
添加新条目new_entry页面

19.1.3 编辑条目

下面创建一个页面,让用户能够编辑既有条目
1. URL模式edit_entry
修改后的learning_logs/urls.py

from django.conf.urls import url

from . import views

app_name = 'learning_logs'

urlpatterns = [
    # index
    url(r'^$', views.index, name='index'),
    
    # show all topics
    url(r'^topics/$', views.topics, name='topics'),
    
    # show detail info for specific topic
    url(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
    
    # add new topic page
    url(r'^new_topic/$', views.new_topic, name='new_topic'),
    
    # add new entry page
    url(r'new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'),
    
    # edit entry page
    url(r'edit_entry/(?P<entry_id>\d+)/$', views.edit_entry, name='edit_entry')
]

2.视图函数edit_entry()
页面edit_entry收到GET请求时,edit_entry()将返回一个表单,让用户能够对条目进行编辑。该页面收到POST请求时,它将修改后的文本保存到数据库中

# views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse

from .models import Topic, Entry
from .forms import TopicForm, EntryForm

# Create your views here.

def index(request):
    return render(request, 'learning_logs/index.html')
    
def topics(request):
    """show all topics"""
    topics = Topic.objects.order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)
    
def topic(request, topic_id):
    topic = Topic.objects.get(id=topic_id)
    entries = topic.entry_set.order_by('date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)

def new_topic(request):
    """添加新主题"""
    if request.method != 'POST':
        # 未提交数据: 创建一个新表单
        form = TopicForm()
    else:
        # POST提交的数据, 对数据进行处理
        form = TopicForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('learning_logs:topics'))
            
    context = {'form': form}
    return render(request, 'learning_logs/new_topic.html', context)

def new_entry(request, topic_id):
    """在特定的主题中添加新条目"""
    topic = Topic.objects.get(id=topic_id)
    
    if request.method != 'POST':
        # 未提交数据,创建一个空表单
        form = EntryForm()
    else:
        # POST提交的数据,对数据进行处理
        form = EntryForm(data=request.POST)
        if form.is_valid():
            new_entry = form.save(commit=False)
            new_entry.topic = topic
            new_entry.save()
            return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id]))
            
    context = {'topic': topic, 'form': form}
    return render(request, 'learning_logs/new_entry.html', context)
    
def edit_entry(request, entry_id):
    """编辑既有条目"""
    entry = Entry.objects.get(id=entry_id)
    topic = entry.topic
    if request.method != 'POST':
        # 初次请求,使用当前条目填充表单
        form = EntryForm(instance=entry)
        
    else:
        # POST提交表单,对数据进行处理
        form = EntryForm(instance=entry, data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id]))
        
    context = {'entry': entry, 'topic': topic, 'form': form}
    return render(request, 'learning_logs/edit_entry.html', context)

在请求方法为GET时将执行的if代码块中,我们使用实参instance=entry创建一个EntryForm实例。这个实参让Django创建一个表达,并使用既有条目对象中的信息填充它。处理POST请求时,我们传递实参instance=entry和data=request.POST,让Django根据既有条目对象创建一个表单实例,并根据request.POST中的相关数据对其进行修改。

3. 模板 edit_entry.html

# edit_entry.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p>

<p>Edit entry:</p>
<form action="{% url 'learning_logs:edit_entry' entry.id %}" method='post'>
    {% csrf_token %}
    {{ form.as_p }}
    <button name="submit">save changes</button>
</form>

{% endblock content %}

4.链接到页面edit_entry

# topic.html
{% extends 'learning_logs/base.html' %}

{% block content %}

<p>Topic: {{ topic }}</p>

<p>Entries:</p>
<p>
    <a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a>
</p>
<ul>
  {% for entry in entries %}
  <li>
    <p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
    <p>{{ entry.text|linebreaks }}</p>
    <p>
        <a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entry</a>
    </p>
  </li>
  
  {% empty %}
  <li>
    There are no entries for this topic yet.
  </li>
  {% endfor %}
</ul>

{% endblock content %}
每个条目都有一个用于对其进行编辑的链接
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容