您的位置:首页 > 编程语言 > Go语言

初步尝试Django

2020-04-03 18:24 651 查看

Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型M,模板T和视图V。Django是一个重量级的框架,他将很多功能都集成好了,开发很便捷.适合入门学习

  1. pip install Django
    检测安装成功与否,看是否能打印出来版本号3.0.3
    import django
    print(django.get_version())

  2. 创建一个名称为webtest的项目
    打开命令行,cd 到一个你想放置你代码的目录
    django-admin startproject webtest
    将会在当前目录下创建一个 webtest 目录,结构如下所示
    webtest/
    —manage.py (一个让你用各种方式管理 Django 项目的命令行工具)
    —webtest/ (目录包含你的项目)
    l— init.py (一个空文件,这个目录应该被认为是一个 Python 包)
    l—settings.py (Django 项目的配置文件)
    l—urls.py (Django 项目的 URL 声明,就像你网站的“目录”)
    l—wsgi.py (作为你的项目的运行在 WSGI 兼容的Web服务器上的入口)

  3. 确认一下你的 Django 项目是否真的创建成功
    python manage.py runserver
    打开浏览器http://127.0.0.1:8000/,可以看到界面
    默认情况下,runserver 命令会将服务器设置为监听本机内部 IP 的 8000 端口。
    如果你想更换服务器的监听端口,请使用命令行参数。举个例子,下面的命令会使服务器监听 8080 端口:
    python manage.py runserver 8080

  4. 创建投票应用
    处于 manage.py 所在的目录下
    python manage.py startapp polls
    这将会创建一个 polls 目录,它的目录结构大致如下:
    polls/
    init.py
    —admin.py
    —apps.py
    —migrations/
    l—init.py
    l—models.py
    l—tests.py
    l—views.py
    这个目录结构包括了投票应用的全部内容

  5. 编写第一个视图
    开始编写第一个视图。打开 polls/views.py,输入以下Python 代码

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

这是 Django 中最简单的视图。如果想看见效果,我们需要将一个 URL 映射到它,所以需要用到URLconf 。
5.1 为了创建 URLconf,请在 polls 目录里新建一个 urls.py 文件
输入如下代码:

from django.urls import path
from . import views

urlpatterns = [
path('', views.index, name='index'),
#与urls同目录下的view文件中的函数index
]

5.2 下一步是要在 URLconf 文件中指定我们创建的 polls.urls 模块。在 webtest/urls.py 文件的 urlpatterns 列表里插入一个 include(), 如下:

from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),   #这个是插入的代码
#route体现的是url路径,''即为http://localhost:8000/
#             'polls/'即为http://localhost:8000/polls/
path('admin/', admin.site.urls),
]

用浏览器访问 http://localhost:8000/polls/,能够看见 “Hello, world. You’re at the polls index.”

函数 path() 具有四个参数,两个必须参数:route 和 view,两个可选参数:kwargs 和 name。下面是这些参数的含义:

1) route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。这些准则不会匹配 GET 和 POST 参数或域名。
例如,URLconf 在处理请求 https://www.example.com/myapp/ 时,它会尝试匹配 myapp/ 。处理请求 https://www.example.com/myapp/?page=3 时,也只会尝试匹配 myapp/。
2) view 当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest 对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。
3) kwargs 任意个关键字参数可以作为一个字典传递给目标视图函数
4) name 为 URL 取名,就能在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式。

6. 数据库配置
settings.py 通常这个配置文件使用 SQLite 作为默认数据库。
在实际的业务中并不常用,所以这里我们使用mysql。
6.1 pip install pymysql
然后在 init.py 文件中添加以下代码:

import MySQLdb

6.2 修改 Django的配置文件 settings.py
将以下代码

# 这段代码在 settings.py的 76行
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}

更换成

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1',
'PORT':'3306',
'USER':'test',
'PASSWORD':'123456',
'NAME':'kg_db',
}
}

报错的话就执行:
pip install pymysql
pip install --upgrade mysqlclient
随后根目录下执行: python manage.py migrate
可以用 show tables 看到新建的数据表
auth_group
| auth_group_permissions
| auth_permission
| auth_user
| auth_user_groups
| auth_user_user_permissions
| django_admin_log
| django_content_type
| django_migrations
| django_session

  1. 创建模型
    在 Django 里写一个数据库驱动的 Web 应用的第一步是定义模型
    Django 遵循 DRY Principle 。它的目标是你只需要定义数据模型,然后其它的杂七杂八代码你都不用关心,它们会自动从模型生成。

在这个简单的投票应用中,需要创建两个模型:问题 Question 和选项 Choice。Question 模型包括问题描述和发布时间。Choice 模型有两个字段,选项描述和当前得票数。每个选项属于一个问题。
编辑 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)   #当前得票数

每个 Field 类实例变量的名字(例如 question_text 或 pub_date )也是字段名,所以最好使用对机器友好的格式。你将会在 Python 代码里使用它们,而数据库会将它们作为列名。

数据库的表名是由应用名(polls)和模型名的小写形式( question 和 choice)连接而来。如 polls_choice 与 polls_question

每个模型有一些类变量,表示模型里的一个数据库字段
question模型中包含 CharField 字段和 DateTimeField 字段
choice模型中包含 CharField 字段和 IntegerField 字段
每个字段都是 Field 类的实例 ,字符字段被表示为 CharField ,日期时间字段被表示为 DateTimeField,计票字段被表示为 IntegerField 。这将定义好 Django 每个字段要处理的数据类型。

最后使用 ForeignKey 定义了一个关系。每个 Choice 对象都关联到一个 Question 对象。Django 支持所有常用的数据库关系:多对一、多对多和一对一。

  1. 把 polls 应用安装到项目里
    为了在我们的工程中包含这个应用,我们需要在配置类 INSTALLED_APPS 中添加设置。因为 PollsConfig 类写在文件 polls/apps.py 中,所以它的点式路径是 ‘polls.apps.PollsConfig’。在文件 webtest/settings.py 中 INSTALLED_APPS 子项添加点式路径后,它看起来像这样:
INSTALLED_APPS = [
'polls.apps.PollsConfig',    #这个是添加的路径
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]

现在你的 Django 项目会包含 polls 应用。接着运行下面的命令:
python manage.py makemigrations polls
看到如下输出:
Migrations for ‘polls’:
polls/migrations/0001_initial.py:
- Create model Choice
- Create model Question
- Add field question to choice
通过运行 makemigrations 命令,Django 会检测对模型文件的修改,并且把修改的部分储存为一次 迁移。模型的迁移数据,它被储存在polls/migrations/0001_initial.py 里

现在再一次运行 python manage.py migrate 命令
可以在数据库中看到新添加的两个表:
polls_choice
polls_question

只需要记住,改变模型需要这三步:
1)编辑 models.py 文件,改变模型。
2)运行 python manage.py makemigrations 为模型的改变生成迁移文件。
3)运行 python manage.py migrate 来应用数据库迁移。

9、交互式 Python 命令行,尝试一下 Django 创建的各种 API
链接:
https://docs.djangoproject.com/zh-hans/2.1/intro/tutorial02/
python manage.py shell

>>> from polls.models import Choice, Question  # >>>Question.objects.all()
Out[2]: <QuerySet []>

>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
>>> q.save()
>>>q = Question.objects.get(pk=1)
>>>q.choice_set.create(choice_text='Not much', votes=0)
>>>q.choice_set.create(choice_text='The sky', votes=0)
>>>q.choice_set.count()
Out[10]:  3
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
  1. 创建一个管理员账号
    10.1 创建一个用户
    python manage.py createsuperuser
    输入用户名,邮箱,还有密码
    admin adminadmin
    10.2 启动开发服务器
    python manage.py runserver
    打开浏览器,转到地域名的 “/admin/” 目录, – 比如 http://127.0.0.1:8000/admin/
    以用户名与密码登录,可以看到如下界面

    你将会看到几种可编辑的内容:组和用户。它们是由 django.contrib.auth 提供的,这是 Django 开发的认证框架。
    10.2 向管理页面中加入投票应用
    打开 polls/admin.py 文件,编辑成下面这样:
from django.contrib import admin
from .models import Question
admin.site.register(Question)

保存之后,刷新页面即可看到

点击Questions可以看到问题描述 What’s up
点击What’s up 可以看到问题 question_text 与 pub_date,可以在此界面进行修改

  1. 编写更多视图
    在我们的投票应用中,我们需要下列几个视图:
    问题索引页——展示最近的几个投票问题。
    问题详情页——展示某个投票的问题和不带结果的选项列表。
    问题结果页——展示某个投票的结果。
    投票处理器——用于响应用户为某个问题的特定选项投票的操作。

11.1 向 polls/views.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)

11.2 新视图添加进 polls.urls 模块里

urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),    #与urls同目录下的view文件中的函数index
# ex: /polls/34/    》》》》》》》》》》》》》》》》》》》》》》
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/34/results/   》》》》》》》》》》》》》》》
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/5/vote/   》》》》》》》》》》》》》》》》
path('<int:question_id>/vote/', views.vote, name='vote'),
]

浏览器输入http://localhost:8000/polls/34/ 可以看到
You’re looking at question 34.
而"/polls/34/vote/" 和 “/polls/34/results/” 将会看到暂时用于占位的投票页和结果页
在 Django 中,网页和其他内容都是从视图派生而来。每一个视图表现为一个简单的 Python 函数(或者说方法,如果是在基于类的视图里的话)。Django 将会根据用户请求的 URL 来选择使用哪个视图(更准确的说,是根据 URL 中域名之后的部分)。

为每个 URL 加上不必要的东西,例如 .html ,是没有必要的。不过如果你非要加的话,也是可以的:
path(‘polls/latest.html’, views.index),

  1. 做一个真正有用的视图
    每个视图必须要做的只有两件事:返回一个包含被请求页面内容的 HttpResponse 对象,或者抛出一个异常,比如 Http404

因为 Django 自带的数据库 API 很方便,在 index() 函数里插入一些新内容,让它能展示数据库里以发布日期排序的最近 5 个投票问题,以逗号分割:

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)

因为页面的设计写死在视图函数的代码里的。所以想改变页面的样子,需要使用 Django 的模板系统。
13. 使用模板
首先在 polls 目录里创建一个 templates 目录。Django 将会在这个目录里查找模板文件。
项目的 TEMPLATES 配置项描述了 Django 如何载入和渲染模板。默认的设置文件设置了 DjangoTemplates 后端,并将 APP_DIRS 设置成了 True。这一选项将会让 DjangoTemplates 在每个 INSTALLED_APPS 文件夹中寻找 “templates” 子目录。这就是为什么尽管我们没有像在第二部分中那样修改 DIRS 设置,Django 也能正确找到 polls 的模板位置的原因。

在刚刚创建的 templates 目录里,再创建一个目录 polls,在其中新建一个文件 index.html 。模板文件的路径应该是 polls/templates/polls/index.html 。因为 Django 会寻找到对应的 app_directories ,所以只需要使用 polls/index.html 就可以引用到这一模板了。
将以下代码输入到index.html文件中

{% 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 %}

再修改下视图函数,Django 提供了一个快捷函数 render()
载入模板,填充上下文,再返回由它生成的 HttpResponse 对象
是一个非常常用的操作流程。

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)

浏览器输入http://127.0.0.1:8000/polls/
可以看到:

14. 抛出 404 错误
投票详情视图——它会显示指定投票的问题标题,但如果所指定问题不存在要抛出404错误。
下面是这个视图的代码

from django.http import Http404
from django.shortcuts import render

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_object_or_404()
尝试用 get() 函数获取一个对象,如果不存在就抛出 Http404 错误。下面是修改后的详情 detail() 视图代码:

from django.shortcuts import get_object_or_404, render

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

然后模板中新建一个detail.html文件,输入:

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

在 {% for %} 循环中发生的函数调用:question.choice_set.all 被解释为 Python 代码 question.choice_set.all() ,将会返回一个可迭代的 Choice 对象,这一对象可以在 {% for %} 标签内部使用。

  1. 去除模板中的硬编码 URL
    之前在 polls/index.html 里编写投票链接时,链接是硬编码的:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

在 polls.urls 的 url() 函数中通过 name 参数为 URL 定义了名字,现在使用 {% url %} 标签代替它:

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

这个标签的工作方式是在 polls.urls 模块的 URL 定义中寻具有指定名字的条目。回忆一下,具有名字 ‘detail’ 的 URL 是在如下语句中定义的:

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

如果想改变投票详情视图的 URL,比如想改成 polls/specifics/12/ ,就不用在模板里修改任何东西(包括其它模板),只要在 polls/urls.py 里稍微修改一下就行:

path('specifics/<int:question_id>/', views.detail, name='detail'),
  1. 为 URL 名称添加命名空间
    在根 URLconf 中添加命名空间。在 polls/urls.py 文件中稍作修改,加上 app_name 设置命名空间:
app_name = 'polls'

这一段添加在 poll/urls.py 中 " urlpatterns = [ " 之前,
再编辑 polls/index.html 文件

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

更新在上一个教程中编写的投票详细页面的模板 (“polls/detail.html”) ,让它包含一个 HTML 元素

<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% 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 %}
<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 表单(它具有修改数据的作用),所以需要小心跨站点请求伪造。所有针对内部 URL 的 POST 表单都应该使用 {% csrf_token %} 模板标签。

现在来创建一个 Django 视图来处理提交的数据。
将下面的代码添加到 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 vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
#request.POST['choice'] 以字符串形式返回选择的 Choice 的 ID

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 调用改动。
在增加 Choice 的得票数之后,代码返回一个 HttpResponseRedirect 而不是常用的 HttpResponse 、 HttpResponseRedirect 只接收一个参数:用户将要被重定向的 URL。reverse() 函数。这个函数避免了我们在视图函数中硬编码 URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。

浏览器输入http://127.0.0.1:8000/polls/1/vote/,即可看到投票界面:

  1. 结果界面
    当有人对 Question 进行投票后, vote() 视图将请求重定向到 Question 的结果界面。
    那么在view.py中编写这个结果视图:
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})

创建一个 polls/results.html 模板:

<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 %}">Vote again?</a>

接上述操作,提交后页面结果为:

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

  1. 改良视图
    17.1 改良 URLconf
    打开 polls/urls.py ,修改
urlpatterns = [
# ex: /polls/
path('', views.IndexView.as_view(), name='index'),
# ex: /polls/1/
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
# ex: /polls/1/results/
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]

17.2 改良视图
删除旧的 index, detail, 和 results 视图,并用 Django 的通用视图代替。打开 polls/views.py 文件,并将它修改成:

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'
context_object_name = 'latest_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'

class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
  1. 编写测试
    18.1 首先得有个 Bug
    polls 应用现在就有一个小 bug 需要被修复:我们的要求是如果 Question 是在一天之内发布的, Question.was_published_recently() 方法将会返回 True ,然而现在这个方法在 Question 的 pub_date 字段比当前时间还晚时也会返回 True(这是个 Bug,因为没有发布时间会比当前之间还晚)。
    用djadmin:
    shell
    命令确认一下这个方法的日期bug.
    python manage.py shell
>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> # create a Question instance with pub_date 30 days in the future
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> # was it published recently?
>>> future_question.was_published_recently()
True

18.2 写测试文件
按照惯例,Django 应用的测试应该写在应用的 tests.py 文件里。测试系统会自动的在所有以 tests 开头的文件里寻找并执行测试代码。
将下面的代码写入 polls 应用里的 tests.py 文件内:

from django.test import TestCase

import datetime
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)
#datetime.timedelta(days=30) 当前时间的30天后
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)

我们创建了一个 django.test.TestCase 的子类,并添加了一个方法,此方法创建一个 pub_date 时未来某天的 Question 实例。然后检查它的 was_published_recently() 方法的返回值——它 应该 是 False。
18.3 运行测试代码
python manage.py test polls
将能看到运行结果

发生了什么呢?以下是自动化测试的运行过程

python manage.py test polls 将会寻找 polls 应用里的测试代码
它找到了 django.test.TestCase 的一个子类
它创建一个特殊的数据库供测试使用
它在类中寻找测试方法——以 test 开头的方法。
在 test_was_published_recently_with_future_question 方法中,它创建了一个 pub_date 值为 30 天后的 Question 实例。
接着使用 assertls() 方法,发现 was_published_recently() 返回了 True,而我们期望它返回 False。

测试系统通知我们哪些测试样例失败了,和造成测试失败的代码所在的行号。
18.4 修复这个bug
我们早已知道,当 pub_date 为未来某天时, Question.was_published_recently() 应该返回 False。我们修改 models.py 里的方法,让它只在日期是过去式的时候才返回 True:

def was_published_recently(self):
now = timezone.now()
#一天之内的最新发布
return now - datetime.timedelta(days=1) <= self.pub_date <= now

然后重新运行测试

我们在测试中写三个方法来确保 Question.was_published_recently() 方法对于过去,最近,和将来的三种情况都返回正确的值。

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)

def test_was_published_recently_with_old_question(self):
"""
was_published_recently() returns False for questions whose pub_date is older than 1 day.
"""
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
old_question = Question(pub_date=time)
self.assertIs(old_question.was_published_recently(), False)

def test_was_published_recently_with_recent_question(self):
"""
was_published_recently() returns True for questions whose pub_date is within the last day.
"""
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
  1. Django 测试工具之 Client
    Django 提供了一个供测试使用的 Client 来模拟用户和视图层代码的交互(即用这种直接输入代码的形式来执行访问网页的过程)。我们能在 tests.py 甚至是 shell 中使用它。
    python manage.py shell

19.1

>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
>>>from django.test import Client
>>>client = Client()
>>> response = client.get('/')
Not Found: /
>>> response.status_code
404
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code
200
>>> response.content
b'\n    <ul>\n    \n        <li><a href="/polls/1/">What&#39;s up?</a></li>\n    \n    </ul>\n\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>

接下来要改善视图代码,现在的投票列表会显示将来的投票( pub_date 值是未来的某天),马上来修复这个问题。
我们需要改进 get_queryset() 方法,让他它能通过将 Question 的 pub_data 属性与 timezone.now() 相比较来判断是否应该显示此 Question。

from django.utils import timezone

class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return Question.objects.filter(
pub_date__lte=timezone.now()
).order_by('-pub_date')[:5]

我们可以创建一个发布时间是3天后的Question,

>>>f = Question(question_text="Which are more beautiful", pub_date=timezone.now()+datetime.timedelta(days=3))
>>>f.save()

刷新 http://localhost:8000/polls/ 页面,依旧只能看到最早发布的两个问题,看不到未来发布的问题。

19.2 测试新视图
基于以上 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_past_question(self):
"""
Questions with a pub_date in the past are displayed on the
index page.
"""
create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)

def test_future_question(self):
"""
Questions with a pub_date in the future aren't displayed on
the index page.
"""
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])

def test_future_question_and_past_question(self):
"""
Even if both past and future questions exist, only past questions
are displayed.
"""
create_question(question_text="Past question.", days=-30)
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)

def test_two_past_questions(self):
"""
The questions index page may display multiple questions.
"""
create_question(question_text="Past question 1.", days=-30)
create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question 2.>', '<Question: Past question 1.>']
)

一个快捷函数 create_question,它封装了创建投票的流程,减少了重复代码。

19.3 测试 DetailView
就算在发布日期时未来的那些投票不会在目录页 index 里出现,但是如果用户知道或者猜到正确的 URL ,还是可以访问到它们。所以得在 DetailView 里增加一些约束,修改view.py文件:

class DetailView(generic.DetailView):
...
def get_queryset(self):
"""
Excludes any questions that aren't published yet.
"""
return Question.objects.filter(pub_date__lte=timezone.now())
  1. 自定义 应用 的界面和风格
    在你的 polls 目录下创建一个名为 static 的目录。Django 将在该目录下查找静态文件,在 static 文件夹中创建一个名为 polls 的文件夹,再在 polls 文件夹中创建一个名为 style.css 的文件。换句话说,你的样式表路径应是 polls/static/polls/style.css。
    将以下代码放入样式表(polls/static/polls/style.css):
li a {
color: green;
}

下一步,在 polls/templates/polls/index.html 的文件头添加以下内容:

{% load static %}

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

接下来添加一个背景图:
创建一个用于存在图像的目录。在 polls/static/polls 目录下创建一个名为 images 的子目录。目录中,放一张名为 background.gif 的图片。
刷新浏览器,即可看到:

  1. 自定义后台表单
    修改admin.py文件:
from django.contrib import admin
from .models import Choice, Question

class ChoiceInline(admin.TabularInline):
model = Choice
extra = 3

class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None,               {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
inlines = [ChoiceInline]

admin.site.register(Question, QuestionAdmin)

这会告诉 Django:“Choice 对象要在 Question 后台页面编辑。默认提供 3 个足够的选项字段。”
重载浏览器http://127.0.0.1:8000/admin/polls/question/,点击右上角的添加问题按钮,可以看到:

对“更改列表”页面进行一些调整——改成一个能展示系统中所有投票的页面。
修改admin.py文件

class QuestionAdmin(admin.ModelAdmin):
# ...
list_display = ('question_text', 'pub_date', 'was_published_recently')

现在使修改投票的页面变成这样:

将以下代码添加至 QuestionAdmin:
这样做添加了一个“过滤器”侧边栏,允许人们以 pub_date 字段来过滤列表:

list_filter = ['pub_date']


在列表的顶部增加一个搜索框。当输入待搜项时,Django 将搜索 question_text 字段。

search_fields = ['question_text']
  1. 自定义工程模板
    在工程目录(指包含 manage.py 的那个文件夹)内创建一个名为 templates 的目录。
    修改设置文件 webtest/settings.py,在 TEMPLATES 设置中添加 DIRS 选项:
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',
],
},
},
]

现在,在 templates 目录内创建名为 admin 的目录,随后,将存放 Django 默认模板的目录(django/contrib/admin/templates)内的模板文件 admin/base_site.html 复制到这个目录内。

  1. 打包此项目
    23.1 安装pyinstaller,运行 pip install pyinstaller
    23.2 进入django项目所在路径,运行 pyi-makespec -D manage.py,在路径下,就会生成一个.spec文件
    23.3 以文本的方式打开.spec文件,spec文件格式如下。


23.4 打包
运行以下语句 pyinstaller manage.spec
能够打包成功,但是在manage.exe所在路径webtest\dist\manage\下,在cmd中运行 manage.exe runserver,会发现以下错误:
ModuleNotFoundError: No module named ‘polls’
这种错误的原因是 polls在django项目中是隐式导入的,所以pyinstaller打包时,并不能识别这种库或者模块,导致打包出来的.exe中并不包括这样隐式导入的库。
如果碰到这样的错误,只需要将这个库添加到.spec文件中的hiddenimports中即可。在接下来打包django项目缺少很多这样的隐式库,所以在.spec文件中一并修改了,修改如下:(如果缺少什么,直接在hiddenimports中加就可以了)

hiddenimports=[
‘polls’,‘polls.apps’,‘polls.urls’,‘polls.admin’,
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’
],

24.5 模板缺失
以上hiddenimports弄好后,运行后会出现以下的错误:
TemplateDoesNotExist
是因为没有找到templates文件。


根据错误提示将templates文件添加至对应的路径下,刷新即可。
24.6 CSS缺失
在第五步后,可以发现页面已经出来,但是发现页面没有css和js了
这是因为Pyinstaller 能找到templates(html files文件),但不能找到css和js文件。
具体操作是在django项目的settirngs.py文件中加入:

STATIC_ROOT = os.path.join(BASE_DIR, 'static', 'static_root')

其中static是笔者项目中的静态文件位置,static_root是static下的一个空文件夹,然后执行命令将静态文件收录到static_root中:
python manage.py collectstatic
然后在urls.py中添加如下代码:

from django.conf.urls import static
from webtest import settings

urlpatterns += static.static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

最后还需要将static_root中的静态文件打包到.exe中。这一步是在.spec文件中的datas中加入下面一个元组:

datas=[(r'D:\code\webtest\static\static_root',r'.\static\static_root')],

重新打包即可。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
小二焦 发布了3 篇原创文章 · 获赞 0 · 访问量 77 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: