一,表单form
为了接收用户的投票选择,我们需要在前段页面显示一个投票界面,让我们重写之前的polls/detail.html文件,代码如下:
<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>
简要说明:
- 上面的模板显示一系列单选按钮,按钮的值时选项的ID,按钮的名字是字符串“choice”。这意味着,当你选择了其中某个按钮,并提交表单,一个包含数据choice=# 的POST请求将被发送到指定的url,#是被选择的选项的ID,这就是HTML表单的基本概念。
- 如果你有一定的前端知识,那么form标签的action属性和method属性应该很清楚其含义,action表示你要发送的目的url,method表示提交数据的方式,一般分为POST和GET。
- forloop.counter是Django模板系统专门提供的一个变量,用来表示你当前循环的次数,一般用来给循环项目添加有序数标。
- 由于我们发送了一个POST请求,就必须考虑一个跨扎请求伪造的安全问题,简称CSRF,Django为你提供了一个简单的方法来避免这个困扰,那就是在form表单内添加一条{ % csrf_token %} 标签,标签名不可更改,固定格式,位置任意,只要是在form表单内。这个方法对form表单的提交方式方便好使,但如果是用ajax的方式提交数据,就不能用这个方法了。
现在,让我们创建一个处理提交过来的数据视图,前面我们已经写了一个“占坑”的vote视图的url(polls/urls.py)
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
以及占坑的vote视图函数(polls.views.py),我们把坑填起来:
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect, HttpResponse 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): # 发生choice未找到异常时,重新返回表单页面,并给出提示信息 return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() # 成功处理数据后,自动跳转到结果页面,防止用户连续多次提交。 return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
有些新的东西,我们要解释一下:
- request.POST是一个类似字典的对象,允许你通过键名访问提交的数据,本例中,request.POST['choice'] 返回被选择选项的ID,并且值的类型永远是string字符串,哪怕它看起来像数字!同样的,你也可以用类似的手段获取GET请求发送过来的数据,一个道理。
- request.POST['choice'] 有可能触发一个KeyError异常,如果你的POST数据里没有提供choice键值,在这种情况下,上面的代码会返回表单页面并给出错误提示,PS:通常我们会给个默认值,防止这种异常的产生,例如request.POST['choice',None],一个None解决所有问题。
- 在选择计数器加一后,返回的是一个HttpResponseRedirect而不是先前我们常用的HTTPResponse,HttpResponseRedirect需要一个参数:重定向的URL。这里有一个建议,当你成功处理POST数据后,应当保持一个良好的习惯,始终返回一个HttpResponseRedirect。这里不仅仅是对Django而言,它是一个良好的WEB开发习惯。
- 我们再上面HttpResponseRedirect的构造器中使用了一个reverse()函数,它能帮助我们避免在视图函数中硬编码URL。它首先需要一个我们再URLconf中指定的name,然后是传递的数据。例如'polls/3/results/’ ,其中的3是某个question.id的值,重定向后将进入polls:results对应的视图,并将question.id传递给它,白话来讲,就是把活扔给另一个路由对应的视图去干。
当有人对某个问题投票后,vote()视图重定向到了问卷的结果显示页面,下面我们来写这个处理结果页面的视图(polls/views.py):
from django.shortcuts import get_object_or_404, render def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question})
同样,还需要些个模板polls/templates/polls/results.html (路由,视图,模板,模型都是这个套路)
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
现在你可以到浏览器中访问/polls/1/ 了 ,投票了。你会看到一个结果页面,每投一次,它的内容就会更新一次。如果你提交的时候没有选择项目,则会得到一个错误提示。
如果你在前面漏掉了一部分操作没做,比如没有创建choice选项对象,那么可以按下面的操作,补充一下:
D:Djangomysite>python manage.py shell Python 3.7.1 (v3.7.1:260ec2c36a, Oct 20 2018, 14:57:15) [MSC v.1915 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from polls.models import Question >>> q = Question.objects.get(pk=1) >>> q.choice_set.create(choice_text = 'Not much',votes=0) <Choice: Not much> >>> q.choice_set.create(choice_text = 'The sky',votes=0) <Choice: The sky> >>> q.choice_set.create(choice_text = 'Just hacking again',votes=0) <Choice: Just hacking again>
为了方便,我将当前状态的各主要内容一并贴出,依次可以对照参考!
1 ——完整的mysite/urls.py文件如下:
from django.conf.urls import url,include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^polls/', include('polls.urls')), ]
2 ——完整的mysite/settings.py文件如下:
import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '85vvuta(p05ow!4pz2b0qbduu0%pq6x5q66-ei*pg+-lbdr#m^' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'polls', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'mysite.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'mysite.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = True
3 ——完整的polls/views.py 应该如下所示:
from django.shortcuts import reverse from django.shortcuts import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.shortcuts import HttpResponse from django.shortcuts import render from .models import Choice from .models import Question from django.template import loader # Create your views here. 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)) def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question}) def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': 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): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
4 ——完整的polls/urls.py 应该如下所示:
from django.conf.urls import url from . import views app_name = 'polls' urlpatterns = [ # ex: /polls/ url(r'^$', views.index, name='index'), # ex: /polls/5/ url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'), # ex: /polls/5/results/ url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'), # ex: /polls/5/vote/ url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
5 ——完整的polls.model.py文件如下:
from django.db import models import datetime from django.utils import timezone # Create your models here. class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1) def __str__(self): return self.question_text class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) def __str__(self): return self.choice_text
6 ——完整的polls/admin.py 文件如下:
from django.contrib import admin # Register your models here. from .models import Question admin.site.register(Question)
7 ——完整的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 %}
8 ——完整的templates/polls/detail.html 文件如下:
<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>
9 ——完整的templates/polls/results.html 文件如下:
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
vote()视图没有对应的HTML 模板,它直接跳转到results视图去了。
运行服务器,测试各功能如下:
这是问卷列表页面:
这是“What's up” 问卷选项页面:
这是选择结果的页面:
这是没有选择选项时,提示错误信息的页面:
二,使用类视图:减少重复代码
上面的detail ,index和results 视图的代码非常相似,有点冗余。他们都具有类似的业务逻辑,实现类似的功能:通过从URL传递过来的参数去数据库查询数据,加载一个模板,利用刚才的数据渲染模板,返回这个模板,由于这个过程是如此的常见,Django很善解人意的帮你想办法偷懒,于是他提供了一种快捷方式,名为“类视图”。
现在,让我们来试试看将原来的代码改为使用类视图的方式,整个过程分为三步走:
- 修改URLconf设置
- 删除一些旧的无用的视图
- 采用基于类视图的新视图
PS:为什么本文的代码来回改动这么频繁?
通常在写一个Django的APP时候,我们一开始就要决定使用类视图还是不用,而不是等到代码写到一半了才重构你的代码成类视图。但是前面为了理解视图,所以故意走了一条比较曲折的路。
1,改良路由配置系统URLconf
URL配置(URLConf)就像是Django所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于客户端发来的某个URL调用哪一段逻辑代码对应执行。
打开polls/urls.py文件,将其修改成下面的样子:
from django.conf.urls import url from . import views app_name = 'polls' urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
请注意:在上面的第2,3条目中将原来的question_id 改成了pk。若要从URL中捕获一个值,只需要在它周围放置一对圆括号。不需要添加一个前导的反斜杠,因为每个URL都有。例如,应该是^articles而不是^/articles。每个正则表达式前面的 r是可选的但是建议加上,它告诉Python这个字符串是“原始的”——字符串中任何字符都不应该转义。示例:
先看一个URL路由配置:
from django.urls import path,re_path from app01 import views urlpatterns = [ re_path(r'^articles/2003/$', views.special_case_2003), re_path(r'^articles/([0-9]{4})/$', views.year_archive), re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), re_path(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), ]
解析:
一些请求的例子: /articles/2005/03/ 请求将匹配列表中的第三个模式。Django 将调用函数views.month_archive(request, '2005', '03')。 /articles/2005/3/ 不匹配任何URL 模式,因为列表中的第三个模式要求月份应该是两个数字。 /articles/2003/ 将匹配列表中的第一个模式不是第二个,因为模式按顺序匹配,第一个会首先测试是否匹配。请像这样自由插入一些特殊的情况来探测匹配的次序。 /articles/2003 不匹配任何一个模式,因为每个模式要求URL 以一个反斜线结尾。 /articles/2003/03/03/ 将匹配最后一个模式。Django 将调用函数views.article_detail(request, '2003', '03', '03')。
2,修改视图
接下来,打开polls/views.py文件,删除index,detail和 results 视图,替换成Django的类视图,如下所示:
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect from django.urls import reverse from django.views import generic from .models import Choice, Question class IndexView(generic.ListView): template_name = 'polls/index.html' context_object_name = 'latest_question_list' def get_queryset(self): """返回最近发布的5个问卷.""" return Question.objects.order_by('-pub_date')[:5] class DetailView(generic.DetailView): model = Question template_name = 'polls/detail.html' class ResultsView(generic.DetailView): model = Question template_name ='polls/results.html' def vote(request, question_id): ... # 这个视图未改变!!!
在这里,我们使用了两种类视图ListView和DetailView(他们是作为父类被继承的)。这两者分别代表“显示一个对象的列表” 和“显示特定类型对象的详细页面” 的抽象概念。
- 每一种类视图都需要知道它要作用在哪个模型上,这通过model 属性提供。
- DetailView类视图需要从url 捕获到的称为“pk” 的主键值,因此我们在url 文件中将2和3条目的<question_id> 修改成 <pk> 。
默认情况下,DetailView 类视图使用一个称作 <app name>/<model name>_detail.html 的模板。
在本例中,实际使用的是polls/detail.html。 template_name属性就是用来指定这个模板名的,用于代替自动生成的默认模板名。(一定要仔细观察上面的代码,对号入座,注意细节)同样的,在results列表视图中,指定template_name 为 'polls/results.html’ ,这样就确保了虽然result视图和detail 视图同样继承了DetailView类,使用了同样的model: Question,但他们依然会显示不同的页面。
类似的,ListView类视图使用一个默认模板称为 <app name>/<model name>_list.html 。我们也使用template_name 这个变量来告诉ListView使用我们已经存在的'polls/index.html' 模板,而不是使用他自己默认的那个。
在前面部分,我们给模板提供了一个包含question 和 lastest_question_list 的上下文变量,而对于DetailView,question变量会被自动提供,因为我们使用Django的模型(Question),Django会智能的选择合适的上下文变量。然而,对于ListView ,自动生成的上下文变量是 question_list 。为了覆盖它,我们提供了 context_object_name 属性,指定说我们希望使用 lastest_question_list 而不是 question_list。
现在可以运行开发服务器,然后试试基于类视图的应用程序了,类视图是Django比较高级的一种用法。
3,URL配置(URLconf)
3.1有名分组
简单的路由配置示例:
from django.urls import path,re_path from app01 import views urlpatterns = [ re_path(r'^articles/2003/$', views.special_case_2003), re_path(r'^articles/([0-9]{4})/$', views.year_archive), re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), re_path(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), ]
上面的示例使用简单的,没有命名的正则表达式组(通过圆括号)来捕获URL中的值并以位置参数传递给视图。在更高级的用法中,可以使用命名的正则表达式组来捕获URL中的值并以关键字参数传递给视图。
在Python正则表达式中,命名正则表达式组的语法是(?Ppattern),其中name是组的名称,pattern是要匹配的模式。
下面是以上URLconf使用命名组的重写:
from django.urls import path,re_path from app01 import views urlpatterns = [ re_path(r'^articles/2003/$', views.special_case_2003), re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail), ]
这个实现与前面的示例完全相同,只有一个细微的差别:捕获的值作为关键字参数而不是位置参数传递给视图函数。例如:
/articles/2005/03/ 请求将调用views.month_archive(request, year='2005', month='03')函数, 而不是views.month_archive(request, '2005', '03')。 /articles/2003/03/03/ 请求将调用函数 views.article_detail(request, year='2003', month='03', day='03')。
所以在实际应用中,这意味着你的URLconf会更加清晰且不容易产生顺序问题的错误——你可以在你的视图函数中定义重新安排参数的顺序。当然,这些好处是以简介为代价。
3.2 分发
At any point, your urlpatterns can “include” other URLconf modules. This essentially “roots” a set of URLs below other ones. ''' from django.urls import path,re_path,include from app01 import views urlpatterns = [ re_path(r'^admin/', admin.site.urls), re_path(r'^blog/', include('blog.urls')), ]
3.3 反向解析
在使用Django项目时,一个常见的需求是获得URL的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。人们强烈希望不要硬编码这些URL(费力,不可扩展且容易出现错误)或者设计一种与URLconf毫不相关的专门的URL生成机制,因为这样容易导致一定程度上产生过期的URL。
在需要URL的地方,对于不同层级,DJango提供不同的工具用于URL反查:
- 在模板中:使用url模板标签
- 在Python代码中:使用from django.urls import reverse() 函数
urls.py
from django.conf.urls import url from . import views urlpatterns = [ re_path(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'), ]
在模板中:
<a href="{% url 'news-year-archive' 2019 %}">2019 Archive</a> <ul> {% for yearvar in year_list %} <li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li> {% endfor %} </ul>
在python中:
from django.urls import reverse from django.http import HttpResponseRedirect def redirect_to_year(request): year = 2019 return HttpResponseRedirect(reverse('news-year-archive', args=(year,))) # 同redirect("/path/")
当命名你的URL模式时,请确保使用的名称不会与其他应用中名称冲突。如果你的URL模式叫做comment,而另外一个应用中也有一个同样的名称,当你在模板中使用这个名称的时候不能保证将插入哪个URL。在URL名称中加上一个前缀,比如应用的名称,将减少冲突的可能。我们建议使用myapp-comment ,而不是comment。
3.4 命名空间
命名空间(英语:Namespace)是表示标识符的可见范围。一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。这样,在一个新的命名空间中可定义任何标识符,他们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他命名空间中。
由于name没有作用域,Django在反解URL时,会在项目全局顺序搜索,当查找到第一个name指定URL时,立即返回我们再开发项目时,会经常使用name属性反解出URL,当不小心在不同的app的urls中定义相同的name时,可能会导致URL反解错误,为了避免这种事情发生,引入了命名空间。
project的urls.py
urlpatterns = [ re_path(r'^admin/', admin.site.urls), re_path(r'^app01/', include("app01.urls",namespace="app01")), re_path(r'^app02/', include("app02.urls",namespace="app02")), ]
app01.urls:
urlpatterns = [ re_path(r'^index/', index,name="index"), ]
app02.urls:
urlpatterns = [ re_path(r'^index/', index,name="index"), ]
app01.views:
from django.core.urlresolvers import reverse def index(request): return HttpResponse(reverse("app01:index"))
app02.views:
from django.core.urlresolvers import reverse def index(request): return HttpResponse(reverse("app02:index"))
django 2.0 版的path
思考情况如下:
urlpatterns = [ re_path('articles/(?P<year>[0-9]{4})/', year_archive), re_path('article/(?P<article_id>[a-zA-Z0-9]+)/detail/', detail_view), re_path('articles/(?P<article_id>[a-zA-Z0-9]+)/edit/', edit_view), re_path('articles/(?P<article_id>[a-zA-Z0-9]+)/delete/', delete_view), ]
考虑下这样的两个问题:
1,函数 year_archive 中 year 参数是字符串类型的,因此需要先转化为整数类型的变量值,当然 year= int(year) 不会有诸多如 TypeError 或者 ValyeError的异常。那么有没有一种方法,在url中,使得这一转化步骤可以由Django自动完成呢?
2,三个路由中的 article_id 都是同样的正则表达式,但是你需要些三遍,当之后 article_id 规则改变后,需要同时修改三处代码,那么有没有一种方法,只需要修改一处即可。
在DJango2.0中,可以使用path解决以上两个问题。
from django.urls import path from . import views urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<int:year>/', views.year_case_2003), path('articles/<int:year>/<int:mouth>/', views.month_archive), path('articles/<int:year>/<int:month>/<slug>/', views.article_detial), ]
基本规则:
- 使用尖括号<>从url中捕获值
- 捕获值可以包含一个转化器类型(converter type),比如使用 <int:name>捕获一个整数变量。如果没有转化器,将匹配任何字符串,当然也包括了 / 字符
- 无需添加前导斜杠
下面是根据2.0 官方文档 整理的示例分析表:
3.5 注册自定义转化器
对于一些复杂或者复用的需要,可以定义自己的转化器。转化器是一个类或者接口,它的要求有三点:
regex类属性,字符串类型
to_python(self, value) 方法,value是由类属性regex所有匹配到的字符串,返回具体的Python变量值,以供Django传递到奥对应的视图函数中。
to_url(selfm value)方法,和to_python 相反,value是一个具体的python变量值,返回器字符串,通常用于url反向引用。
例子:
class FourDigitYearConverter: regex = '[0-9]{4}' def to_python(self, value): return int(value) def to_url(self, value): return '%04d' % value
使用register_converter将其注册到URL配置中:
from django.urls import register_converter, path from . import converters, views register_converter(converters.FourDigitYearConverter, 'yyyy') urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<yyyy:year>/', views.year_archive), ... ]
三,自动化测试概述
1,什么是自动化测试
测试是一种例行的,不可缺失的工作,用于检测你的程序是否符合预期。
测试可以划分为不同的级别。一些测试可能专注于小细节(比如某一个模型的方法是否会返回预期的值?), 一些测试则专注于检查软件的整体运行是否正常(用户在对网站进行了一系列的输入后,是否返回了期望的结果?)。
测试可以分为手动测试和自动测试。手动测试很常见,有时候print一个变量内容,都可以看做是测试的一部分。手动测试往往很零碎、不成体系、不够完整、耗时费力、效率低下,测试结果也不一定准确。
自动化测试则是系统地较为完整地对程序进行测试,效率高,准确性高,并且大部分共同的测试工作会由系统来帮你完成。一旦你创建了一组自动化测试程序,当你修改了你的应用,你就可以用这组测试程序来检查你的代码是否仍然同预期的那样运行,而无需执行耗时的手动测试。
2,为什么需要测试?
- 测试可以节省时间
- 测试不仅仅可以发现问题,还能防止问题
- 测试使你的代码更受欢迎
- 测试有助于团队合作
3,编写测试程序
Django是一个全面,完善,严谨的Web框架,当然不会缺失测试功能。
3.1 遇到BUG
在前面我们的投票应用中有一个小BUG需要修改:在Question.was_published_recently() 方法的返回值中,当Question 在最近的一天发布的时候返回True(这是正确的),然而当Question在未来的日期内发布的时候也返回True(这是错误的)。
我们可以在admin后台创建一个发布日期在未来的Question,然后在shell里面验证这个bug:
>>> import datetime >>> from django.utils import timezone >>> from polls.models import Question >>> # 创建一个发布日期在30天后的问卷 >>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30)) >>> # 测试一下返回值 >>> future_question.was_published_recently() True
问题的核心在于我们允许创建在未来时间才发布的问卷,由于“未来”不等于“最近”,因此这显然是个bug。
3.2 创建一个测试来暴露这个bug
刚才我们是在shell里面测试了这个bug,那么如何通过自动化测试来发现这个bug呢?
通常我们会把测试代码放在应用的tests.py文件中,测试系统将自动的从任何名字以test开头的文件中查找测试程序,每个APP在创建的时候,都会自动创建一个test.py 文件,就像view.py 等文件一样。
将下面的代码输入投票应用的polls/tests.py 文件中:
import datetime from django.utils import timezone from django.test import TestCase from .models import Question class QuestionMethodTests(TestCase): def test_was_published_recently_with_future_question(self): """ 在将来发布的问卷应该返回False """ time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False)
我们再这里创建了一个django.test.TestCase 的子类,它具有一个方法,该方法创建一个pub_date在未来的Question实例,最后我们检查was_published_recently() 的输出,他应该是FALSE。
3.3 运行测试程序
在终端中,运行下面的命令:
python manage.py test polls
你将看到结果如下:
Creating test database for alias 'default'... System check identified no issues (0 silenced). F ====================================================================== FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionMethodTest2) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:Djangomysitepolls ests.py", line 15, in test_was_published_recently_with_future_question self.assertIs(future_question.was_published_recently(),False) AssertionError: True is not False ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (failures=1) Destroying test database for alias 'default'...
这其中都发生了什么?
- Python manage.py test polls 命令会去查找投票应用中所有测试程序
- 发现一个django.test.TestCase 的子类
- 为测试创建一个专用的数据库
- 查找名字以test开头的测试方法
- 在test_was_published_recently_with_feature_question方法中,创建一个Question实例,该实例的pub_data字段的值时30天后的未来日期。
- 然后利用assertIs()方法,它发现was_published_recently()返回了True,而不是我们希望的False。
最后,测试程序会通知我们那个测试失败了,错误出现在哪一行。
整个测试用例基本上和Python内置的unittest非常相似。
3.4 修改bug
我们已经知道问题所在,现在可以去修复bug了,修复源代码,具体如下:
# polls/models.py def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now
再次运行测试程序:
Creating test database for alias 'default'... System check identified no issues (0 silenced). . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK Destroying test database for alias 'default'...
可以看到没有bug了。
3.5 更加全面的测试
事实上,前面的测试用例还不够完整,为了使was_published_recently() 方法更加可靠,我们在上面的测试类中再额外添加两个其他的方法,来更加全面的进行测试。
# polls/tests.py def test_was_published_recently_with_old_question(self): """ 只要是超过1天的问卷,返回False """ time = timezone.now() - datetime.timedelta(days=1, seconds=1) old_question = Question(pub_date=time) self.assertIs(old_question.was_published_recently(), False) def test_was_published_recently_with_recent_question(self): """ 最近一天内的问卷,返回True """ time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) recent_question = Question(pub_date=time) self.assertIs(recent_question.was_published_recently(), True)
现在我们有三个测试来保证无论发布时间在过去,现在还是未来。Question.was_published_recently() 都将返回正确的结果。
四,模板语法
在Django模板中遍历复杂数据结构的关键是句点字符。
4.1,模板的组成
其组成: HTML代码 + 逻辑控制代码
4.2,逻辑控制代码的组成
4.2.1 变量(使用双大括号来引用变量)
语法格式: {{var_name}}
举个例子:
def index(request): import datetime s="hello" l=[111,222,333] # 列表 dic={"name":"james","age":18} # 字典 date = datetime.date(1999, 5, 2) # 日期对象 class Person(object): def __init__(self,name): self.name=name person_james=Person("james") # 自定义类对象 person_durant=Person("durant") person_curry=Person("curry") person_list=[person_james,person_durant,person_curry] return render(request,"index.html",{"l":l,"dic":dic,"date":date,"person_list":person_list})
template:
<h4>{{s}}</h4> <h4>列表:{{ l.0 }}</h4> <h4>列表:{{ l.2 }}</h4> <h4>字典:{{ dic.name }}</h4> <h4>日期:{{ date.year }}</h4> <h4>类对象列表:{{ person_list.0.name }}</h4>
注意:句点符也可以用来引用对象的方法(无参数方法)
<h4>字典:{{ dic.name.upper }}</h4>
Template 和Context对象
>>> python manange.py shell (进入该django项目的环境) >>> from django.template import Context, Template >>> t = Template('My name is {{ name }}.') >>> c = Context({'name': 'Stephane'}) >>> t.render(c) 'My name is Stephane.' # 同一模板,多个上下文,一旦有了模板对象,你就可以通过它渲染多个context,无论何时我们都可以 # 像这样使用同一模板源渲染多个context,只进行 一次模板创建然后多次调用render()方法渲染会 # 更为高效: # Low for name in ('John', 'Julie', 'Pat'): t = Template('Hello, {{ name }}') print(t.render(Context({'name': name}))) # Good t = Template('Hello, {{ name }}') for name in ('John', 'Julie', 'Pat'): print(t.render(Context({'name': name})))
Django模板解析非常快捷。大部分的解析工作都是在后台通过对简短正则表达式一次性调用来完成。这和基于XML的模板引擎形成鲜明对比,那些引擎承担了XML解析器的开销,且往往比Django模板渲染引擎要慢上几个数量级。
推荐方法
from django.shortcuts import render,HttpResponse from django.template.loader import get_template #记得导入 # Create your views here. import datetime from django.template import Template,Context # def current_time(req): #原始的视图函数 # now=datetime.datetime.now() # html="<html><body>现在时刻:<h1>%s.</h1></body></html>" %now # return HttpResponse(html) # def current_time(req): #django模板修改的视图函数 # now=datetime.datetime.now() # t=Template('<html><body>现在时刻是:<h1 style="color:red">{{current_date}}</h1></body></html>') #t=get_template('current_datetime.html') # c=Context({'current_date':now}) # html=t.render(c) # return HttpResponse(html) #另一种写法(推荐) def current_time(req): now=datetime.datetime.now() return render(req, 'current_datetime.html', {'current_date':now})
深度变量的查找(万能的句点号)
在到目前为止的例子中,我们通过context传递的简单参数值主要是字符串,然而,模板系统能够非常简洁的处理更加复杂的数据结构,例如list,dictionary和自定义的对象。
在Django模板中遍历复杂数据结构的关键是句点字符(.)
#最好是用几个例子来说明一下。 # 首先,句点可用于访问列表索引,例如: >>> from django.template import Template, Context >>> t = Template('Item 2 is {{ items.2 }}.') >>> c = Context({'items': ['apples', 'bananas', 'carrots']}) >>> t.render(c) 'Item 2 is carrots.' #假设你要向模板传递一个 Python 字典。 要通过字典键访问该字典的值,可使用一个句点: >>> from django.template import Template, Context >>> person = {'name': 'Sally', 'age': '43'} >>> t = Template('{{ person.name }} is {{ person.age }} years old.') >>> c = Context({'person': person}) >>> t.render(c) 'Sally is 43 years old.' #同样,也可以通过句点来访问对象的属性。 比方说, Python 的 datetime.date 对象有 #year 、 month 和 day 几个属性,你同样可以在模板中使用句点来访问这些属性: >>> from django.template import Template, Context >>> import datetime >>> d = datetime.date(1993, 5, 2) >>> d.year >>> d.month >>> d.day >>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.') >>> c = Context({'date': d}) >>> t.render(c) 'The month is 5 and the year is 1993.' # 这个例子使用了一个自定义的类,演示了通过实例变量加一点(dots)来访问它的属性,这个方法适 # 用于任意的对象。 >>> from django.template import Template, Context >>> class Person(object): ... def __init__(self, first_name, last_name): ... self.first_name, self.last_name = first_name, last_name >>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.') >>> c = Context({'person': Person('John', 'Smith')}) >>> t.render(c) 'Hello, John Smith.' # 点语法也可以用来引用对象的方法。 例如,每个 Python 字符串都有 upper() 和 isdigit() # 方法,你在模板中可以使用同样的句点语法来调用它们: >>> from django.template import Template, Context >>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}') >>> t.render(Context({'var': 'hello'})) 'hello -- HELLO -- False' >>> t.render(Context({'var': '123'})) '123 -- 123 -- True' # 注意这里调用方法时并* 没有* 使用圆括号 而且也无法给该方法传递参数;你只能调用不需参数的 # 方法。
变量的过滤器(filter)的使用
语法格式: {{ obj|filter:param}}
default
如果一个变量是false或者为空,使用给定的默认值。否则,使用变量的值,例如:
{{ value|default:"nothing" }}
length
返回值的长度,它对字符串和列表都有起作用。例如:
{{ value|length }}
如果value是 ['a', 'b', 'c', 'd'],那么输出是4。
filesizeformat
将值格式化为一个“人类可读的”文件尺寸(例如:‘13kb’, ‘4.1mb’, ‘102bytes'’)
{{ value|filesizeformat }}
如果 value
是 123456789,输出将会是 117.7 MB
。
date
如果 value= datetime.datetime.now()
{{ value|date:"Y-m-d" }}
slice
如果 value ='hello world:'
{{ value|slice:"2:-1" }}
truncatechars
如果字符串字符多于指定的字符数量,那么会被截断。阶段的字符串将可以翻译的省略号序列(“...”)结尾。
参数:要截断的字符数
例如:
{{ value|truncatechars:9 }}
safe
Django的模板中会对HTML标签和JS等语法标签进行自动转义。原因显而易见,这样是为了安全。但是有的时候我们可能不希望这些HTML元素被转义。比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义由两种方法,如果是一个单独的变量我们可以通过过滤器“|safe” 的方式告诉Django这段代码是安全的不必转义。比如:
value="<a href="">点击</a>" {{ value|safe}}
用法:
# 1 add : 给变量加上相应的值 # # 2 addslashes : 给变量中的引号前加上斜线 # # 3 capfirst : 首字母大写 # # 4 cut : 从字符串中移除指定的字符 # # 5 date : 格式化日期字符串 # # 6 default : 如果值是False,就替换成设置的默认值,否则就是用本来的值 # # 7 default_if_none: 如果值是None,就替换成设置的默认值,否则就使用本来的值 #实例: #value1="aBcDe" {{ value1|upper }}<br> #value2=5 {{ value2|add:3 }}<br> #value3='he llo wo r ld' {{ value3|cut:' ' }}<br> #import datetime #value4=datetime.datetime.now() {{ value4|date:'Y-m-d' }}<br> #value5=[] {{ value5|default:'空的' }}<br> #value6='<a href="#">跳转</a>' {{ value6 }} {% autoescape off %} {{ value6 }} {% endautoescape %} {{ value6|safe }}<br> {{ value6|striptags }} #value7='1234' {{ value7|filesizeformat }}<br> {{ value7|first }}<br> {{ value7|length }}<br> {{ value7|slice:":-1" }}<br> #value8='http://www.baidu.com/?a=1&b=3' {{ value8|urlencode }}<br> value9='hello I am yuan'
4.2.2 标签(tag)的使用(使用大括号和百分比的组合来表示tag)
标签看起来像下面的格式。但是标签比变量更加复杂:一些在输出中创建文本,一些通过循环或者逻辑来控制流程,一些加载其后的变量将使用到的额外信息到模板中。一些标签需要开始和结束标签等等
{% tags %}
{% if %} 的使用
{% if %}标签计算一个变量值,如果是“true”,即它存在,不为空并且不是FALSE的boolean值,系统则会显示{% if %} 和{% endif %} 间所有内容
{% if num >= 100 and 8 %} {% if num > 200 %} <p>num大于200</p> {% else %} <p>num大于100小于200</p> {% endif %} {% elif num < 100%} <p>num小于100</p> {% else %} <p>num等于100</p> {% endif %} {% if %} 标签接受and,or或者not来测试多个变量值或者否定一个给定的变量 {% if %} 标签不允许同一标签里同时出现and和or,否则逻辑容易产生歧义,例如下面的标签是不合法的: {% if obj1 and obj2 or obj3 %}
{% for %}的使用
{% for %}标签运行你按顺序遍历一个序列中的各个元素,每次循环模板系统都会渲染{% for %}和{% endfor %} 之间的所有内容。
<ul> {% for obj in list %} <li>{{ obj.name }}</li> {% endfor %} </ul> #在标签里添加reversed来反序循环列表: {% for obj in list reversed %} ... {% endfor %} #{% for %}标签可以嵌套: {% for country in countries %} <h1>{{ country.name }}</h1> <ul> {% for city in country.city_list %} <li>{{ city }}</li> {% endfor %} </ul> {% endfor %} #系统不支持中断循环,系统也不支持continue语句,{% for %}标签内置了一个forloop模板变量, #这个变量含有一些属性可以提供给你一些关于循环的信息 1,forloop.counter表示循环的次数,它从1开始计数,第一次循环设为1: {% for item in todo_list %} <p>{{ forloop.counter }}: {{ item }}</p> {% endfor %} 2,forloop.counter0 类似于forloop.counter,但它是从0开始计数,第一次循环设为0 3,forloop.revcounter 4,forloop.revcounter0 5,forloop.first当第一次循环时值为True,在特别情况下很有用: {% for object in objects %} {% if forloop.first %}<li class="first">{% else %}<li>{% endif %} {{ object }} </li> {% endfor %} # 富有魔力的forloop变量只能在循环中得到,当模板解析器到达{% endfor %}时forloop就消失了 # 如果你的模板context已经包含一个叫forloop的变量,Django会用{% for %}标签替代它 # Django会在for标签的块中覆盖你定义的forloop变量的值 # 在其他非循环的地方,你的forloop变量仍然可用 #{% empty %} {{li }} {% for i in li %} <li>{{ forloop.counter0 }}----{{ i }}</li> {% empty %} <li>this is empty!</li> {% endfor %} # [11, 22, 33, 44, 55] # 0----11 # 1----22 # 2----33 # 3----44 # 4----55
{% csrf_token%} : csrf_token标签
用于生成csrf_token的标签,用于防治跨站攻击验证。注意如果你在view的index里用的是render_to_response方法,不会生效。
其实,这里只会生成一个input标签,和其他表单标签一起提交给后台。
{% url %}:引用路由配置的地址
<form action="{% url "bieming"%}" > <input type="text"> <input type="submit"value="提交"> {%csrf_token%} </form>
{% with %}:用更简单的变量名替代复杂的变量名
使用简单的名字缓存一个复杂的变量,当你需要使用一个“昂贵”的方法(比如访问数据库)很多次的时候非常有用
{% with total=fhjsaldfhjsdfhlasdfhljsdal %} {{ total }} {% endwith %}
{% verbatim %} 禁止render
{% verbatim %} {{ hello }} {% endverbatim %}
{% load %} :加载标签库
4.2.3 自定义filter和simple_tag
1,在app中创建templatetags模块(必须的)
2,创建任意.py文件,如my_tag.py
from django import template from django.utils.safestring import mark_safe register = template.Library() #register的名字是固定的,不可改变 @register.filter def filter_multi(v1,v2): return v1 * v2 @register.simple_tag def simple_tag_multi(v1,v2): return v1 * v2 @register.simple_tag def my_input(id,arg): result = "<input type='text' id='%s' class='%s' />" %(id,arg,) return mark_safe(result)
3,在使用自定义simple_tag 和filter的HTML文件中导入之前创建的my_tags.py:
{% load my_tags %}
4,使用simple_tag 和filter (如何调用)
-------------------------------.html {% load xxx %} #首行 # num=12 {{ num|filter_multi:2 }} #24 {{ num|filter_multi:"[22,333,4444]" }} {% simple_tag_multi 2 5 %} 参数不限,但不能放在if for语句中 {% simple_tag_multi num 5 %}
5,在settings的INSTALLED_APPS配置当前APP,不然Django无法找到自定义的simple_tag
注意:filter可以用在if等语句后,simpke_tag不可以
{% if num|filter_multi:30 > 100 %} {{ num|filter_multi:30 }} {% endif %}
4.2.4 extend模板继承
include模板标签
在讲解了模板加载机制之后,我们再介绍一个利用该机制的内建模板标签: {% include %} 。该标签允许在(模板中)包含其它的模板的内容。 标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串。 每当在多个模板中出现相同的代码时,就应该考虑是否要使用 {% include %} 来减少重复。
extend(继承)模板标签
到目前为止,我们的模板范例都只是些零星的 HTML 片段,但在实际应用中,你将用 Django 模板系统来创建整个 HTML 页面。 这就带来一个常见的 Web 开发问题: 在整个网站中,如何减少共用页面区域(比如站点导航)所引起的重复和冗余代码?
解决该问题的传统做法是使用 服务器端的 includes ,你可以在 HTML 页面中使用该指令将一个网页嵌入到另一个中。 事实上, Django 通过刚才讲述的 {% include %} 支持了这种方法。 但是用 Django 解决此类问题的首选方法是使用更加优雅的策略—— 模板继承 。
本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载。
让我们通过修改 current_datetime.html 文件,为 current_datetime 创建一个更加完整的模板来体会一下这种做法:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>The current time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>It is now {{ current_date }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html>
这看起来很棒,但如果我们要为 hours_ahead 视图创建另一个模板会发生什么事情呢?
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>Future time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html>
很明显,我们刚才重复了大量的 HTML 代码。 想象一下,如果有一个更典型的网站,它有导航条、样式表,可能还有一些 JavaScript 代码,事情必将以向每个模板填充各种冗余的 HTML 而告终。
解决这个问题的服务器端 include 方案是找出两个模板中的共同部分,将其保存为不同的模板片段,然后在每个模板中进行 include。 也许你会把模板头部的一些代码保存为 header.html 文件:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head>
你可能会把底部保存到文件 footer.html :
<hr> <p>Thanks for visiting my site.</p> </body> </html>
对基于 include 的策略,头部和底部的包含很简单。 麻烦的是中间部分。 在此范例中,每个页面都有一个<h1>My helpful timestamp site</h1> 标题,但是这个标题不能放在 header.html 中,因为每个页面的 <title> 是不同的。 如果我们将 <h1> 包含在头部,我们就不得不包含 <title> ,但这样又不允许在每个页面对它进行定制。 何去何从呢?
Django 的模板继承系统解决了这些问题。 你可以将其视为服务器端 include 的逆向思维版本。 你可以对那些不同 的代码段进行定义,而不是 共同 代码段。
第一步是定义 基础模板,该框架之后将由子模板所继承。 以下是我们目前所讲述范例的基础模板:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>My helpful timestamp site</h1> {% block content %}{% endblock %} {% block footer %} <hr> <p>Thanks for visiting my site.</p> {% endblock %} </body> </html>
这个叫做 base.html 的模板定义了一个简单的 HTML 框架文档,我们将在本站点的所有页面中使用。 子模板的作用就是重载、添加或保留那些块的内容。 (如果你一直按顺序学习到这里,保存这个文件到你的template目录下,命名为 base.html .)
我们使用模板标签: {% block %} 。 所有的 {% block %} 标签告诉模板引擎,子模板可以重载这些部分。 每个{% block %}标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖。
现在我们已经有了一个基本模板,我们可以修改 current_datetime.html 模板来 使用它:
{% extends "base.html" %} {% block title %}The current time{% endblock %} {% block content %} <p>It is now {{ current_date }}.</p> {% endblock %}
再为 hours_ahead 视图创建一个模板,看起来是这样的:
{% extends "base.html" %} {% block title %}Future time{% endblock %} {% block content %} <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> {% endblock %}
看起来很漂亮是不是? 每个模板只包含对自己而言 独一无二 的代码。 无需多余的部分。 如果想进行站点级的设计修改,仅需修改 base.html ,所有其它模板会立即反映出所作修改。
以下是其工作方式:
在加载 current_datetime.html 模板时,模板引擎发现了 {% extends %} 标签, 注意到该模板是一个子模板。 模板引擎立即装载其父模板,即本例中的 base.html 。此时,模板引擎注意到 base.html 中的三个 {% block %} 标签,并用子模板的内容替换这些 block 。因此,引擎将会使用我们在 { block title %} 中定义的标题,对 {% block content %} 也是如此。 所以,网页标题一块将由{% block title %}替换,同样地,网页的内容一块将由 {% block content %}替换。
注意由于子模板并没有定义 footer 块,模板系统将使用在父模板中定义的值。 父模板 {% block %} 标签中的内容总是被当作一条退路。继承并不会影响到模板的上下文。 换句话说,任何处在继承树上的模板都可以访问到你传到模板中的每一个模板变量。你可以根据需要使用任意多的继承次数。 使用继承的一种常见方式是下面的三层法:
<1> 创建 base.html 模板,在其中定义站点的主要外观感受。 这些都是不常 修改甚至从不修改的部分。 <2> 为网站的每个区域创建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。这些模板对base.html 进行拓展, 并包含区域特定的风格与设计。 <3> 为每种类型的页面创建独立的模板,例如论坛页面或者图片库。 这些模板拓展 相应的区域模板。
这个方法可最大限度地重用代码,并使得向公共区域(如区域级的导航)添加内容成为一件轻松的工作。
以下是使用模板继承的一些诀窍:
<1>如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。 否则,模板继承将不起作用。 <2>一般来说,基础模板中的 {% block %} 标签越多越好。 记住,子模板不必定义 父模板中所有的代码块,因此 你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行 (重)定义。 俗话说,钩子越多越好。 <3>如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的 某个 {% block %} 中。 如果你需要访问父模板中的块的内容,使用 {{ block.super }}这个标签吧,这一 个魔法变量将会表现出父模板中的内容。 如果只想在上级代码块基础上添加内容,而 不是全部重载,该变量就显得非常有用了。 <4>不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因 为block 标签的工作方式是双向的。 也就是说,block 标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充 的内容。如果模板中出现了两个相同名称的 {% block %} 标签,父模板将无从得知 要使用哪个块的内容。
python __str__(self)函数的用法
比如文中出现的例子:
class Author(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) def __str__(self): return self.name
当python中方法名如果是__XXXX__() 的,那么就有特殊的功能,因此叫做“魔法”方法,当使用print输出对象的时候,只要自己定义了__str__(self) 方法,那么就会打印从这个方法中return的数据。
如果要把一个类的实例变成str ,就需要实现特殊方法__str__() :
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def __str__(self): return '(Person: %s, %s)' % (self.name, self.gender)
下面举个例子:
现在,在交互式命令行下用 print 试试: >>> p = Person('Bob', 'male') >>> print p (Person: Bob, male) 但是,如果直接敲变量 p: >>> p <main.Person object at 0x10c941890> 似乎__str__() 不会被调用。
因为 python定义了__str__() 和 __repr__() 两种方法,__str__() 用于显示给用户,而__repr__() 用于显示给开发人员。
参考文献:https://www.cnblogs.com/feixuelove1009/p/8404054.html