个人学习笔记,参考django官方文档:https://docs.djangoproject.com/zh-hans/3.2/

一、First Django APP

  上篇笔记从零创建了一个django项目,一个项目肯定是由多个模块组成的,比方说一个电商项目,包括支付模块,商品展示模块,商家用户聊天模块等等,这些模块便可称之为一个应用。

  本篇笔记继续跟着django官方文档创建了一个投票的应用。

  它由两部分组成:

  • 一个让人们查看和投票的公共站点。
  • 一个让你能添加、修改和删除投票的管理站点。

  在项目目录下,执行下面这行命令,创建一个应用。

1
python manage.py startapp polls

  此时app目录如下,各文件的作用在使用中揭晓。

1
2
3
4
5
6
7
8
9
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py

二、挂载 app

  app创建完成,需要把app加载到整个项目上。

  为了避免项目名称和其他的一些模块名冲突,可以将'polls',写成,polls.apps.PollsConfig,注意格式,字符串列表需要用,分开。

settings.py

1
2
3
4
5
6
7
8
9
10
INSTALLED_APPS = [
'polls',

'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]

三、编写model层

  model层,是Django的数据管理层,负责和数据库交互,编写model层就是设计应用所需要的数据表,得益于Django的ORM模块,我们不必写SQL语句就可以操作数据表。

  model层里的一个类就是一个数据表,一行就是一个对象,一列就是一个对象的一个属性。

polls/models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# django框架的接口模块,默认已经引入
from django.db import models


# Create your models here.

# 创建了一个“问题”类(表),表里有两个字段。
class Question(models.Model):
# 问题描述字段
question_text = models.CharField(max_length=200)
# 创建日期字段
pub_date = models.DateTimeField('date published')

# python魔法方法,显示调用该对象的时候的返回内容
def __str__(self):
return self.question_text


# 创建了一个选项类(表),表中包含三个字段。
class Choice(models.Model):
# 这个表里定义了一个外键字段,因为一个问题可以有多个选项,每个问题对应每个问题的选项。
question = models.ForeignKey(Question, on_delete=models.CASCADE)
# 选项描述字段
choice_text = models.CharField(max_length=200)
# 是否选取字段
votes = models.IntegerField(default=0)

# python魔法方法,显示调用该对象的时候的返回内容
def __str__(self):
return self.choice_text

  model层写好了,数据表创建好了吗,并没有,我们还需要执行以下命令。

生成中间文件

1
python manage.py makemigrations polls

日志如下

1
2
3
polls\migrations\0001_initial.py
- Create model Question
- Create model Choice

  django为我们生成了一个0001_initial.py,他是一个中间文件,执行迁移数据库的命令后,django会依赖这个文件去帮我们创建数据库表。

迁移数据

1
python manage.py migrate

日志如下

1
2
3
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
Applying polls.0001_initial... OK

  查看生成的表,撒花。

1、使用Django可视化管理数据

  表已经创建好了,如何使用django自带的后台可视化管理数据呢,这需要我们注册一下。(选项类也要注册到里面去,后面会用到)

polls/admin.py

1
2
3
4
5
6
7
8
9
# django的接口,默认已经引入。
from django.contrib import admin

# 引入我们自己写的数据表(类)
from .models import Question

# 注册以下就可以交给django后台管理啦!
# Register your models here.
admin.site.register(Question)

2、启动项目

  重新启动下项目,看下Django后台可不可以编辑我们的数据表。

1
2
python manage.py runserver

进入:http://127.0.0.1:8000/admin/

四、编写view层

  数据我们已经创建好了,也可以进行基本的CRUD操作了,接下来就是前端显示了对吧。view就是干这个的。

  view层可以和数据库交互,处理前端显示,但有些东西是不需要和数据库交互就可以显示的。

  所以我们先写一个Hello World展示在前端,然后再和model层结合,写两个吧,方便引入路由器的概念。

polls/views.py

1
2
3
4
5
6
7
8
9
10
11
12
# # Django的接口,默认已引入,这是一个渲染的函数,后面经常用。
from django.shortcuts import render
# 引入 HttpResponse 来处理HTTp响应
from django.shortcuts import HttpResponse


def index(request):
return HttpResponse("<h1>Hello world!</h1>")


def hello(request):
return HttpResponse("<h1>Hello Django!</h1>")

  写了两个视图函数,我们需要把他显示在前端,但是有两个,我们改如何分别显示他们呢?

  路由器。

五、编写路由器

  类比于电脑前的路由器,电脑前的路由器是把局域网内的(WIFI信号内)的网络请求,打包成一个数据包发出去的,然后再把响应发回来。

  同理,我们配置下Django的路由,就可以让每个视图,各回各家,各找各妈了。

polls 目录下创建一个新的文件urls.py

1
2
3
4
5
6
7
8
9
10
11
12
13

# path 就是处理路径的一个模块
from django.urls import path

# 引入我们自己写的视图函数,不引入的话路由器怎么知道改管理谁的视图呢?
from . import views

# 配置路由规则,各回各家,各找各妈!
urlpatterns = [
path('index', views.index, name='index'),
path('hello', views.hello, name='hello'),
]

1、挂载路由

  路由管理我们写好了,但是这只是一个app的路由管理,整个项目是还需要一个路由管理的,所以我们需要将我们写的这个应用的路由文件挂载到项目的路由管理上去。

mysite/urls.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"""mysite URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
# django的后台管理模块,默认已经引入
from django.contrib import admin
# 路径处理模块,默认已经引入path,我们需要引入一个include模块,因为我们要引入app自己的路由规则
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
# 引入polls应用的url路由配置文件。
path('polls/', include('polls.urls')),
]

2、启动项目

1
python manage.py runserver

  这时候我们可以启动项目,查看下我们的路由和视图是否可以正常工作。按照我们编写的逻辑。

  访问http://127.0.0.1:8000/polls/index 会显示Hello world!,并且是一级标题

  访问http://127.0.0.1:8000/polls/hello 会显示Hello Django!,同样是一级标题

  很好,项目正常运行了!

六、编写模板

  从<h1></h1>一级标题可以正常显示我们知道,我们完全可以return一个HTML文档,当然我们最好把它分离出来,因为写在这个文件里不方便修改,也有点臃肿。

  在polls根目录下创建templates文件夹,然后在此目录创建一个index.html文件。

templates/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>Hello Django!</h1>
</body>
</html>

更改视图函数

polls/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
# Django的接口,默认已引入,这是一个渲染的函数,后面经常用。
from django.shortcuts import render
# 引入 HttpResponse 来处理HTTp响应
from django.shortcuts import HttpResponse


def index(request):
return HttpResponse("<h1>Hello world!</h1>")


def hello(request):
return render(request, 'index.html',context=None)

  测试,程序正常。

  因为我们这里用到了index.html文件,使用render函数更加方便。

  但有个小问题,就是我们的内容是写死在HTML文件的,我们有办法让HTML的文件跟着我们的程序改变吗,有的,就是Django的插值语法。

  我们把<h1>标签里的内容改成。

1
2
3
...
<h1>Hello {{name}}!</h1>
...

  name就是一个变量,随着变量值的不同,HTML就响应不同的内容了。

  在视图函数里加入name变量。

polls/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
# # Django的接口,默认已引入,这是一个渲染的函数,后面经常用。
from django.shortcuts import render
# 引入 HttpResponse 来处理HTTp响应
from django.shortcuts import HttpResponse


def index(request):
return HttpResponse("<h1>Hello world!</h1>")


def hello(request):
name = "小孙同学"
return render(request, 'index.html', context={'name': name})

  context就是变量和模板交接的东西。专业名称叫做上下文

  测试一下,我们可以写动态的数据啦!

七、写点有用的视图

  上面虽然写了两个视图,打通了前端,但是没有和后端交互。把我们写的视图注释掉,继续跟着官方文档写点有用的视图吧。

polls/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Django自带的渲染函数,默认已经引入
from django.shortcuts import render
# 引入我们写的数据表
from .models import Question

# 写了一个和后端交互的视图
def index(request):
# 按照时间逆序排列,查询前五个数据表中存在的投票,存储为列表
latest_question_list = Question.objects.order_by('-pub_date')[:5]
# 上下文
context = {
'latest_question_list': latest_question_list
}
# 返回渲染好的页面
return render(request, 'index.html', context)

templates/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!--先判断是否存在数据,存在则遍历输出,否则返回不存在-->
{% 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>没有可用的投票。</p>
{% endif %}
</body>
</html>

  重启应用。
访问http://127.0.0.1:8000/polls/

访问http://127.0.0.1:8000/admin/为应用增加加几个投票。

访问http://127.0.0.1:8000/polls/正常显示

八、路由分发的进阶

  上面写了几个投票,他是一个超链接, href值指向的是这个投票对应的详情。所以我们需要定义一个新的视图函数detail(名字自拟)来显示详情。
polls/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Django自带的渲染函数,默认已经引入
from django.shortcuts import render
# 引入我们写的数据表
from .models import Question

from django.shortcuts import HttpResponse


# 写了一个和后端交互的视图
def index(request):
# 按照时间逆序排列,查询前五个数据表中存在的投票,存储为列表
latest_question_list = Question.objects.order_by('-pub_date')[:5]
# 上下文
context = {
'latest_question_list': latest_question_list
}
# 返回渲染好的页面
return render(request, 'index.html', context)


# 显示详情函数
def detail(request, question_id):
return HttpResponse("你在看问题 %s。" % question_id)

  定义一个新的视图后,我们需要为这个视图配置url的规则。

  但是,按照上述的设计,每个详情都应该对应一个URl,那我们是不是要写好多URL

  可以是也可以不是,因为这些URL是有规律的,路由分发的路径可以用公式代替。

polls/urls.py

1
2
3
4
5
6
7
8
9
10
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'),
]

  重启项目,点进去相应的问题,可以显示内容了,虽然这里只是显示问题几。

九、去除模板中的硬编码 URL

1
2
3
...
<a href="/polls/{{ question.id }}/">
...

  记得这行代码吧,就是写在index.html里的,这里有个问题,这个路径是写死的,一旦我们改了路由地址,我们还需要动模板里面的href值,这样不好。

  这时候path()函数的第三个参数就起作用了。

polls/urls.py

1
path('<int:question_id>/', views.detail, name='detail'),

  把模板这样写

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

  但这样还有一个问题,我们的URL最终是给项目使用的,但一个项目里有多个应用,名字有重名怎么办,所以这就需要我们为应用加一个名字。

poll/urls.py

1
2
3
...
app_name = 'polls'
...

  把模板这样写

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

十、显示详情

  当我们点进去一个投票后,可以进入该投票的详情页,然后对其进行选择,之前我们仅仅显示了你在看问题几,现在是时候该细化了。

  首先,我们在后台为这个投票加几个数据。然后在模板文件夹里创建一个模板detail.html,显示详情。

新建模板
detail.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>详情</title>
</head>
<body>
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
</body>
</html>

更新视图
polls/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Django自带的渲染函数,默认已经引入
from django.shortcuts import get_object_or_404, render
# 引入我们写的数据表
from .models import Question

from django.shortcuts import HttpResponse


# 写了一个和后端交互的视图
def index(request):
# 按照时间逆序排列,查询前五个数据表中存在的投票,存储为列表
latest_question_list = Question.objects.order_by('-pub_date')[:5]
# 上下文
context = {
'latest_question_list': latest_question_list
}
# 返回渲染好的页面
return render(request, 'index.html', context)


# 显示详情函数
def detail(request, question_id):
# 404异常处理
question = get_object_or_404(Question, pk=question_id)
return render(request, 'detail.html', {'question': question})

  404异常处理需要引用一个,get_object_or_404,emmm,雀食,多写异常处理是好事。
测试一下。

十一、编写表单

  我们已经写好的详情页需要显示的内容,但这毕竟是一个选择题,这样写只能展示,但用户没法选择,所以我们需要写一个表单。

detail.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>详情</title>
</head>
<body>
<form 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>
{% endfor %}
</fieldset>
<input type="submit" value="提交">
</form>
</body>
</html>

  运行项目,表单可以正常显示。

  但是这只是前端的表单,我们是需要提交数据到数据库的,目前的这个提交按钮形同虚设,所以我们需要定义一个新的视图函数,然后添加到表单的action属性上。这个视图函数负责接受表单里的数据,然后将其添加到数据库中。

polls/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Django自带的渲染函数,默认已经引入,404处理函数。
from django.shortcuts import get_object_or_404, render
# 引入我们写的数据表
from .models import *


# 写了一个和后端交互的视图
def index(request):
# 按照时间逆序排列,查询前五个数据表中存在的投票,存储为列表
latest_question_list = Question.objects.order_by('-pub_date')[:5]
# 上下文
context = {
'latest_question_list': latest_question_list
}
# 返回渲染好的页面
return render(request, 'index.html', context)


# 显示详情函数
def detail(request, question_id):
# 404异常处理
question = get_object_or_404(Question, pk=question_id)
return render(request, 'detail.html', {'question': question})

# 处理投票结果
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
# 重新显示问题投票表单。
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "您没有选择任何一个选项。",
})

为其配置url规则
polls/urls.py

1
2
3
4
...
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
...

  当我们选择了选项,下面就是显示投票结果了,所以我们还需要定义一个视图,来显示投票结果。

1
2
3
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'results.html', {'question': question})

  我们需要定义显示结果的模板文件。
results.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>答案</title>
</head>
<body>
<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 %}">再次投票?</a>
</body>
</html>

  为其匹配路由规则。
polls/urls.py

1
2
3
4
...
# ex: /polls/5/results/
path('<int:question_id>/results/', views.results, name='results'),
...

  当我们投完票后,我们就可以指定相应的路由地址来查看投票结果了。

  但是这样非常不智能,我们的需求是,当我们投完票后就可以自动跳转到相应的路由地址查看投票结果。

  我们需要在投完票后添加这个功能,所以我们需要更改vote这个视图函数来实现这个功能。
polls/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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, 'detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

  值得注意的是,因为我们这里引用了HttpResponseRedirect()reverse()函数,所以我们需要从相应的模块里引入他们。

1
2
from django.urls import reverse
from django.shortcuts import HttpResponseRedirect

  到此,简易的投票功能就已经实现了,不谈及样式问题,官方还有一个优化,就是使用通用视图。

十二、通用视图

  改良可做可不做,下面的代码会少一点,但我觉得上面的代码更容易理解。

1、改良 URLconf

polls/urls.py

1
2
3
4
5
6
7
8
9
10
11
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>

2、改良视图

  在视图中,我们是通过get方法获取数据表中的数据然后赋值给一个变量(对象),但我们也可以直接把数据表搬到视图中,这应该就是所谓的通用视图吧。

  删除旧的 index, detail, 和 results 视图,并用 Django 的通用视图代替。
polls/urls.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 = 'index.html'
context_object_name = 'latest_question_list'

def get_queryset(self):
"""返回最近五个已发布的问题。"""
return Question.objects.order_by('-pub_date')[:5]


class DetailView(generic.DetailView):
model = Question
template_name = 'detail.html'


class ResultsView(generic.DetailView):
model = Question
template_name = 'results.html'


def vote(request, question_id):
... # 和之前一样,不用改。