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示例教程到这里结束。