笔记_编写你的第一个Django应用

以下内容是本人学习 Django 5.0官方介绍文档 的笔记

第一部分

创建项目

django-admin startproject mysite

这些目录和文件的用处是:

  • 最外层的 mysite/ 根目录只是你项目的容器, 根目录名称对 Django 没有影响,你可以将它重命名为任何你喜欢的名称。

  • manage.py: 一个让你用各种方式管理 Django 项目的命令行工具。你可以阅读 django-admin 和 manage.py 获取所有 manage.py 的细节。

  • 里面一层的 mysite/ 目录包含你的项目,它是一个纯 Python 包。它的名字就是当你引用它内部任何东西时需要用到的 Python 包名。 (比如 mysite.urls).

  • mysite/__init__.py:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。如果你是 Python 初学者,阅读官方文档中的 更多关于包的知识

  • mysite/settings.py:Django 项目的配置文件。如果你想知道这个文件是如何工作的,请查看 Django 配置 了解细节。

  • mysite/urls.py:Django 项目的 URL 声明,就像你网站的“目录”。阅读 URL调度器 文档来获取更多关于 URL 的内容。

  • mysite/asgi.py:作为你的项目的运行在 ASGI 兼容的 Web 服务器上的入口。阅读 如何使用 ASGI 来部署 了解更多细节。

  • mysite/wsgi.py:作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。阅读 如何使用 WSGI 进行部署 了解更多细节。

用于开发的简易服务器

python manage.py runserver or python manage.py runserver 80080 or python manage.py runserver 0.0.0.0:8000

关于这个简易服务器的完整信息可以在 runserver 文档中找到。

创建投票应用

项目 VS 应用

项目和应用有什么区别?应用是一个专门做某件事的网络应用程序——比如博客系统,或者公共记录的数据库,或者小型的投票程序。项目则是一个网站使用的配置和应用的集合。项目可以包含很多个应用。应用可以被很多个项目使用。

python manage.py startapp polls

编写第一个视图

# polls/view.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

URL映射

# polls/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
]

项目的URL映射

# 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),
]

函数 path() 具有四个参数,两个必须参数:routeview,两个可选参数:kwargsname

参数:route

route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。

这些准则不会匹配 GET 和 POST 参数或域名。例如,URLconf 在处理请求 https://www.example.com/myapp/ 时,它会尝试匹配 myapp/ 。处理请求 https://www.example.com/myapp/?page=3 时,也只会尝试匹配 myapp/

参数:view

当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest 对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。稍后,我们会给出一个例子。

参数:kwargs

任意个关键字参数可以作为一个字典传递给目标视图函数。本教程中不会使用这一特性。

参数:name

为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式。

第二部分

数据库配置

mysite/settings.py是项目配置文件,默认使用SQLite作为默认数据库,如果你想使用其他数据库,你需要安装合适的 database bindings ,然后改变设置文件中 DATABASES 'default' 项目中的一些键值:

  • ENGINE – 可选值有 'django.db.backends.sqlite3''django.db.backends.postgresql''django.db.backends.mysql',或 'django.db.backends.oracle'。其它 可用后端

  • NAME – 数据库的名称。如果你使用 SQLite,数据库将是你电脑上的一个文件,在这种情况下,NAME 应该是此文件完整的绝对路径,包括文件名。默认值 BASE_DIR / 'db.sqlite3' 将把数据库文件储存在项目的根目录。

如果你不使用 SQLite,则必须添加一些额外设置,比如 USERPASSWORDHOST 等等。想了解更多数据库设置方面的内容,请看文档:DATABASES

除了SQLite外,其它数据库需在使用前手动创建数据库

执行命令python manage.py migrate

migrate 命令只会为在 INSTALLED_APPS 里声明了的应用进行数据库迁移。

创建模型

# polls/models.py
from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")
    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):
    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

注意在最后,我们使用 ForeignKey 定义了一个关系。这将告诉 Django,每个 Choice 对象都关联到一个 Question 对象。Django 支持所有常用的数据库关系:多对一、多对多和一对一

激活模型

# mysite/settings.py
INSTALLED_APPS = [
		# ...
    "polls.apps.PollsConfig",
    ]

运行命令 python manage.py makemigrations polls

通过运行 makemigrations 命令,Django 会检测你对模型文件的修改(在这种情况下,你已经取得了新的),并且把修改的部分储存为一次 迁移

查看具体的迁移SQL语句 python manage.py sqlmigrate polls 0001

检查项目中的问题 python manage.py check

再次运行migrate完成修改 python manage.py migrate

改变模型需要3个步骤

编辑 models.py 文件,改变模型。

运行 python manage.py makemigrations 为模型的改变生成迁移文件。

运行 python manage.py migrate 来应用数据库迁移。

数据库API

from polls.models import Choice, Question
from django.utils import timezone
current_year = timezone.now().year

Question.objects.all()
#<QuerySet [<Question: What's up?>]>

q = Question(question_text="What's new?", pub_date=timezone.now())
# <QuerySet [<Question: What's up?>]>

q.save()
q.id 
# 1

q.question_text 
# "What's new?"

q.pub_date 
# datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=datetime.timezone.utc)

q.question_text = "What's up?"
q.save()

Question.objects.get(pk=1) # shortcut for primary-key exact lookups.
# <Question: What's up?>

q.was_published_recently()
# True

q.choice_set.all() # 查找关联到q这个Question的所有choice集
# <QuerySet []>

q.choice_set.create(choice_text="Not much", votes=0) # 创建一个关联到q到choice类数据
# <Choice: Not much>

q.choice_set.count()
# 1

Choice.objects.filter(question__pub_date__year=current_year)
# <QuerySet [<Choice: Not much>]>

c = q.choice_set.filter(choice_text__startswith="Not much")
c.delete()

Django管理界面

管理界面不是为了网站的访问者,而是为管理者准备的

创建管理员账户

python manage.py createsuperuser

访问管理后台

http://127.0.0.1:8000/admin/

如果你设置了 LANGUAGE_CODE,登录界面将显示你设置的语言(如果 Django 有相应的翻译)

向管理界面注册Question模型类

# polls/admin.py
from django.contrib import admin
from .models import Question

admin.site.register(Question)

创建模板

ref: 模板指南

默认的设置文件设置了 DjangoTemplates 后端,并将 APP_DIRS 设置成了 True。这一选项将会让 DjangoTemplates 在每个 INSTALLED_APPS 文件夹中寻找 “templates” 子目录。这就是为什么尽管我们没有像在第二部分中那样修改 DIRS 设置,Django 也能正确找到 polls 的模板位置的原因。

虽然我们现在可以将模板文件直接放在 polls/templates 文件夹中(而不是再建立一个 polls 子文件夹),但是这样做不太好。Django 将会选择第一个匹配的模板文件,如果你有一个模板文件正好和另一个应用中的某个模板文件重名,Django 没有办法 区分 它们。我们需要帮助 Django 选择正确的模板,最好的方法就是把他们放入各自的 命名空间 中,也就是把这些模板放入一个和 自身 应用重名的子文件夹里。

创建文件: polls/templates/polls/index.html

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <!--<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>-->
        <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

为了让教程看起来不那么长,所有的模板文件都只写出了核心代码。在你自己创建的项目中,你应该使用 完整的 HTML 文档 。

第三部分

视图

每一个视图表现为一个 Python 函数

Django 将会根据用户请求的 URL 来选择使用哪个视图(更准确的说,是根据 URL 中域名之后的部分)。

URL 一般形式:/newsarchive/<year>/<month>/

Django 使用 URLconfs 将 URL 模式映射到视图。

Django 只要求视图返回的是一个 HttpResponse ,或者抛出一个异常。

# polls/view.py
from django.shortcuts import render
from django.http import HttpResponse
from django.http import Http404
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 = { # 传递一个上下文(context)。这个上下文是一个字典,它将模板内的变量映射为 Python 对象
        "latest_question_list": latest_question_list,
    }
    #return HttpResponse(template.render(context, request)) #等效于render
    return render(request, "polls/index.html", context) # return an HttpResponse object
    
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
		response = "You're looking at the results of question %s."
		return HttpResponse(response % question_id)
		
def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

把这些新视图添加进 polls.urls 模块里

# polls/urls.py
from django.urls import path
from . import views

urlpatterns = [
    # ex: /polls/
    path("", views.index, name="index"),
    # ex: /polls/5/
    path("<int:question_id>/", views.detail, name="detail"),
    # ex: /polls/5/results/
    path("<int:question_id>/results/", views.results, name="results"),
    # ex: /polls/5/vote/
    path("<int:question_id>/vote/", views.vote, name="vote"),
]

抛出404错误

更新polls/views.py

from django.http import Http404
#...
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()

# polls/views.py
from django.shortcuts import get_object_or_404, render
from .models import Question

# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/detail.html", {"question": question})

也有 get_list_or_404()函数, 如果列表为空的话会抛出 Http404 异常。

添加detail.html

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

在示例 {{ question.question_text }} 中,首先 Django 尝试对 question 对象使用字典查找(也就是使用 obj.get(str) 操作),如果失败了就尝试属性查找(也就是 obj.str 操作),结果是成功了。如果这一操作也失败的话,将会尝试列表查找(也就是 obj[int] 操作)

为URL名称添加命名空间

举个例子,polls 应用有 detail 视图,可能另一个博客应用也有同名的视图。Django 如何知道 {% url %} 标签到底对应哪一个应用的 URL 呢?

答案是:在根 URLconf 中添加命名空间。在 polls/urls.py 文件中稍作修改,加上 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/templates/polls/index.html 文件,从:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

修改为指向具有命名空间的详细视图:

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

第四部分

表单

 <!-- polls/templates/polls/detail.html -->

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
    <legend><h1>{{ question.question_text }}</h1></legend>
    {% if error_message %}
    <p><strong>{{ error_message }}</strong></p>
    {% endif %}
    
    {% 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 %}
</fieldset>
<input type="submit" value="Vote">
</form>

简要说明:

  • 上面的模板在 Question 的每个 Choice 前添加一个单选按钮。 每个单选按钮的 value 属性是对应的各个 Choice 的 ID。每个单选按钮的 name 是 "choice" 。这意味着,当有人选择一个单选按钮并提交表单提交时,它将发送一个 POST 数据 choice=# ,其中# 为选择的 Choice 的 ID。这是 HTML 表单的基本概念。

  • 我们将表单的 action 设置为 {% url 'polls:vote' question.id %},并设置 method="post"。使用 method="post" (而不是 method="get" )是非常重要的,因为提交这个表单的行为将改变服务器端的数据。当你创建一个改变服务器端数据的表单时,使用 method="post"。这不是 Django 的特定技巧;这是优秀的网站开发技巧。

  • forloop.counter 指示 for 标签已经循环多少次。

  • 由于我们创建一个 POST 表单(它具有修改数据的作用),所以我们需要小心跨站点请求伪造。 谢天谢地,你不必太过担心,因为 Django 自带了一个非常有用的防御系统。 简而言之,所有针对内部 URL 的 POST 表单都应该使用 {% csrf_token %} 模板标签。

更新polls/views.py

from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from .models import Choice, 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):
        # 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,)))
#..
  • request.POST 是一个类字典对象,让你可以通过关键字的名字获取提交的数据。 这个例子中, request.POST['choice'] 以字符串形式返回选择的 Choice 的 ID。 request.POST 的值永远是字符串。注意,Django 还以同样的方式提供 request.GET 用于访问 GET 数据 —— 但我们在代码中显式地使用 request.POST ,以保证数据只能通过 POST 调用改动。

  • 如果在 request.POST['choice'] 数据中没有提供 choice , POST 将引发一个 KeyError 。上面的代码检查 KeyError ,如果没有给出 choice 将重新显示 Question 表单和一个错误信息。

  • 在增加 Choice 的得票数之后,代码返回一个 HttpResponseRedirect 而不是常用的 HttpResponse 、 HttpResponseRedirect 只接收一个参数:用户将要被重定向的 URL(请继续看下去,我们将会解释如何构造这个例子中的 URL)。正如上面的 Python 注释指出的,在成功处理 POST 数据后,你应该总是返回一个 HttpResponseRedirect。这不是 Django 的特殊要求,这是那些优秀网站在开发实践中形成的共识。

  • 在这个例子中,我们在 HttpResponseRedirect 的构造函数中使用 reverse() 函数。这个函数避免了我们在视图函数中硬编码 URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。 在本例中,使用在 教程第 3 部分 中设定的 URLconf, reverse() 调用将返回一个这样的字符串:"/polls/3/results/"

我们的 vote() 视图代码有一个小问题。代码首先从数据库中获取了 selected_choice 对象,接着计算 vote 的新值,最后把值存回数据库。如果网站有两个方可同时投票在 同一时间 ,可能会导致问题。同样的值,42,会被 votes 返回。然后,对于两个用户,新值43计算完毕,并被保存,但是期望值是44。

这个问题被称为 竞争条件 。如果你对此有兴趣,你可以阅读 使用 F() 避免竞争条件 来学习如何解决这个问题。

使用通用视图

通用视图对常见模式进行了抽象,使您甚至不需要编写 Python 代码就能编写应用程序。例如, ListView 和 DetailView 通用视图分别抽象了 “显示对象列表 “和 “显示特定类型对象的详细页面 “的概念

更多关于通用视图的详细信息,请查看 通用视图的文档

修改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"),
]

请注意,第二和第三个模式的路径字符串中匹配模式的名称已从 <question_id> 变为 <pk> 。这是必要的,因为我们将使用 DetailView 通用视图来替换 detail() 和 results() 视图,它希望从 URL 获取的主键值称为 "pk"

修改polls/views.py删除旧的 indexdetail, 和 results 视图,并用 Django 的通用视图代替

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from .models import Choice, Question

class IndexView(generic.ListView):
    template_name = "polls/index.html"
    # 如果不指定template_name,则默认将会使用polls/question_list.html
    context_object_name = "latest_question_list"
    # 如果不指定context_object_name, 则默认将会使用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"
    # 如果不指定template_name,则默认将会使用polls/question_detail.html
    
    def get_queryset(self):
        """
        Excludes any questions that aren't published yet.
        """
        return Question.objects.filter(pub_date__lte=timezone.now())
    
class ResultsView(generic.DetailView):
    model = Question
    template_name = "polls/results.html"

def vote(request, question_id):
    ...  # same as above, no changes needed.

每个通用视图都需要知道它将对什么模型采取行动。这可以通过模型属性(在本例中,DetailView 和 ResultsView 的model = Question)或定义 get_queryset() 方法(如 IndexView 所示)来提供。

第五部分

测试

ref:Django中的测试

Django 应用的测试应该写在应用的 tests.py 文件里,测试系统会自动的在所有文件里寻找并执行以 test 开头的测试函数。

模型测试

# polls/tests.py
import datetime

from django.test import TestCase
from django.utils import timezone

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)

运行测试

python manage.py test polls

视图测试

Django 提供了一个供测试使用的 Client 来模拟用户和视图层代码的交互。我们能在 tests.py 甚至是 shell 中使用它。

# polls/tests.py

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_two_past_questions(self):
        """
        The questions index page may display multiple questions.
        """
        question1 = create_question(question_text="Past question 1.", days=-30)
        question2 = create_question(question_text="Past question 2.", days=-5)
        response = self.client.get(reverse("polls:index"))
        self.assertQuerySetEqual(
            response.context["latest_question_list"],
            [question2, question1],
        )
        
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)

第六部分

静态文件

Django 的 STATICFILES_FINDERS 设置包含了一系列的查找器,它们知道去哪里找到 static 文件。AppDirectoriesFinder 是默认查找器中的一个,它会在每个 INSTALLED_APPS 中指定的应用的子文件中寻找名称为 static 的特定文件夹

建议在每个应用目录下创建static/appname文件夹来存放该应该的静态文件。

比如:polls/static/polls/style.css

你可以在 Django 中以 polls/style.css 的形式引用此文件,类似你引用模板路径的方式

虽然可以直接放到polls/static文件夹下,Django 只会使用第一个找到的静态文件。如果你在其它应用中有一个相同名字的静态文件,Django 将无法区分它们。

我们需要指引 Django 选择正确的静态文件,而最好的方式就是把它们放入各自的 命名空间 。也就是把这些静态文件放入 另一个 与应用名相同的目录中,既**polls/static/polls/**

导入静态文件

<!-- polls/templates/polls/index.html -->
{% load static %}

<link rel="stylesheet" href="{% static 'polls/style.css' %}">

{% static %} 模板标签会生成静态文件的绝对路径。

添加一个背景图

# polls/static/polls/style.css
body {
    background: white url("images/background.png") no-repeat;
}

需在**polls/static/polls/images/**文件夹下存放background.png,url使用相对路径。

更多关于设置和框架的资料,参考 静态文件解惑 和 静态文件指南部署静态文件 介绍了如何在真实服务器上使用静态文件

第七部分

自定义后台表单

# 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)

使用字段集

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {"fields": ["question_text"]}),
        ("Date information", {"fields": ["pub_date"]}),
    ]

关联对象

class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3 # 提供3个choice的字段
        
class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {"fields": ["question_text"]}),
        ("Date information", {"fields": ["pub_date"], "classes": ["collapse"]}),
    ]
    inlines = [ChoiceInline

使用表格式

class ChoiceInline(admin.TabularInline):
    ...

更改显示列表

# polls/admin.py
class QuestionAdmin(admin.ModelAdmin):
    # ...
    list_display = ["question_text", "pub_date","was_published_recently"]
    list_filter = ["pub_date"]
    search_fields = ["question_text"] #后台使用 LIKE 来查询数据

# polls/models.py
class Question(models.Model):
    # ...
    [@admin](https://micro.blog/admin)(
        boolean=True,
        ordering="pub_date",
        description="Published recently?",
    )
    def was_published_recently(self):
    # ...

其它修改:变更页分页搜索框过滤器日期层次结构, 和 列标题排序

自定义后台界面和风格

新建项目级模板mysite/templates,并添加到设置中

# mysite/settings.py
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        #...    
     }
 ]

DIRS 是一个包含多个系统目录的文件列表,用于在载入 Django 模板时使用,是一个待搜索路径。

新建文件:mysite/templates/admin/base_site.html, 可粘贴默认的模板再进行修改。

修改站点名

{% block branding %}
<div id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a><div>
{% endblock %}

在一个实际工程中,你可能更期望使用 django.contrib.admin.AdminSite.site_header 来进行简单的定制.

自定义应用管理模板

我们的投票应用不是非常复杂,所以无需自定义后台模板。不过,如果它变的更加复杂,需要修改 Django 的标准后台模板功能时,修改 应用 的模板会比 工程 的更加明智。这样,在其它工程包含这个投票应用时,可以确保它总是能找到需要的自定义模板文件。

更多关于 Django 如何查找模板的文档,参见 加载模板文档

自定义后台主页

默认情况下,它展示了所有配置在 INSTALLED_APPS 中,已通过后台应用注册,按字母排序的应用。

需要自定义的模板是 admin/index.html,修改app_list 的模板变量,可以使用硬编码链接(链接至特定对象的管理页)替代使用这个变量

第八部分

安装Django调式工具栏

这是一个常用的第三方软件包Django Debug Toolbar, 由Jazzband开发

pip install django-debug-toolbar

具体的安装方法查看官方文档

通常第三方软件包安装后,需要添加到INSTALLED_APPS中,可能还需要添加到URLconf (urls.py) 中。

还有更多的第三方软件包,可以在这里搜索

下一步看什么

可以开始你的项目了,下面是相关文档供查找

References

设计理念

创建可复用的应用教程

编写你的第一个 Django 补丁

Djiano List