Django中view的创建

view就是一个一个页面,前面在创建应用的时候创建了一个简单的index view。这里演示更多view的用法。每个view有一个独立无二的url,对应一个python函数,这个python函数负责渲染页面并返回给请求端。

urls和views的对应关系用URL dispatcher描述。

带路径参数的views

在Django应用polls的views.py中创建几个带有传入参数的views:

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

注意这几个views的传入参数除了request,还有一个question_id,question_id是从url中解析出来的,是在设置urls和views的对应关系是设定的,例如,在polls/urls.py中设置如下:

from django.urls import path

from . import views

urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

urls中的<int:question_id>就是解析给question_id的数值,类型是int。

在url中带的参数,被解析给view的参数,然后在view的处理函数中被返回,例如:

$ curl 127.0.0.1:8000/polls/3/
You're looking at question 3.

$ curl 127.0.0.1:8000/polls/3/results/
You're looking at the results of question 3.

$ curl 127.0.0.1:8000/polls/3/vote/
You're voting on question 3.

views中的准备响应数据

views最终要返回一个HttpResponse类型的变量或者抛出Http404等异常,在views对应的函数中,可以用各种方式生成要返回的数据,譬如读取数据库、通过运算计算出等。

例如把index的view进行扩展,返回从数据库中查询到数据:

from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

这个views方法的实现同时演示了在Django中如何引用models,并通过models查询数据库。

views中使用页面模版

前面的几个views都是硬编码了返回的数据,没有数据和样式分开,这样会导致要修改返回的数据的样式时,必须修改代码。可以用页面模版(templates)功能将数据和样式分开。

Django项目的settings.py中配置了页面模版类型,例如:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'APP_DIRS': True,
    },
]

Django支持的页面模版有:

'django.template.backends.django.DjangoTemplates'
'django.template.backends.jinja2.Jinja2'

页面模版文件默认位于Django应用的templates目录中,创建一个页面模版文件polls/templates/polls/index.html:

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

页面模版文件中可以进行逻辑判断,根据传入的参数值生成不同的内容。

然后在views中加载页面模版文件,并在渲染后返回:

from django.http import HttpResponse
from django.template import loader

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

这时候请求index view,返回下面的数据:

$ curl http://127.0.0.1:8000/polls/

    <ul>

        <li><a href="/polls/1/">What&#39;s up?</a></li>

    </ul>

可以用render()函数简化views中的代码:

from django.shortcuts import render

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

views中抛出404异常

当没有找到对应的数据时,抛出404异常:

from django.http import Http404
from django.shortcuts import render

from .models import Question

def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

可以用get_object_or_404()函数简化代码:

from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

对应的模版文件polls/detail.html:

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

去掉模版文件中硬编码

前面使用的模版文件中,有对url的硬编码,例如:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

这样会降低模版文件的自适应能力,可以用urls.py中的定义动态生成路径,例如:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

href的值是名为detail的view对应的url,url路径参数的值是question.id。

这样一来,更改view对应的url时,只需要在urls.py中修改,不需要改变页面模版文件。

为url设置namespace

Django项目中可能有多个Django应用,每个应用都会定义自己的views,以及对应的urls,不同Django应用的view可能重名,那么要怎样在页面对重名的views进行区分?

答案是在每个应用的urls.py中,定义一个名为app_name的变量,这个变量为urls中添加了一个命名空间,例如polls/urls.py:

from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

上面的代码定义了url的命名空间polls,在页面模版中引用对应的views时需要加上poll:前缀:

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

在views中实现form表单

更改detail.html模版,在其中插入一个表单,注意表单中有一个csrf_token,这是django内置支持的CSRF防御机制:

<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
<input type="submit" value="Vote">
</form>

在接收表单请求的views中处理表单数据:

from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse

from .models import Choice, Question
# ...
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))