个人学习笔记,参考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 from django.db import modelsclass Question (models.Model ): question_text = models.CharField(max_length=200 ) pub_date = models.DateTimeField('date published' ) 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 ) 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 from django.contrib import adminfrom .models import Questionadmin.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 from django.shortcuts import renderfrom django.shortcuts import HttpResponsedef 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 from django.urls import pathfrom . import viewsurlpatterns = [ 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')) """ from django.contrib import adminfrom django.urls import path, includeurlpatterns = [ path('admin/' , admin.site.urls), 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 from django.shortcuts import renderfrom django.shortcuts import HttpResponsedef 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 from django.shortcuts import renderfrom django.shortcuts import HttpResponsedef 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 from django.shortcuts import renderfrom .models import Questiondef 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 from django.shortcuts import renderfrom .models import Questionfrom django.shortcuts import HttpResponsedef 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 pathfrom . import viewsurlpatterns = [ path('' , views.index, name='index' ), 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 from django.shortcuts import get_object_or_404, renderfrom .models import Questionfrom django.shortcuts import HttpResponsedef 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 ): 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 from django.shortcuts import get_object_or_404, renderfrom .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 ): 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 ... 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 ... 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): 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 reversefrom django.shortcuts import HttpResponseRedirect
到此,简易的投票功能就已经实现了,不谈及样式问题,官方还有一个优化,就是使用通用视图。
十二、通用视图 改良可做可不做,下面的代码会少一点,但我觉得上面的代码更容易理解。
1、改良 URLconf polls/urls.py
1 2 3 4 5 6 7 8 9 10 11 from django.urls import pathfrom . import viewsapp_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 HttpResponseRedirectfrom django.shortcuts import get_object_or_404, renderfrom django.urls import reversefrom django.views import genericfrom .models import Choice, Questionclass 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 ): ...