Django开发示例Polls App
https://www.djangoproject.com/start/
Creat Project
查看Django版本:
python -m django --version
进入到某个目录,创建工程文件夹(mysite):
django -admin startproject mysite
运行django的开发服务器,然后到浏览器输入http://127.0.0.1:8000/,就可以看到应用的运行情况:
python manage.py runserver
创建应用polls:
python manage.py startapp polls
Write First View
到polls/views.py敲下代码:
from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the polls index.")
为了调用这个view,需要将URL映射过去,在polls/urls.py敲下代码:
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ]
将URLconf指向polls.urls模块,于是到mysite/urls.py敲下代码:
from django.contrib import admin from django.urls import include, path urlpatterns = [ path('polls/', include('polls.urls')), path('admin/', admin.site.urls), ]
Database Setup
在mysite/setting.py设置
DATABASES‘default’:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql',#'django.db.backends.sqlite3', 'NAME': '<your database name for this app>',#os.path.join(BASE_DIR, 'db.sqlite3'), 'USER': 'root', 'PASSWORD': '<your password>', 'HOST': 'localhost' } }
TIME_ZONE:
TIME_ZONE = 'Asia/Shanghai'
输入命令:
python manage.py migrate
Creating Models
model就是数据库中的表,在polls/models.py中写入如下代码即可创建表:
class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)
Activating Models
为了把app(polls)包含到项目中,需要到mysite/setting.py的INSTALLED_APPS中添加(第一行):
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
现在运行命令将polls包含进去:
makemigrations命令告诉django你对models做了一些改变,并且希望将这些改变作为migration保存下来
python manage.py makemigrations polls
补充:migration其实就是磁盘上的一些文件,记录了你每一次对models做的改变,可以在polls/migrations/0001_initial.py查阅
然后再执行一次migrate:
python manage.py migrate
小结一下,改变models的三步:
1.在models.py里修改代码
2. 执行 python manage.py makemigration 将改变保存位migration
3.执行python manage.py migrate 将改变应用到数据库上
Playing With The API
打开Python shell,不直接用python来打开,是因为manage.py里有项目的一些环境变量:
python manage.py shell
使用django的API,这些API的作用相当于数据库SQL语句的增删改查:
>>> from polls.models import Question, Choice # 引入models >>> Question.objects.all() # 查询数据库中所有的question <QuerySet []> >>> from django.utils import timezone # 引入timezone >>> q = Question(question_text="What's new?", pub_date=timezone.now()) # 创建question >>> q.save() # 创建之后必须调用save()才能将其保存到数据库 >>> q.id # 直接通过一个点获取其属性 1 >>> q.question_text "What's new?" >>> q.pub_date datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>) >>> q.question_text = "What's up?" # 获取属性后直接赋值,即可改变属性值 >>> q.save() # 改变之后必须保存
在models里增加一些方法,不需要执行migrate,其中__str__()方法有助于在查询时显示更多信息:
from django.db import models from django.db import models from django.utils import timezone class Question(models.Model): # ... def __str__(self): return self.question_text def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1) class Choice(models.Model): # ... def __str__(self): return self.choice_text
更多的API用法,如满足一定条件的查询:
>>> Question.objects.all() # 现在就展示了更多信息(question_text)了 <QuerySet [<Question: What's up?>]> >>> Question.objects.filter(id=1) # 选id=1的question <QuerySet [<Question: What's up?>]> >>> Question.objects.filter(question_text__startswith='What') # 通过双下划线获取字段 <QuerySet [<Question: What's up?>]> >>> from django.utils import timezone >>> current_year = timezone.now().year >>> Question.objects.get(pub_date__year=current_year) <Question: What's up?> >>> Question.objects.get(pk=1) # pk和id是一样的 <Question: What's up?> >>> q = Question.objects.get(pk=1) >>> q.choice_set.all() # 获取这个question的全部choice <QuerySet []> >>> q.choice_set.create(choice_text='Not much', votes=0) # 创建choice <Choice: Not much> >>> q.choice_set.create(choice_text='The sky', votes=0) <Choice: The sky> >>> c = q.choice_set.create(choice_text='Just hacking again', votes=0) >>> q.choice_set.count() # 统计该question的choice的个数 3 >>> Choice.objects.filter(question__pub_date__year=current_year) # 双下划线获取字段 <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> >>> c = q.choice_set.filter(choice_text__startswith='Just hacking') # 删除对象 >>> c.delete()
更多内容:
1.获取相关对象 https://docs.djangoproject.com/en/2.0/ref/models/relations/
2.字段查找 https://docs.djangoproject.com/en/2.0/topics/db/queries/#field-lookups-intro
3.数据库API文档 https://docs.djangoproject.com/en/2.0/topics/db/queries/
Introducing The Django Admin
创建admin:
python manage.py createsuperuser
Start The Development Server
python manage.py runserver
进入http://127.0.0.1:8000/admin/
Make The Poll App Modifiable In The Admin
此时进入admin页面,发现还没有polls app。为了将其引入,到polls/admin.py添加代码:
from django.contrib import admin from .models import Question admin.site.register(Question)
然后刷新一下页面即可。
Write More Views
现在需要写更多的视图(views)来供浏览者使用我们的polls app。views其实就是一个函数,用来处理浏览者相应的请求,通过在polls/urls.py的urlpatterns中给view指定相应的url,我们就可以知道浏览者请求的内容是什么,从而给予相应的处理。
在urlpatterns中,之所以不需要写/polls/,是因为在mysite/url.py中的urlpatterns已经设置了path('polls/',include('polls.urls')),从而将其包含进来了。这就是为什么在官方tutorial中说到,“只有admin/不需要include(),其他都需要”。
好了,现在到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)
接着到polls/urls.py中添加相应的url:
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'), ]
这里说明一下:
1.<'int:question_id'>,后半部分question_id就是指定了这个变量的名字,从而在views中可以通过这个变量名字获取值,如在detail(request, question_id)中,就是通过question_id获取url传递过来的值的;
前半部分int:,用来获取和传递url的参数的,int是用来匹配0和正整数的,意思就是,规定了你这里输入的参数只能是0和正整数,如果你输入的是字母,就会报错,告诉你我们的app不存在这样的url,没法给你处理。
2.django的url路由机制是这样的:先找到urlpatterns,然后逐个元素比对,找到了对应的元素,就调用相应的view进行处理。
可以看看这个,说的还挺详细:https://www.cnblogs.com/xshan/p/8294896.html
Write Views That Actually Do Something
现在来丰富一下views的功能。我们希望在index,即首页显示最新的5个questions,因此要到polls/views.py修改index()。同时我们也发现,需要有个前端页面来展示,这时候就引入了templates。
需要注意的是,如果不同的app有相同的template名字,django是无法分辨哪一个template对应哪一个app,django只会匹配第一个template。因此,在polls里建立文件夹templates后,还需要在templates下再建立文件夹polls,从而可以让django区分开来。因此index.html的路径是:polls/templates/polls/index.html
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的index():
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会自动加载对应的template,然后将数据传过去。传过去的context是一个字典,在index.html那边,根据字典的key获取其value。
加载模板,填满context,然后返回HttpResponse,这是很常用的套路了,所以为了方便开发,django集成了一个render(),现重写polls/views.py的index()如下:
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 render(request,'polls/index.html',context)
现在来丰富一下detai.html。当请求的question不存在时,应该返回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 objects or 404的套路,所以django又集成了一个函数get_objects_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})
detail.html需要展示该question的text以及其所有choice,代码如下:
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul>
Removing Hardcoded URLs In Templates
还记得polls/urls.py的urlpatterns中设置的url中的name吗?现在派上用场了。在index.html中,要求点击question的时候,跳转到其对应的detail.html页面,原本在index.html跳转的时候URL是这么写的:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
为了改变这种硬编码,可以改写成:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
Namespacing URL Names
实际上,一个project中包含好几个app,那么当你使用{% url ‘detail’ %}的时候,django怎么知道你说的是哪个app的detail模板呢?
这可以通过在app/urls.py中添加app_name = ‘<app_name>’来解决,如:在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改成这样:
<a href="{% url 'polls:detail' question.id %}">{{question.question_text}}</a>
Write A Simple Form
现在我们来修改一下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>
值得说明一下的是:
1.每一个radio的value都和相对应的choice的id绑定了,当你点击提交表单按钮时,就将该radio的value,即其对应的choice的id发送过去。
2.每当你要提交表单修改数据时,用post方法。
3.forloop.counter表示了循环的次数
4.所有针对内部URL的POST表单都应使用{%csrf_token%}模板标记,这样可以避免跨站请求伪造(Cross Site Request Forgeries,CSRF)。
现在可以写polls/views.py中的vote()来处理投票了:
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): # 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,)))
值得说明一下的是:
1.request.POST是一个类似字典的对象,可以通过key获取value。在这里request.POST['choice']返回的是一个字符串,是选择的choice的id。request.POST的值类型永远是字符串。
2.如果没有投票就提交了表单,request.POST['choice']会返回一个KeyError。因此我们需要捕捉KeyError,然后返回错误信息给detail.html,这样detail就可以获取错误信息,然后重新显示。
3.学习一下人家重定向的写法,用法。
4.看到重定向中的reverse了吗?官方是这么解释的:“此功能有助于避免在视图功能中硬编码URL。 它给出了我们想要传递控制权的视图的名称以及指向该视图的URL模式的可变部分”。我没懂,先放着。
5.若要查阅更多的HttpRequest:https://docs.djangoproject.com/en/2.0/ref/request-response/
投完票后,就跳转到结果页面展示投票结果:
polls/views.py的results():
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>
Use Generic Views: Less Code Is Better
我们发现,其实在polls/views.py中,detail()和results()特别像,几乎一样,那么我们如何减少这种代码的冗余呢?
这时候就可以用到django提供的通用视图Generic Views。
接下来我们主要有三个改动:
1.更改URLconf
2.删除旧的,不需要的views
3.利用django的Generic Views,引入新的views
首先修改URLconf:
polls/urls.py
from django.urls import path from . import views app_name = 'polls' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'), path('<int:pk>/results/', views.ResultsView.as_view(), name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ]
注意第二和第三个pattern把question_id修改为pk了,这是因为DetailView希望从URL获取的主键值称为“pk”。
然后修改views:
polls/views.py
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): """Return the last five published questions.""" 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): ... # same as above, no changes needed.
使用ListView和DetailView的时候,他们都会自动为template和context_object_name命名,我们要用自己的命名就直接重载就行了。
关于更多generic views的细节,查看:https://docs.djangoproject.com/en/2.0/topics/class-based-views/
Introducing Automated Testing
现在要写一个测试,因为。。。反正一堆好处啦,找到bug,节省时间,最重要的是“Code without tests is broken by design”。
其实在Question.was_published_recently()中有个bug,就是,如果设置的question的pub_date是未来的时间的话,那么was_published_recently()也会返回true。但显然这是未来的question,应该返回false。
现在写个test来暴露这个bug:
polls/tests.py
import datetime from django.utils import timezone from django.test import TestCase from .models import Question class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): """ was_published_recently() returns False for questions whose pub_date is in the future. """ time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False)
运行这个test:
$ python manage.py test polls
这里解释一下django是如何运行test的:
1.python manage.py tests polls 在polls里找到tests
2.找到django.test.TestCase类的子类
3.为test创建一个专门的数据库
4.找到test的方法——这个方法名字开头是test
5.用test_was_published_recently_with_future_question创建一个pub_date是未来30天的Question实例
6.用assertIs()方法来判断,我们期望was_published_recently()返回的是false,但是返回的是true,于是assertIs()这个函数就会报错
接下来修复这个bug:
polls/models.py
def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now
然后现在再运行这个test,就不会报错了。
现在完善一下这个test:
polls/tests.py
def test_was_published_recently_with_old_question(self): """ was_published_recently() returns False for questions whose pub_date is older than 1 day. """ 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): """ was_published_recently() returns True for questions whose pub_date is within the last day. """ 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)
这里介绍一下django测试工具Client,这里从shell启动,然后配置一下测试的环境:
>>> from django.test.utils import setup_test_environment >>> setup_test_environment()
然后需要引入client类:
>>> from django.test import Client >>> # create an instance of the client for our use >>> client = Client()
准备好后,现在我们可以让client来做一些测试了:
>>> # get a response from '/' >>> response = client.get('/') Not Found: / >>> # we should expect a 404 from that address; if you instead see an >>> # "Invalid HTTP_HOST header" error and a 400 response, you probably >>> # omitted the setup_test_environment() call described earlier. >>> response.status_code 404 >>> # on the other hand we should expect to find something at '/polls/' >>> # we'll use 'reverse()' rather than a hardcoded URL >>> from django.urls import reverse >>> response = client.get(reverse('polls:index')) >>> response.status_code 200 >>> response.content b'\n <ul>\n \n <li><a href="/polls/1/">What's up?</a></li>\n \n </ul>\n\n' >>> response.context['latest_question_list'] <QuerySet [<Question: What's up?>]>
接下来完善一下我们的view:
目前,polls会把还未发布的question展示出来,现在来修复这个bug
polls/views.py的IndexView
def get_queryset(self): """ Return the last five published questions (not including those set to be published in the future). """ return Question.objects.filter( pub_date__lte=timezone.now() ).order_by('-pub_date')[:5]
这里的lte的意思是:less than or equal
测试我们的新view:
from django.urls import reverse def create_question(question_text, days): """ Create a question with the given `question_text` and published the given number of `days` offset to now (negative for questions published in the past, positive for questions that have yet to be published). """ time = timezone.now() + datetime.timedelta(days=days) return Question.objects.create(question_text=question_text, pub_date=time) class QuestionIndexViewTests(TestCase): def test_no_questions(self): """ If no questions exist, an appropriate message is displayed. """ response = self.client.get(reverse('polls:index')) self.assertEqual(response.status_code, 200) self.assertContains(response, "No polls are available.") self.assertQuerysetEqual(response.context['latest_question_list'], []) def test_past_question(self): """ Questions with a pub_date in the past are displayed on the index page. """ create_question(question_text="Past question.", days=-30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], ['<Question: Past question.>'] ) def test_future_question(self): """ Questions with a pub_date in the future aren't displayed on the index page. """ create_question(question_text="Future question.", days=30) response = self.client.get(reverse('polls:index')) self.assertContains(response, "No polls are available.") self.assertQuerysetEqual(response.context['latest_question_list'], []) def test_future_question_and_past_question(self): """ Even if both past and future questions exist, only past questions are displayed. """ create_question(question_text="Past question.", days=-30) create_question(question_text="Future question.", days=30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], ['<Question: Past question.>'] ) def test_two_past_questions(self): """ The questions index page may display multiple questions. """ create_question(question_text="Past question 1.", days=-30) create_question(question_text="Past question 2.", days=-5) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], ['<Question: Past question 2.>', '<Question: Past question 1.>'] )
这里还有个问题,就是,如果用户猜到正确的url,那么用户还是可以访问未来的question,因此我们要在DetailView添加一些限制条件:
class DetailView(generic.DetailView): ... def get_queryset(self): """ Excludes any questions that aren't published yet. """ return Question.objects.filter(pub_date__lte=timezone.now())
当然了,修改了DetailView,我们还要写个测试来证实这个功能和预期是一样的:
class QuestionDetailViewTests(TestCase): def test_future_question(self): """ The detail view of a question with a pub_date in the future returns a 404 not found. """ future_question = create_question(question_text='Future question.', days=5) url = reverse('polls:detail', args=(future_question.id,)) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_past_question(self): """ The detail view of a question with a pub_date in the past displays the question's text. """ past_question = create_question(question_text='Past Question.', days=-5) url = reverse('polls:detail', args=(past_question.id,)) response = self.client.get(url) self.assertContains(response, past_question.question_text)
最后关于测试,有以下几点是需要知道的:
1.写测试的时候,不要嫌多;测试中的冗余是好的。
2.分别为每个model或者view写test。
3.为每一组条件分别写一个测试方法。
4.测试方法的名字应该能描述其功能。
5.关于测试的更多信息:https://docs.djangoproject.com/en/2.0/topics/testing/
Customize Your App's Look And Feel
首先创建文件夹及文件:
polls/static/polls/style.css
li a { color: green; }
然后在polls/templates/polls/index.html最上面添加如下代码:
{% load static %} <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
现在再来添加一张图片(polls/static/polls/images/background.png):
polls/static/polls/style.css
body { background: white url("images/background.gif") no-repeat; }
Customize The Admin Form
改变Question的change页面的字段的顺序,原本是question_text在前,pub_date在后,现在反过来:
polls/admin.py
from django.contrib import admin from .models import Question class QuestionAdmin(admin.ModelAdmin): fields = ['pub_date', 'question_text'] admin.site.register(Question, QuestionAdmin)
现在只是两个字段,如果一堆字段,可以分块管理:
polls/admin.py
from django.contrib import admin from .models import Question class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {'fields': ['question_text']}), ('Date information', {'fields': ['pub_date']}), ] admin.site.register(Question, QuestionAdmin)
现在在admin页面还没法管理Choice,最简单的方法是在admin.py像注册Question一样注册Choice,但是这么的话,那么就得一个个得添加choice,很麻烦。我们可以把Choice的管理嵌入到Question里面去:
polls/admin.py
from django.contrib import admin from .models import Choice, Question class ChoiceInline(admin.StackedInline): model = Choice extra = 3 class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {'fields': ['question_text']}), ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), ] inlines = [ChoiceInline] admin.site.register(Question, QuestionAdmin)
还有个小问题,在Question的change页面,choice占了屏幕太多地方了,我们把它变小一点:
polls/admin.py
class ChoiceInline(admin.TabularInline): #...
把StackedInline替换成TabularInline,choice就能更紧凑一些。
在admin/question页面,仅仅展示了Question的question_text,如果能展示更多信息就更好了,同时还在admin页面加上了个filter和搜索栏:
polls/admin.py
class QuestionAdmin(admin.ModelAdmin): # ... list_display = ('question_text', 'pub_date', 'was_published_recently') list_filter = ['pub_date'] search_fields = ['question_text']
接下来官方文档说给was_published_recently()这个方法添加一些属性来达到优化性能的目的,其实我没太懂怎么就优化了性能:
class Question(models.Model): # ... def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now was_published_recently.admin_order_field = 'pub_date' was_published_recently.boolean = True was_published_recently.short_description = 'Published recently?'
admin页面上显示的Django administration太傻了,我们应该将其换成我们app的名字,我们可以通过重写模板文件来达到这个目的。
首先把配置文件的TEMPLATES的DIRS修改一下:
mysite/settings.py
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', ], }, }, ]
然后在polls/templates/下创建文件夹admin,然后把默认的模板文件base_site.html复制到polls/templates/admin/,再对模板文件修改。
默认模板文件在django的django/contrib/admin/templates/admin/里,如果不知道django在哪,可以:
$ python -c "import django; print(django.__path__)"
然后修改base_site.html修改一下:
{% block branding %} <h1 id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></h1> {% endblock %}
好啦,django示例教程到这里结束。