Skip to main content

第一个 Django 案例

案例介绍

创建一个投票应用

在项目主目录下创建 apps 文件夹, 随后进入此目录执行 python manage.py startapp polls 创建一个 pools 的 APP

编写第一个视图

之后编写视图, 写入初步处理请求与响应的内容

polls/views.py
from django.http import HttpResponse

def index(request):
return HttpResponse("这是 JTZ 的投票站点")

同时为了可以调用对应的视图, 还需要编写 URLConf, 也就是路由配置, 创建 polls/urls.py 文件

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

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

之后, 需要在主项目的 urls.py 中配置 APP polls 的 URLConf 内容

project/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path("polls/", include('apps.polls.urls'))
]
include 介绍

在上面的代码中引入了 include , 这个相当于一个包含作用, 将对应的 APP 的 URLConf 包含到主项目的 URLConf 中

path() 介绍

在 Django 中路由配置模块其实就是一个 urlpatterns 列表, 其中的每个元素都是一个 path. 从下面可以看到 path() 可以接收四个参数

def _path(route, view, kwargs=None, name=None, Pattern=None):
  • route : 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项path开始,按顺序依次匹配列表中的项,直到找到匹配的项,然后执行该条目映射的视图函数或下级路由,其后的条目将不再继续匹配。因此,url路由执行的是短路机制,path的编写顺序非常重要!
  • view : 指的是处理当前url请求的视图函数。当Django匹配到某个路由条目时,自动将封装的HttpRequest对象作为第一个参数,被“捕获”的参数以关键字参数的形式,传递给该条目指定的视图view。
  • kwargs : 任意数量的关键字参数可以作为一个字典传递给目标视图。
  • name : 对你的URL进行命名,让你能够在Django的任意处,尤其是模板内显式地引用它。这是一个非常强大的功能,相当于给URL取了个全局变量名,不会将url匹配地址写死。

创建模型 (⭐⭐⭐⭐⭐)

模型(Model) 本质上是数据库标的布局, 以及一些附加的元数据信息.

Django 通过自定义 Python 类的形式来定义具体的模型, 每个模型的物理存在方式就是一个Python的类Class,每个模型代表数据库中的一张表,每个类的实例代表数据表中的一行数据,类中的每个变量代表数据表中的一列字段。Django通过模型,将Python代码和数据库操作结合起来,实现对SQL查询语言的封装。也就是说,你可以不会管理数据库,可以不会SQL语言,你同样能通过Python的代码进行数据库的操作,这就是所谓的ORM。Django通过ORM对数据库进行操作,奉行代码优先的理念,将Python程序员和数据库管理员进行分工解耦。

在此次投票应用中, 需要创建两个模型 QuestionChoice

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

class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)

对于上面的模型来进行简单的介绍,

  1. 每一个类都是django.db.models.Model的子类
  2. 每一个字段都是Field类的一个实例
  3. 每一个Field实例的名字就是数据库字段的名字

在创建完成模型之后, 接下来需要进行一些配置, 并交付给 Django 进行自动化的操作, 首页需要先在项目中注册对应的 APP

project/settings.py
INSTALLED_APPS = [
//....
'apps.polls.apps.PollsConfig'
]

设置成功后, Django已经知道了投票应用的存在了,并把它加入了项目大家庭。随后运行下面的命令

(.venv) PS C:\Users\14894\Desktop\Django\oneDjango> py.exe .\manage.py makemigrations polls
(.venv) PS C:\Users\14894\Desktop\Django\oneDjango> py.exe .\manage.py migrate

视图和模板

在 Django 中, 网页和其它的一些内容都是通过视图来处理的。视图其实就是一个简单的Python函数(在基于类的视图中称为方法)。Django通过对比请求的URL地址来选择对应的视图,也就是路由。

编写视图

之后在 polls 中编写对应的视图函数 (PS : 注意函数的参数信息)

pills\urls.py
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/views.py
urlpatterns = [
path("", views.index, name='index'),

# 例如: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),

# 例如: /polls/5/results/
path('<int:question_id>/results/', views.results, name='results'),

# 例如: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]

注意这里的路由对应匹配路径使用了 <int:question_id> 这个是匹配数字冰将其赋值为函数中的 question_id 参数

编写 Index 视图

下面是一个新的index视图,用于替代先前无用的index,它会根据发布日期显示最近的5个投票问卷。

polls\views.py
from django.http import HttpResponse

from .models import Question


def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([q.question_text for q in latest_question_list])
return HttpResponse(output)
# 省略了那些没改动过的视图(detail, results, vote)

这里有个非常重要的问题:在当前视图中的HTML页面是硬编码的。如果你想改变页面的显示内容,就必须修改这里的Python代码。为了解决这个问题,需要使用Django提供的模板系统,解耦视图和模板之间的硬连接。

我们需要在 polls 目录下创建一个新的 templates 目录,Django会在它里面查找模板文件。

项目settings.py文件中的 TEMPLATES配置项描述了 Django 如何载入和渲染模板。默认的设置文件设置了 DjangoTemplates 后端作为模板引擎,并将 APP_DIRS设置成了 True。这一选项将会让 DjangoTemplates 在每个 INSTALLED_APPS 文件夹中寻找 "templates" 子目录。

在刚才创建的templates目录中,再创建一个新的子目录名叫polls,进入该子目录,创建一个新的HTML文件index.html。换句话说,你的模板文件应该是polls/templates/polls/index.html。因为 Django 会寻找到对应的app_directories ,所以你只需要使用polls/index.html就可以引用到这一模板了。

polls/templates/polls/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</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>No polls are available.</p>
{% endif %}

</body>
</html>

同时修改视图文件 polls/views.py ,让新的index.html文件生效:

polls\views.py
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))

上面的代码会加载polls/index.html文件,并传递给它一个参数。这个参数是一个字典,包含了模板变量名和Python对象之间的映射关系。

上面这种模式在实际应用中会比较繁琐, 因为加载模板、传递参数,返回HttpResponse对象是一整套再常见不过的操作了,为了节省力气,Django提供了一个快捷方式:render函数,一步到位!

polls/views.py
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, 'polls/index.html', context)

render()函数的第一个位置参数是请求对象(就是view函数的第一个参数),这个参数是固定写法,不需要变动。第二个位置参数是模板文件。还可以有一个可选的第三参数,一个字典,包含需要传递给模板的数据。最后render函数返回一个经过字典数据渲染过的模板封装而成的HttpResponse对象。

404 错误

对于 404 错误, Django 也提供了一个默认的方式, 可以帮助快速实现

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()方法,和上面的get_object_or_404()类似,只不过是用来替代filter()函数,当查询列表为空时弹出404错误。

使用模板系统

在 detail 视图中, 向模板传递了上下文变量 question 。下面是 polls/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对象中尝试查找一个字典,如果失败,则尝试查找属性,如果再失败,则尝试作为列表的索引进行查询。

{% for %} 循环中的方法调用—— question.choice_set.all 其实就是Python的代码 question.choice_set.all(),它将返回一组可迭代的Choice对象,并用在 {% for %}标签中。

删除模板中硬编码的URLs

在polls/index.html文件中,还有一部分硬编码存在,也就是href里的“/polls/”部分:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

它对于代码修改非常不利。设想如果你在urls.py文件里修改了路由表达式,那么你所有的模板中对这个url的引用都需要修改,这是无法接受的!

我们前面给urls定义了一个name别名,可以用它来解决这个问题。具体代码如下:

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

Django会在polls.urls文件中查找name='detail'的路由,具体的就是下面这行:

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

举个栗子,如果你想将polls的detail视图的URL更换为polls/specifics/12/,那么你不需要在模板中重新修改url地址了,仅仅只需要在polls/urls.py文件中,将对应的正则表达式改成下面这样的就行了,所有模板中对它的引用都会自动修改成新的链接:

# 添加新的单词'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),

URL names的命名空间

本教程例子中,只有一个app,也就是polls,但是在现实中很显然会有5个、10个、更多的app同时存在一个项目中。Django是如何区分这些app之间的URL name呢?举个例子,polls 应用有 detail 视图,可能另一个博客应用也有同名的视图。Django 如何知道 {% url %} 标签到底对应哪一个应用的 URL 呢?

答案是使用URLconf的命名空间。可以在polls/urls.py文件的开头部分,添加一个app_name的变量来指定该应用的命名空间:

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 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

表单和类视图