概述
本章主要集中于django的view。view指django中的具有自己的模板及实现特定的功能的网页。比如说,一个博客里面可能包括以下这些views:
- Blog homepage – displays the latest few entries.
- Entry “detail” page – permalink page for a single entry.
- Year-based archive page – displays all months with entries in the given year.
- Month-based archive page – displays all days with entries in the given month.
- Day-based archive page – displays all entries in the given day.
- Comment action – handles posting comments to a given entry.
在polls项目里会有如下views:
- Question “index” page – displays the latest few questions.
- Question “detail” page – displays a question text, with no results but with a form to vote.
- Question “results” page – displays results for a particular question.
- Vote action – handles voting for a particular choice in a particular question.
在django里面网页及内容是通过views来进行传递的。每个view都代表了一个功能。django是通过查找url来匹配是找哪个view。
在实际浏览网页的过程中,你可能会遇到类似于“ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”这样写的网页。然后django提供了比这种写法更好的url模型结构。
为了更好的编写view和url的关系,django使用了URLconfs这种结构。URLconfs指的就是描述view和url的映射关系。
本篇介绍了一些基本的URLconfs,如需探索更多,请移步官网的 URL dispatcher。
构造简单的views模型
现在我们编写polls/views.py文件。
polls/views.py 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)
然后编写 polls.urls文件
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'), ]
然后启动django应用,在浏览器访问/polls/34/,然后会调用detail()
方法并且会展示你在url里提供的任何id。然后调用“/polls/34/results/” and “/polls/34/vote/”也会调用对应的结果及投票页面。
当有人请求你的网站上的某个页面时-比如“/polls/34/”,django会加载mysite.urls
模块,因为这是在ROOT_URLCONF
里配置的。它会查找对应的urlpatterns并告知路由。在匹配到对应的url后,会去掉这个url,然后再根据后面的入参去做进一步的操作。也就是根据<int:question_id>/去调用detail()的view:
detail(request=<HttpRequest object>, question_id=34)
上面代码的question_id=34来源于<int:question_id>。尖括号里面的int:代表数据的类型,然后question_id代表入参。
实现demo的view
每个view都会实现1到2个目标:给一个包括了响应内容的HttpResponse
或者抛出一个404报错。这取决于你。
你的view可以取数据库的数据或者不取。可以使用django的模板系统或者第三方模板系统或者都不用。可以生成一个PDF文件,输出xml,创造一个zip文件或者使用任何python的依赖库
django需要的是一个HttpResponse或者一个异常。
现在开始编写我们的代码,使用数据库的数据,实现展示最新的5个问题:
polls/views.py from django.http import HttpResponse 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) # Leave the rest of the views (detail, results, vote) unchanged
上述代码有点问题:view中的网页设计是硬编码的。如果你需要修改网页的内容,你必须要修改python的代码。所以我们引入django的模板系统来做区分。
首先我们在polls的目录下创建一个templates的目录。django会在里面寻找对应的模板。
你的项目的TEMPLATES配置描述了django怎样加载和使用模板。默认配置项在
TEMPLATES。为了方便DjangoTemplates会在每个安装的INSTALLED_APPS
.下去查找模板子文件夹。
在刚才创建的templates目录下新建一个另外的文件夹polls,并在里面新建一个index.html文件。换句话说你的模板应该在polls/templates/polls/index.html。
然后在index.html中编写如下内容:
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 %}
然后再更新下polls/views.py文件如下:
polls/views.py 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))
上述代码加载了模板polls/index.html并且传递了一个字典内容给它。
快捷方式:render()
django提供了一个更方便的方式来实现上述逻辑,如下:
polls/views.py 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)
注意一下,一旦按照我们上述代码的方式实现了,那么我们就不需要再导包loader
和HttpResponse
。
render()这个方法提供了三个入参,2个必传入参request及模板名称,以及一个可选的字典入参。它会返回一个html响应。
抛出404异常
现在我们继续处理展示问题详细页面的部分:
polls/views.py 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})
上述代码里出现了一个新的概念:这个view会抛出一个Http404
假如问题id对应的问题不存在。
我们会在晚点讨论在polls/detail.html应该写什么,但是如果你想早点让上述代码实现的话,可以按照如下这样写:
polls/templates/polls/detail.html {{ question }}
便捷处理404
上述抛出404异常的,django提供了一个更快速的方式:
polls/views.py 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})
get_object_or_404这个方法第一个入参是一个django的数据库model类,后续入参可以是任意的入参。假如不存在那么会抛出异常。存在会返回对应的信息。
使用模板系统
然后现在转回我们的detail这个view。应该这样编写polls/detail.html
:
polls/templates/polls/detail.html <h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul>
在{{ question.question_text }}中,首先django会进行一个基于question的字典查找,如果失败没有找到,那么会对这个question类进行属性值查找--也就是我们这种情况。如果属性查找也失败了,就会尝试进行索引查找。
在{% for %}
循环中:question.choice_set.all被python解释成question.choice_set.all()。
移掉模板中的硬编码
记住,当我们在polls/index.html中给每个question增加一个链接时,这个链接会被像如下一样硬编码:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
这种硬编码会在项目中出现很多模板时出现困扰,不知道对应的是哪个模板。但是可以通过path()
方法中定义的name属性来做区分:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
在path中定义的如下:
... # the 'name' value as called by the {% url %} template tag path('<int:question_id>/', views.detail, name='detail'), ...
如果你想修改polls detail view为polls/specifics/12/,那么你不需要修改模板,可以直接在polls/urls.py中修改:
... # added the word 'specifics' path('specifics/<int:question_id>/', views.detail, name='detail'), ...
给URL增加命名空间
这个项目只有一个app,但是真实的django应用中有很多个app。那么django怎么对他们做区分呢,假如有多个app中都包括了detail这个view?
这个答案是给URLconf增加命名空间。然后我们转到前面的文件polls/urls.py,在里面增加一行代码:
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'), ]
现在修改你的polls/index.html文件由如下:
polls/templates/polls/index.html <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
改为现在的如下使用命名空间的模式:
polls/templates/polls/index.html <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>