第一个 Django 应用
1. 创建项目
1.1 新建项目
首先新建一个项目,名为 mysite,命令如下:
django-admin startproject mysite # 或用 django-admin.py
运行成功,生成一些目录:
mysite/manage.py # 管理 Django 项目的命令行工具mysite/ # 包,包含项目__init__.pysettings.py # 配置文件urls.py # 路由文件wsgi.py # WSGI 接口,web 服务器进入点,提供底层网络通信功能,无需关心
1.2 启动服务器
python manage.py runserver # 默认以 8000 端口开启
python manage.py runserver 8080 # 指定端口
执行成功,看到输出如下信息:
在浏览器中访问 http://127.0.0.1:8000/
,看到以下信息,表示开启成功(Django2.x 以下版本不一样):
1.3 新建应用
现在我们新建一个应用(app),名为 polls,命令如下:
cd mysite # 切好到项目里面
python manage.py startapp polls
执行成功后,可以看到 mysite 中多了一个 polls文件夹,打开 polls,里面包含以下文件:
polls/__init__.pyadmin.py # Django 提供的后台管理程序apps.py migrations/ # 数据库表生成记录__init__.pymodels.py # 模型(与数据库相关)tests.py # 测试文件views.py # 视图(一个视图函数表示一个页面)
项目与应用的区别
- 一个项目可以有一个或多个应用
- 一个应用往往是用来实现某个功能,如:博客、日程管理系统等
- 一个应用可以属于多个项目
1.4 第一个视图
一个视图函数表示一个 Web 页面,在 polls/views.py
中编写:
from django.shortcuts import render, HttpResponsedef index(request):"""首页"""return HttpResponse('Is Ok!')
要调用视图,我们需要先配置 urlconf
,让 Django 找到我们的视图函数,在此之前我们先把 app 添加到 settings.py
中:
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','polls', # 最好空一行,以示区分
]
配置 urlconf
编写 mysite/urls.py
:
from django.contrib import admin
from django.urls import path, include
from polls import views # 导入视图函数urlpatterns = [path('admin/', admin.site.urls),path('index/', views.index, name='index'),
]
访问 http://127.0.0.1:8000/index/
,如果不出意外的话,会看到 Is Ok!
的字样~
多级路由
上面我们只创建了一个 app,因此 url 路径配置在项目 mysite/urls.py
中毫无影响,但是当有多个应用且有多个相同的名字的视图时,为了避免冲突,就需要用到多级路由了。
- 配置
mysite/urls.py
:
from django.contrib import admin
from django.urls import path, include # 引入 includeurlpatterns = [path('admin/', admin.site.urls),path('polls/', include('polls.urls')), # include 就相当于多级路由,它会将去掉 url 前面的正则,将剩余字符串传递给下一级路由,即 polls/urls.py 来判断
]
- 在应用 polls 目录下新建一个
urls.py
文件,配置如下:
from django.urls import path
from polls import views # 导入视图函数urlpatterns = [path('index/', views.index, name='index'),# url(r'^index/', views.index, name='index'), # django2.x 以前版本
]
那么访问地址将变成 http://127.0.0.1:8000/polls/index/
。
2. 模型和后台管理
2.1 数据库配置
在 Django 中模型即指数据库,Django 内置 SQLite 数据库,可以直接使用它。但是 SQLite 一般仅用来测试使用,实际开发中一般很少不会使用。如果要使用其他数据库,需要配置 settings
,并安装相应驱动,下面我们以 MySQL 为例。
常用数据库配置:
'django.db.backends.sqlite3',
'django.db.backends.postgresql',
'django.db.backends.mysql',
'django.db.backends.oracle',
- 设置
settings.py
DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'test', # 数据库名字,需要事先创建'USER': 'root', # 用户名'PASSWORD': '', # 密码'HOST': '', # 留空默认为 localhost,数据库主机名'PORT': '3306',}
}
- 安装 pymysql 模块
pip install pymysql
- 激活 MySQL,打开项目
mysite/__init__.py
文件,配置如下:
import pymysql
pymysql.install_as_MySQLdb()
时区和语言
Django 默认使用 UTC 时区,以及英文,我们可以将其修改为东八区和中文:
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
2.2 创建模型 Model
Django 通过 ORM(Object Relation Mapping)对象关系映射,以面向对象的方式去操作数据库,即使不懂 SQL 语句也可以操作数据库。
我们只需在模型中创建相应的 类以及字段即可,然后再执行命令,Django会自动帮我们生成数据表:
- 类:对应数据表名
- 字段:对应数据表的列
在此之前我们创建了一个投票应用 polls,现在我们将创建两个数据表:问题表 Question
(用来存储问题以及发布事件)、以及选择人们的选择表Choice
。
下面我们编写 polls/models.py
:
from django.db import modelsclass Question(models.Model): # 每个类必须继承 models.Model"""数据表:问题表"""question_text = models.CharField(max_length=2000) # 问题内容pub_date = models.DateTimeField('date published') # 发布日期class Choice(models.Model):"""数据表:选择表"""choice_text = models.CharField(max_length=200) # 选择votes = models.IntegerField(default=0) # 是否已经投票question = models.ForeignKey(Question, on_delete=models.CASCADE) # 外键关联
-
在上面有些字段我们指定了最长宽度
max_length
,这将限制其输入范围,非必须但是最好有所限制 -
另外我们通过外键(数据库内容)
ForeignKey
将两个表关联起来,也就是这两张表是一对多关系。 -
一个问题可以有多个选择,除此之外数据表间关联还有 一对一、以及多对多关系,后面讲详细介绍。
模型创建和数据迁徙
接下来就是创建模型,执行 python manage.py makemigrations polls
,会看到以下提示:
这表示在 polls\migrations\0001_initial.py
文件中创建相关模型记录,当我们对数据表操作时,会在上面有相应记录,保存在我们的电脑磁盘上面。
接着我们要将数据迁徙到真正的数据库中去,执行 python manage.py migrate
:
在 Pycharm 中打开 SQLite ,可以看到创建很多数据表:
Tips
- 创建模型时,我们不需要创建 id,Django 会自动帮我们创建
- 外键字段,Django会在其名字之上加上一个
_id
,表示与主表的 ID 进行关联 - Django 运行随时修改模型,只需按照以下三步走,即可不丢失数据
- 修改
models.py
- 执行
python manage.py makemigrations app_name
为改动创建迁徙记录 - 执行
python manage.py migrate
,将操作同步至数据库
- 修改
2.3 操作模型
上面我们通过相应命令创建了模型,那么我们该如何操作数据表中内容呢?Django为我们提供了一系列的 API,可以很方便地就能操作数据。
- 进入 Django 提供的 shell 交互环境
python manage.py shell
>>> from polls.models import Question, Choice # 导入模型类
>>> Question.objects.all() # 获取所有 question 对象
<QuerySet []> # 因为里面还没数据,所有是空的>>> from django.utils import timezone # 导入 Django 内置的 timezone 模块,获取时间,来自于依赖库 pytz
>>> q = Question(question_text="What's new?", pub_date=timezone.now()) # 创建 question 对象
>>> q.save() # 保存到数据库
>>> q.id # 通过对象属性调用方式,访问模型中字段的值
1
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2019, 2, 28, 8, 10, 18, 766500, tzinfo=<UTC>)# 修改字段的值,再保存
>>> q.question_text = "What's up?"
>>> q.save()# .all() 方式查询数据库中所有对象,这里是 question 对象
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
在上面我们访问 Question 中所有对象时,得到是一个 object
对象,这样显示很不友好,为此我们可以为模型添加一个 __str()__
方法,使其能够更具有可读性:
from django.db import models
import datetime
from django.utils import timezoneclass Question(models.Model):...def __str__(self):return self.question_text # 返回的是 question_text,而不是 objectclass Choice(models.Model):...def __str__(self):return self.choice_text
- 重新打开一个 shell,来看看其他 API
>>> from polls.models import Question, Choice
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]># 关键字查询 filter() 方法过滤 id=1
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]># 查询 question_text 以 What 开头的 question
>>> Question.objects.filter(question_text__startswith="What")
<QuerySet [<Question: What's up?>]># 导入 timezone
# 查询今年发布的问题
>>> from django.utils import timezone
>>> current_year = timezone.now().year # 获取今年时间:2019
>>> Question.objects.get(pub_date__year=current_year) # __year=2019
<Question: What's up?># 查询不存在的 ID,出现异常
>>> Question.objects.get(id=2)
Traceback (most recent call last):File "<console>", line 1, in <module>File "E:\Python_virtualenvs\for_django\lib\site-packages\django\db\models\manager.py", line 82, in manager_methodreturn getattr(self.get_queryset(), name)(*args, **kwargs)File "E:\Python_virtualenvs\for_django\lib\site-packages\django\db\models\query.py", line 399, in getself.model._meta.object_name
polls.models.Question.DoesNotExist: Question matching query does not exist.# pk 即 primary key 缩写,与 id 等同
>>> Question.objects.get(pk=1)
<Question: What's up?>>>> q = Question.objects.get(pk=1) # 创建 Question 对象
>>> q.choice_set.all() # 通过 数据表名_set.all() 方式获得与其关联的数据表的所有对象
<QuerySet []># 创建三个 choices
>>> q.choice_set.create(choice_text='Not much', votes=0)
<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)
>>> c.question
<Question: What's up?>>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
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() # delete() 删除对象
(1, {'polls.Choice': 1})
上面是官方文档提供的一些例子,还有更多的有关 API 的操作,我们将在后面学习到。
总结
1、创建对象q = Question.objects.all() # QuerySet 对象集合
q = Question.objects.filter() # QuerySet 对象集合
q = Question.objects.get() # QuerySet 对象,一个2、插入数据q = Question(question_text="What's up?", pub_date=timezone.now()) # 方法一
q.save()访问数据:q.idq.pub_dateQuestion.objects.create(question_text="What's up?", pub_date=timezone.now()) # 方法二3、查询数据q = Question.objects.get(id=1) # 通过 q.数据表名_set.all() 方式获得与其关联的数据表对象
q.choice_set.all() # <QuerySet [<Choice: Not much>, <Choice: The sky>]>4、删除数据
q.delete()
2.4 后台管理 Admin
Django 为我们提供了一个后台管理工具 Admin,可以对数据进行简单的增删改查等,简单易用,并支持拓展。
创建管理员用户
python manage.py createsuperuser # 运行命令,新建用户名、邮箱和密码
# username: xxx
# email:xxx@qq.com
# password:xxx
注册应用
将模型中的类注册到 polls/admin.py
中,接收站点的管理:
from django.contrib import admin
from polls.models import Question, Choiceadmin.site.register(Question)
admin.site.register(Choice)
访问 Admin
访问 http://127.0.0.1:8000/admin/
,输入刚才创建的用户名和密码:
样式定制
修改 polls/admin.py
:
from django.contrib import admin
from polls.models import Question, Choice# 定制样式,更多样式见官方文档
class QuestionAdmin(admin.ModelAdmin):list_display = ('id', 'question_text', 'pub_date') # 要显示的字段list_editable = ('question_text', 'pub_date') # 可编辑的admin.site.register(Question, QuestionAdmin)
admin.site.register(Choice)
3. 模板和视图
3.1 编写视图函数
Django 中每一个网页都是通过视图函数来处理的,在 polls 应用中,我们将创建以下四个视图:
URL | 视图函数 | 模板 | 说明 |
---|---|---|---|
/index/ | index() | index.html | 主页,显示最新问题 |
/results/ | results() | results.html | 投票结果 |
/detail/ | detail() | detail.html | 问题详细描述 |
/vote/ | vote() | vote.html | 投票动作,是否投票 |
- 首先我们配置好
mysite/urlconf
,以便能够找到相应视图函数:
from django.contrib import admin
from django.urls import path, include
from polls import viewsurlpatterns = [path('admin/', admin.site.urls),path('index/', views.index, name='index'),path('detail/', views.detail, name='detail'),path('results/', views.results, name='results'),path('vote/', views.vote, name='vote'),
]
- 编写视图
polls/views.py
:
from django.shortcuts import render, HttpResponsedef index(request):"""首页"""return HttpResponse('Is Ok!')def detail(request):"""问题详细描述"""return HttpResponse('问题详细描述')def results(request):"""投票结果"""return HttpResponse('投票结果')def vote(request):"""是否投票"""return HttpResponse('是否已经投票')
现在视图函数已经创建好了,我们可以访问相应视图看看 http://127.0.0.1:8000/detail/
返回的是什么。
3.2 使用模板
3.2.1 创建模板
在上面的视图函数中,我们使用了 HttpResponse
对象返回了一个字符串,而实际开发中,我们得到的都是一个 HTML页面。这就需要用到我们的模板系统了。
在 polls 目录下创建一个 templates
目录,再在 templates
目录下创建一个新的 polls
目录。然后在 polls
中创建相应的模板文件(其路径polls/templates/polls/
),如:index.html/detail.html
等。
为什么要再多创建一个 polls 目录
当有另一个 app 也有 index.html
时,可以避免 Django 匹配错误。
配置 templates
要想 Django 能找到 templates 中的模板文件,那么还要配置下 settings
:
# 当 templates 在 mysite/templates 下,不要添加 polls
TEMPLATE_DIRS = (os.path.join(BASE_DIR, 'polls', 'templates'),) 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',],},},
]
3.2.2 渲染模板
- 渲染模板,Django 为我们提供了一个
render()
函数,用于渲染模板文件,render()
语法格式:
render(request, template_name, context=None) # 三个参数,第一个固定为请求对象request,第二个是要渲染的模板文件,第三个是个可选参数,即要传递的数据,是个字典格式
编辑 polls/views.py
:
from django.shortcuts import render, HttpResponse
from .models import Questiondef index(request):"""首页"""question_list = Question.objects.all() # 取出 Question 中所有 question return render(request, 'polls/index.html', {'question_list': question_list})def detail(request, question_id):"""问题详细描述"""question = Question.objects.get(id=question_id)return render(request, 'polls/detail.html', {'question': question})
当我们访问 http://127.0.0.1:8000/index/
时,index() 函数会处理我们的视图。它从 Question 取出所有的问题对象,并渲染到模板中。
- 创建模板文件
polls/templates/polls/index.html
:
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>{% for question in question_list %}<!-- 相当于访问 <a href='detail/1/'></a>--><li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a> </li>{% endfor %}
</body>
</html>
在模板文件 index.html
中,我们使用 for 循环将所有问题循环,当我们点击其中的 a 标签的链接时,将会被定位到 http://127.0.0.1:8000/detail/1
中。
- 模板文件
polls/templates/polls/detail.html
:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Detail</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>
- 配置
mysite/urls.py
:
from django.contrib import admin
from django.urls import path, include
from polls import viewsurlpatterns = [path('admin/', admin.site.urls),path('index/', views.index, name='index'),# 我们将 'detail/' 修改为: 'detail/<int:question_id>',以可匹配 http://127.0.0.1:8000/detail/1 这样的路径path('detail/<int:question_id>', views.detail, name='detail'),
]
在这里我们将 'detail/'
修改为: 'detail/<int:question_id>'
,以可匹配 http://127.0.0.1:8000/detail/1
这样的路径。其中 <int: question_id>
将匹配到一个正整数,另外不要忘了在视图函数中也要接收相应 question_id
:
def detail(request, question_id):"""问题详细描述"""question = Question.objects.get(id=question_id)return render(request, 'polls/detail.html', {'question': question})
这里我们用的是 Django 提供的模板语言,将数据库中的数据显示在页面上,后面将详细介绍。
3.3 返回 404 错误
当我们访问不存在的路径时,会返回一个 Http404
,我们可以定制下让其返回我们想要的内容,编辑 polls/views.py
:
from django.http import Http404
from django.shortcuts import render
from .models import Questiondef detail(request, question_id):try:question = Question.objects.get(pk=question_id)except Question.DoesNotExist:raise Http404("Question 不存在")return render(request, 'polls/detail.html', {'question': question})
另外 Django 也为我们提供了一个快捷函数 get_object_or_404()
,只需一行即可替代上面多行:
from django.shortcuts import get_object_or_404, render
from .models import Questiondef detail(request, question_id):question = get_object_or_404(Question, pk=question_id) # 第一个参数:模型,第二个:任意关键字return render(request, 'polls/detail.html', {'question': question})
- get_object_or_404():替代的是 get() 方法
- get_list_or_404():替代的是 filter() 方法
3.4 URL 命名空间
什么是 URL 的命名空间呢?就是给每一个 URL 路径,添加一个 别名,它有如下几点好处:
- 当有多个 app 时,可以更好地区分是哪个 app 的路径
- 避免硬编码,在上面
index.html
中,我们使用的就是 URL 命名空间,而不是<a href='/detail/{{question.id}}'
这样的硬编码。这样在我们修改匹配方法时,不需要做大量的修改。
添加命名空间
from django.contrib import admin
from django.urls import path, include
from polls import viewsurlpatterns = [path('admin/', admin.site.urls),path('index/', views.index, name='index'), # 其中 name='index' 即为 URL的 命名空间path('detail/<int:question_id>', views.detail, name='detail'),
]
当有多个应用时
当有多个应用时,我们只需在 urls.py
中添加一个 app_name
,并在使用时带上它即可:
...
app_name = 'polls' # 添加这行
urlpatterns = [path('admin/', admin.site.urls),path('index/', views.index, name='index'), # 其中 name='index' 即为 URL的 命名空间path('detail/<int:question_id>', views.detail, name='detail'),
]
使用时,一定要记得带上 app_name
:
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
4. 表单和通用视图
在创建表单之前,我们先来分析下程序的整体运行流程:
- 访问首页 index,将所有问题都显示出来
- 点击问题,跳转到 detail,显示详细问题,并显示投票选项
- 当用户投票后,跳转到 results 结果页面,并询问是否还要继续投票。
从流程中可以看出,我们要在问题详细页面提供单选框,以供用户选择,下面我们来创建第一个表单:
4.1 Form 表单
- 编写
polls/detail.html
:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Detail</title>
</head>
<body>
<!--问题-->
<h1>{{ question.question_text }}</h1><!-- 错误信息 -->
{% if error_message %}<p>{{ error_message }}</p>
{% endif %}<form action="{% url 'vote' question.id %}" method="post">{% csrf_token %} <!--csrf 攻击,表单提交必须带上这个--><!-- 通过 question.choice_set.all 获得所有 Choice 选项 -->{% for choice in question.choice_set.all %} <!--choice1、choice2--><input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}"><label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label>{% endfor %}<!-- 提交 --><input type="submit" value="vote">
</form>
</body>
</html>
- 在上面
detail.html
模板文件中,我们创建了一个表单,当用户点击提交时,会被提交到 action 对应的 URL 中去。 - 在表单中,我们通过
question.choice_set.all
获得所有 Choice 选项,并循环它。 - 再定义了一个单选框 radio,提交到服务器的键为
choice
,值为选项的 id。 - 另外要注意的是 form 表单发送 post 请求时,务必带上
{% csrf_token %}
,否则将被禁止提交。
- 配置
mysite/urls.py
:
# /index/
path('index/', views.index, name='index'),
# /detail/1/
path('detail/<int:question_id>', views.detail, name='detail'),
# /results/1/
path('results/<int:question_id>', views.results, name='results'),
# /vote/1/
path('vote/<int:question_id>', views.vote, name='vote'),
- 编写
polls/views.py
:
from django.shortcuts import render, HttpResponse, get_object_or_404, redirect
from .models import Question, Choice
from django.http import HttpResponseRedirect
from django.urls import reversedef vote(request, question_id):"""处理投票"""print(question_id)question = get_object_or_404(Question, id=question_id)try:choice_id = request.POST.get('choice', None)print(choice_id)selected_choice = question.choice_set.get(id=choice_id)except (KeyError, Choice.DoesNotExist):# choice 没找到,重新返回表单页面,并给出提示信息return render(request, 'polls/detail.html', {'question': question, 'error_message': '你没用选择选项!'})else:selected_choice.votes += 1selected_choice.save()ret = reverse('results', args=(question.id,)) # /results/1return HttpResponseRedirect(ret)
question_id
为问题所对应的 id- 在
detail.html
模板中,我们将选项的 id 提交到了后台,通过request.POST.get('choice')
我们可以获得用户选择的选项 id - 当没有对应的 choice_id 时,重新返回表单页面,并给出错误信息
- 当有相应 choice_id 时,对应 vote 则加 1,最后重定向到投票结果页面
results
。
- 编写
polls/results.html
<!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 'detail' question.id %}">Vote again?</a>
</body>
</html>
至此一个简单的公共投票系统已大致编写完成,以下为演示:
4.2 通用视图
在视图 polls/views
中,我们写了大量的类似于 index()
的重复代码,存在冗余问题。
Django 为我们提供了一种 通用视图系统,将常见的模式抽象画,可以删去很多冗余代码。为此我们需要以下三个步骤:
- 转换 urlconf
- 删除一些旧的、不需要的视图
- 基于通用视图,引入新的视图
转换 URLconf
编辑 mysite/urls.py
:
urlpatterns = [path('admin/', admin.site.urls),path('index/', views.IndexView.as_view(), name='index'),path('detail/<int:pk>', views.DetailView.as_view(), name='detail'),path('results/<int:pk>', views.ResultsView.as_view(), name='results'),path('vote/<int:question_id>', views.vote, name='vote'),
]
在这里我们将 question_id
修改为 pk,这是因为通用视图从 url 中匹配的将是主键 pk。
修改视图
from django.views import genericclass IndexView(generic.ListView):template_name = 'polls/index.html' # 模板名称context_object_name = 'question_list' # 返回给模板的变量def get_queryset(self):return Question.objects.all()class DetailView(generic.DetailView):model = Question # 模型template_name = 'polls/detail.html'class ResultsView(generic.DetailView):model = Questiontemplate_name = 'polls/results.html'def vote(request, question_id):pass
- ListView:显示对象的列表
- DetaiView:显示特定类型对象详细页面
- context_object_name:返回给模板的变量
{'question_list':question_list}
中的question_list
- DetaiView:匹配的是 URL 中的 pk 主键
- template_name:返回的模板文件,格式为
<app_name>/<model name>_list.html
更多有关通用视图:https://docs.djangoproject.com/zh-hans/2.1/topics/class-based-views/
5. 测试
测试是实际开发中不可或缺的一部分,它可以:
- 检验程序是否符合预期
- 及时发现问题,节省开发时间
- 更有利团队合作等
测试分为手动测试和自动测试,手动测试往往费时费力,效率低下。我们可以借助一些测试模块,如:TestCase,自动帮我们完成测试工作,Django也有自动测试程序,它也是基于 TestCase 模块来实现的。
在模型 models.py
中,我们给 Question 定义了一个 was_published_recently()
方法,用于返回问题是否是最近发布的,当 Question 在最近一天发布时返回 True
。
class Question(models.Model):"""数据表:问题表"""question_text = models.CharField(max_length=2000) # 问题内容pub_date = models.DateTimeField('date published') # 发布日期def __str__(self):return self.question_textdef was_published_recently(self):# 当前时间减去前一天,与问题发布时间比较return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
5.1 验证 bug
进入 Django shell 环境:
>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question# 创建一个在发布日期 30 天后的问题对象
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))# 测试返回值,发现也是 True
>>> future_question.was_published_recently()
True
我们创建了一个在发布日期 30 天后的问题,测试发现还是返回 True,也就是说这里被允许在未来时间发布问题,这就是个 bug。
5.2 测试 bug
编写 polls/tests.py
:
from django.test import TestCase
import datetime
from django.utils import timezone
from .models import Questionclass QuestionModelTests(TestCase):def test_was_published_recently_with_future_question(self):# 创建一个 pub_date 是未来30天后的 Question 示例,然后检查 was_published_recently() 的返回值,它应该是 Falsetime = 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
,会看到结果:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):File "E:\Python_virtualenvs\for_django\Projects\mysite\polls\tests.py", line 11, in test_was_published_recently_with_future_qu
estionself.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False----------------------------------------------------------------------
Ran 1 test in 0.016sFAILED (failures=1)
Destroying test database for alias 'default'...
我们创建了一个 pub_dae
值为 30 天后的 Question 实例,用 assertls()
方法判断是否返回 False,结果发现返回 True。
5.3 修改 bug
我们要让 pub_date
是未来某天时, Question.was_published_recently()
返回 False,修改 polls/models.py
:
def was_published_recently(self):now = timezone.now()return now - datetime.timedelta(days=1) <= self.pub_date <= now
再进行测试,发现测试通过。测试在项目开发中很重要,也很常用,在这里我们只是做个大概的了解,到后面再详细的探讨。
6. 静态文件
静态文件即 Web 应用程序所要用到的一些必要文件,如:图片、JS 脚本、CSS 样式等。一个完整的 Web 应用应该有自己独立静态文件、模板文件,也就是说需要和项目本身区分开。
在应用 polls 下新建一个 static 的目录,再新建一个以应用名字为名的文件夹,最后再分类存储各种静态文件,其目录结构是这样的:
配置静态文件
与模板 templates
一样,再使用前,需要先配置好静态文件,这样 Django 才能找到,编辑 settings.py
:
STATIC_URL = '/static/'
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'polls', 'static'),
) # 一定不要忘记最后的逗号
使用静态文件
在 polls/static/polls/
下创建一个 images
目录用来存储图片,再创建一个 css
目录用来存储 CSS 文件。然后在新建一个 style.css
的文件。
下面我们来给首页 index.html
添加背景图片,编写以下代码:
li a{color: red;
}body {background: white url("images/2.png") no-repeat;
}
然后在 index.html
中来加载 style.css
文件:
{% load static %} <!--引入 static-->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><!--再把 style.css 加载进来 --><link rel="stylesheet" href="{% static 'polls/css/style.css' %}">
</head>
<body>{% for question in question_list %}<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a> </li>{% endfor %}
</body>
</html>
我们再刷新下,发现已经给首页添加好了背景图片。除此之外我们还可以在模板文件中直接使用静态文件,如:在模板中使用 jQuery
:
# 同样地,也要先引入 static
{% load static %}
<script src="{% static 'polls/js/jquery-3.1.1.js' %}"></script>
相关文章:

第一个 Django 应用
1. 创建项目 1.1 新建项目 首先新建一个项目,名为 mysite,命令如下: django-admin startproject mysite # 或用 django-admin.py运行成功,生成一些目录: mysite/manage.py # 管理 Django 项目的命令行工具mysit…...

001-ksum 求符合条件的 k 个数 1. Two Sum/15. 3Sum/18. 4Sum/
推荐阅读 000-从零开始的数据结构与算法 001-01-ksum 求符合条件的 k 个数 1. Two Sum/15. 3Sum/18. 4Sum/ 002-两数相加 add two numbers 003-无重复字符的最长子串 Longest Substring Without Repeating Characters 004-寻找两个正序数组的中位数 005-最长回文子串 Lon…...

Nginx学习笔记(三)Linux环境下Nginx的安装和部署
目录一、官网下载二、配置基本信息1.上传 Linux2.解压3.安装编译环境4.配置基本信息4.1 配置失败原因(1):没有安装C编译环境4.2 配置失败原因(2):没有安装 PCRE 依赖4.3 配置失败原因(3):没有安装 zlib 依赖5.查看文件列表三、编译安装四、配…...

【十二天学java】day05--数组和循环高级
**# 1.数组 概念: 指的是一种容器,可以同来存储同种数据类型的多个值。 但是数组容器在存储数据的时候,需要结合隐式转换考虑。 比如: 定义了一个int类型的数组。那么boolean。double类型的数据是不能存到这个数组中的&#…...

用队列实现栈和用栈实现队列(C 语言)
目录 一、用队列实现栈 二、 用栈实现队列 一、用队列实现栈 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。 实现 MyStack 类: void push(int…...

albedo开源框架配置多数据源
前言:公司框架项目一直都没认真阅读过,最近项目需要连接oracle数据,所以尝试使用框架连接多数据库。添加多数据源插件:我们在项目的插件模块内添加多数据源插件:albedo-dynamic-datasource<?xml version"1.0&…...

22张图带你了解IP地址有什么作用
了解IP地址 1、IP地址的格式 在IP协议的报文中,可以得知IP地址是有32个比特,IP地址在计算机中是以二进制的方式处理的,如果全部以二进制的形式来表示,使用跟表达都非常的困难,所以为了人类方便记忆,采用了…...

121.Android 简单的人工智能聊天项目,chatAi,AI聊天项目,GPTAi
//首页xml布局代码: <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"mat…...

C++ this指针详解
this 是 C 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。所谓当前对象,是指正在使用的对象。例如对于stu.show();,stu 就是当前对象,this 就指向 stu。下面是使用…...

CSS 实现六边形柱状图
前言 👏CSS 实现六边形柱状图 速速来Get吧~ 🥇文末分享源代码。记得点赞关注收藏! 1.实现效果 2.实现步骤 定义全局css变量,柱状宽度为–w,最大高度为–h,柱形整体为渐变色,定义上部分颜色为…...

什么是推挽输出,开漏输出?
这篇文章是看B站“工科男孙老师”这个视频的笔记推挽 开漏 高阻 这都是谁想出来的词?? 我觉得讲的很好,做一下笔记 1.什么是IO输出三态 一共有:高电平, 低电平,浮空/高阻态 三种IO态 2.推挽输出 推挽输出能够表示高、…...

【图像分割】Unet系列深度讲解(FCN、UNET、UNET++)
【图像分割】Unet 深度讲解 文章目录【图像分割】Unet 深度讲解1. 介绍1.1 背景介绍:1.2 医学图像特点1.3 图像分割是什么2. Unet发展历程(FCN、Unet、Unet)2.1 全卷积网络-FCN2.1.1 FCN介绍:2.1.2 FCN框架2.1.3 反卷积层2.1.4 输…...

list底层的简单实现(万字长文详解!)
list底层的简单实现 文章目录list底层的简单实现list_node的实现!list_node的构造函数list的迭代器!——重点!list迭代器的成员变量迭代器的构造函数* 重载前置 重载后置 重载前置-- 重载后置-- 重载! 重载 重载-- 重载list的const迭代器——…...

学习Linux只要学会这个命令就够了!
大家好,我是良许。 这段时间又是搬家,又是找新办公室,现在终于安顿下来了,有时间给大家分享干货了。 今天给大家介绍一个 Linux 超级实用命令,有了这个命令,你就可以愉快使用 Linux 上几乎所有常用命令了…...

javascript基础
javascript基础 1概述: JavaScript是目前web开发中不可缺少的脚本语言,js不需要编译即可运行,运行在客户端,需要通过浏览器来解析执行JavaScript代码。 诞生于1995年,当时的主要目的是验证表单的数据是否合法。 JavaS…...

【游戏逆向】某游戏技能库分析
技能库的分析大多是从技能名字入手的,然后再通过传入职业或者ID等信息去到库中去取当前角色的可用技能。下面我们来对《**明月刀》中的技能库进行分析。 首先通过CE对技能名字进行搜索,得到较少的结果,分别对结果进行修改,并再次…...

Pytorch深度学习常用预训练网络模型的下载地址
Resnet:model_urls {‘resnet18’: ‘https://download.pytorch.org/models/resnet18-5c106cde.pth‘,‘resnet34’: ‘https://download.pytorch.org/models/resnet34-333f7ec4.pth‘,‘resnet50’: ‘https://download.pytorch.org/models/resnet50-19c8e357.pth‘,‘resnet…...

毕业设计 基于51单片机自动智能浇花系统设计
基于51单片机自动智能浇花系统设计1、毕业设计选题原则说明(重点)2、项目资料2.1 系统框架2.2 系统功能3、部分电路设计3.1 STC89C52单片机最小系统电路设计3.2 按键电路设计3.3 水泵控制电路设计4、部分代码展示4.1 数码管位选程序4.2 ad0832数据读取程…...

熟悉常用的 Linux 操作和 Hadoop 操作
文章目录前言一、常用命令集合1、cd命令:切换目录1、切换到目录/usr/local2、切换回上级目录3、切换到当前登录Linux系统的用户的自己的文件夹2、ls命令:查看文件与目录3、mkdir命令:创建目录4、rmdir命令:删除空的目录5、cp 命令…...

Vue2项目总结-电商后台管理系统
Vue2项目总结-电商后台管理系统 去年做的项目,拖了很久,总算是打起精力去做这个项目的总结,并对Vue2的相关知识进行回顾与复习 各个功能模块如果有过多重复冗杂的部分,将会抽取部分值得记录复习的地方进行记录 一:项目…...

【二】一起算法---队列:STL queue、手写循环队列、双端队列和单调队列、优先队列
纸上得来终觉浅,绝知此事要躬行。大家好!我是霜淮子,欢迎订阅我的专栏《算法系列》。 学习经典算法和经典代码,建立算法思维;大量编码让代码成为我们大脑的一部分。 ⭐️已更系列 1、基础数据结构 1.1、链表➡传送门 1…...

<Linux>环境变量
环境变量 文章目录环境变量一、基本概念二、常见环境变量三、查看环境变量的方法四、测试PATH五、测试HOME六、测试SHELL七、环境变量相关的命令八、环境变量的组织方式九、命令行参数十、通过代码获得环境变量十一、通过系统调用获取环境变量十二、环境变量通常是具有全局属性…...

【MySQL】下载(超详细教程)
目录 First-下载 Second-安装 Third-检测是否安装 Last-总结 First-下载 首先 ,我们一步一步跟着我的操作来,不能越步骤,很容易报错,就芭比Q了。 第一步直接进入这个网址:MySQL :: MySQL 社…...

再探pytorch的Dataset和DataLoader
本文从分类、检测、分割三大任务的角度来剖析pytorch得dataset和dataloader源码,可以让初学者深刻理解每个参数的由来和使用,并轻松自定义dataset。思考:在探究Dataset和DataLoader之前,需要明白一个事情,就是当我们不…...

【2023.3.18 美团校招】
文章目录1. 小美剪彩带2. 最多修改两个字符,生成字典序最小的回文串1. 小美剪彩带 题意:找出区间内不超过k种数字子数组的最大长度 使用双指针的方式,用哈希表来统计每个数出现次数。在双指针移动的过程中,动态的维护区间内不同数…...

程序员必须知道的HTML常用代码有哪些?
HTML 即超文本标记语言,是目前应用最为广泛的语言之一,是组成一个网页的主要语言。在现今这个 HTML5 华丽丽地占领了整个互联网的时候,如果想要通过网页抓住浏览者的眼球光靠因循守旧是不行的,程序猿们需要掌握一些必须知道的 HTM…...

多目标家庭行为检测--人脸识别模块构建
文章目录前言原理项目结构编码配置主控函数人脸采集模块特征提取识别测试前言 2023-3-18 天小雨,午觉舒适程度5颗星。任务完成指数2颗星。续接上文:《MidiaPipe stgcn(时空图卷积网络)实现人体姿态判断(单目标&#x…...

RocketMQ重复消费问题的原因
文章目录 概览消息发送异常时重复发送消费消息抛出异常消费者提交offset失败服务端持久化offset失败主从同步offset失败重平衡清理长时间消费的消息总结概览 消息发送异常时重复发送 首先,我们来瞅瞅RocketMQ发送消息和消费消息的基本原理。 如图,简单说一下上图中的概念: …...

proxy详细介绍与使用
proxy详细介绍与使用 proxy 对象用于创建一个对象的代理,是在目标对象之前架设一个拦截,外界对该对象的访问,都必须先通过这个拦截。通过这种机制,就可以对外界的访问进行过滤和改写。 ES6 原生提供 Proxy 构造函数,…...

基于YOLOv5的舰船检测与识别系统(Python+清新界面+数据集)
摘要:基于YOLOv5的舰船检测与识别系统用于识别包括渔船、游轮等多种海上船只类型,检测船舰目标并进行识别计数,以提供海洋船只的自动化监测和管理。本文详细介绍船舰类型识别系统,在介绍算法原理的同时,给出Python的实…...