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

转载于:https://www.cnblogs.com/olajennings/p/8661968.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值