1、Django的安装

pip install Django
  • 验证 Django 是否能被 Python 识别

>>> import django
>>> print(django.get_version())
2.2.6

2、创建Django项目脚手架(里面mysite 是Django容器)

  • cd 到一个你想放置你代码的目录,然后运行以下命令:

      django-admin startproject mysite
    
  • 将会在当前目录下创建一个 mysite 目录,如下


mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py
  • 这些目录和文件的用处是:
    • 最外层的 mysite/ 根目录只是你项目的容器, Django 不关心它的名字,你可以将它重命名为任何你喜欢的名字。
    • manage.py:一个让你用各种方式管理 Django 项目的命令行工具。比如 django-admin 这些命令。
    • 里面一层的 mysite/ 目录包含你的项目,它是一个纯 Python 包。它的名字就是当你引用它内部任何东西时需要用到的 Python 包名。 (比如 mysite.urls)
    • mysite/init.py:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。
    • mysite/settings.py:Django 项目的配置文件。
    • mysite/urls.py:Django 项目的 URL 声明,就像你网站的“目录”。
    • mysite/wsgi.py:作为你的项目运行在 WSGI 兼容的Web服务器上的入口。

3、验证Django 项目是否真的创建成功

  • 运行下面的命令:

      python manage.py runserver
    
  • 你应该会看到如下输出:


Performing system checks...

System check identified no issues (0 silenced).
  • 服务器正在运行,浏览器访问 https://127.0.0.1:8000/。你将会看到一个“祝贺”页面,随着一只火箭发射,服务器已经运行了。

  • 更换端口

    • 默认情况下,runserver 命令会将服务器设置为监听本机内部 IP 的 8000 端口。
      如果你想更换服务器的监听端口,请使用命令行参数。举个例子,下面的命令会使服务器监听 8080 端口:

        python manage.py runserver 8080
      
    • 如果你想要修改服务器监听的IP,在端口之前输入新的。比如,为了监听所有服务器的公开IP,使用:

        python manage.py runserver 0:8000
      
    • 0 是 0.0.0.0 的简写

  • 会自动重新加载的服务器 runserver

    • 用于开发的服务器在需要的情况下会对每一次的访问请求重新载入一遍 Python 代码。所以你不需要为了让修改的代码生效而频繁的重新启动服务器。然而,一些动作,比如添加新文件,将不会触发自动重新加载,这时你得自己手动重启服务器。

4、创建应用demo

  • 你的应用可以存放在任何 Python path 中定义的路径。在本次demo中,我们将在你的 manage.py 同级目录下创建投票应用。这样它就可以作为顶级模块导入,而不是 mysite 的子模块。

4.1、创建应用的目录

  • 在manage.py 所在的目录下,运行以下命令来创建一个应用:

      python manage.py startapp polls
    
  • 这将会创建一个 polls 目录(即该应用的顶级目录,名字可自定义),它的目录结构大致如下:


polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py
  • 这个目录结构包括了投票应用的全部内容。(即目前该应用的所有目录和文件)

4.2、编写第一个视图(即页面响应的内容)

  • 让我们开始编写第一个视图吧。打开 polls/views.py,把下面这些 Python 代码输入进去(在index方法下定义响应内容):

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")
  • 这是 Django 中最简单的视图。如果想看见效果,我们需要将一个 URL 映射到它——这就是我们需要 URLconf 的原因了。

  • 为了创建 URLconf,请在 polls 目录里新建一个 urls.py 文件(当前应用的URLconf,用于定义当前应用涉及的访问路径)。你的应用目录现在看起来应该是这样:


polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    urls.py
    views.py
  • 在 polls/urls.py 中,输入如下代码:

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]
  • ’’表示匹配到视图views.index的路径为空,即读取视图时,不需要额外添加路径,views.index:读取到该路径时调用的视图,name:为你的 URL 取名能使你在 Django 的任意地方唯一地引用它。

  • 下一步是要在根 URLconf 文件中指定我们创建的 polls.urls 模块。在 mysite/urls.py 文件的 urlpatterns 列表里插入一个 include(), 如下(即在根URLconf下,把应用的URLconf包含进来,并定义访问的路径):mysite/urls.py


from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('polls/', include('polls.urls')),
    path('admin/', admin.site.urls),
]
  • 函数 include() 允许引用其它 URLconfs。每当 Django 遇到 include() 时,它会截断与此项匹配的 URL 的部分,并将剩余的字符串发送到 URLconf 以供进一步处理。(即当访问匹配到路径polls时,剩余部分的路径则会到polls.urls配置中找相应的视图响应)
  • 我们设计 include() 的理念是使其可以即插即用。因为投票应用有它自己的 URLconf( polls/urls.py ),他们能够被放在 "/polls/" , "/fun_polls/" ,"/content/polls/",或者其他任何路径下,这个应用都能够正常工作。

何时使用 include()

  • 当包括其它 URL 模式时你应该总是使用 include() , admin.site.urls 是唯一例外(因这是Django的mysite容器的配置管理路径)。
  • 你现在把 index 视图添加进了 URLconf。通过以下命令验证是否正常工作:
    运行python manage.py runserver
  • 用你的浏览器访问 http://localhost:8000/polls/,你应该能够看见 "Hello, world. You're at the polls index." ,这是你在 index 视图中定义的。
  • 函数 path() 具有四个参数,两个必须参数:route(路径) 和 view,两个可选参数:kwargs 和 name。其含义如下:
  • path() 参数: route
  • route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。
  • 这些准则不会匹配 GET 和 POST 参数或域名。例如,URLconf 在处理请求 https://www.example.com/myapp/ 时,它会尝试匹配 myapp/ 。处理请求 https://www.example.com/myapp/?page=3 时,也只会尝试匹配 myapp/。
  • path() 参数: view
  • 当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest 对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。
  • path() 参数: kwargs
  • 任意个关键字参数可以作为一个字典传递给目标视图函数。(即给目标视图提供参数)
  • path() 参数: name
  • 为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式。(即给前面的视图定义一个引用名)

5、数据库配置

  • 打开 mysite/settings.py 。这是个包含了 Django 项目设置的 Python 模块。在这个模块里面,进行如下的数据库配置(若不配置数据库,则默认创建sqlite数据库)

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'djangodb',
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': '127.0.0.1',
        'PORT': '3306',
    }
}
  • 文件头部的 INSTALLED_APPS 设置项。这里包括了会在你项目中启用的所有默认 Django 应用,默认开启的某些应用需要至少一个数据表,所以,在使用他们之前需要在数据库中创建一些表。可执行以下命令:(为已安装的应用创建相应的数据库表)

      python manage.py migrate
    
  • 这个 migrate 命令会检查 INSTALLED_APPS 设置,为其中的每个应用创建需要的数据表
    查看 migrate 命令为 INSTALLED_APPS 设置创建了哪些表(sqlite数据库),用以下命令:

      select * from sqlite_master;
    
  • 若是配置的是MySQL,则使用以下命令查看:

      show tables;
    

6、创建模型

  • 在 Django 里写一个数据库驱动的 Web 应用的第一步是定义模型 - 也就是数据库结构设计和附加的其它元数据。在这个模型里面,定义好应用的数据库的字段和类型以及依赖关系。
  • polls/models.py

from django.db import models
import datetime
from django.utils import timezone

# Create your models here.

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

#  用ForeignKey 定义了一个关系。这将告诉 Django,每个 Choice 对象都关联到一个 Question 对象。
class Choice(models.Model):
    # 定义外键和一对一关系的时候需要加on_delete选项,此参数为了避免两个表里的数据不一致问题
    # on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET()五个可选择的值;CASCADE:此值设置,是级联删除。
    # 这里表示通过question字段来关联Question对象,删除Question对象时,也一并删除该对象下的所有Choice信息
    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

7、激活模型(即依据模型生成相应的数据库表)

  • 上面的一小段用于创建模型的代码给了 Django 很多信息,通过这些信息,Django 可以:
    • 为这个应用创建数据库 schema(生成 CREATE TABLE 语句)。
    • 创建可以与 Question 和 Choice 对象进行交互的 Python 数据库 API。
  • 但是在激活模型前,首先得把 polls 应用安装到我们的项目里。即进行如下操作:
  • 1、为了在我们的工程中包含这个应用,我们需要在配置类 INSTALLED_APPS 中添加设置。因为 PollsConfig 类写在文件 polls/apps.py 中(定义了应用名),所以它的点式路径是 'polls.apps.PollsConfig'。在文件 mysite/settings.pyINSTALLED_APPS 子项添加点式路径后,它看起来像这样:

mysite/settings.py

INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
  • 2、将新增的模型内容生成迁移文件

    • 现在你的 Django 项目会包含 polls 应用。接着运行下面的命令:

        python manage.py makemigrations polls
      
    • 通过运行 makemigrations 命令,Django 会检测你对模型文件的修改(在这种情况下,你已经取得了新的),并且把修改的部分储存为一次 迁移。(即会生成一份存储表结构的文件,即在生成数据库表前,所涉及到的表名,字段类型等结构)假如这里生成了迁移文件0001_initial.py

  • 3、将迁移文件转成sql语句

    • sqlmigrate :显示本次迁移命令会执行哪些 SQL 语句。

        python manage.py sqlmigrate polls 0001
      
    • 这里显示0001这个迁移文件执行时,会生成哪些sql语句,以便检查。

    • 请注意以下几点:

      • 输出的内容和你使用的数据库有关。
      • 数据库的表名是由应用名(polls)和模型名的小写形式( question 和 choice)连接而来。(如果需要,你可以自定义此行为。)
      • 主键(IDs)会被自动创建。(当然,你也可以自定义。)
      • 默认的,Django 会在外键字段名后追加字符串 "_id" 。(同样,这也可以自定义。)
      • 外键关系由 FOREIGN KEY 生成。你不用关心 DEFERRABLE 部分,它只是告诉 PostgreSQL,请在事务全都执行完之后再创建外键关系。
      • 生成的 SQL 语句是为你所用的数据库定制的,所以那些和数据库有关的字段类型,比如 auto_increment (MySQL)、 serial (PostgreSQL)和 integer primary key autoincrement (SQLite),Django 会帮你自动处理。那些和引号相关的事情 - 例如,是使用单引号还是双引号 - 也一样会被自动处理。
      • 这个 sqlmigrate 命令并没有真正在你的数据库中的执行迁移 - 它只是把命令输出到屏幕上,让你看看 Django 认为需要执行哪些 SQL 语句。这在你想看看 Django 到底准备做什么,或者当你是数据库管理员,需要写脚本来批量处理数据库时会很有用。
  • 4、将模型内容生成数据库表结构

    • 再次运行 migrate 命令,在数据库里创建新定义的模型的数据表:

        python manage.py migrate
      
    • 执行结果显示Applying polls.0001_initial... OK

    • 这个 migrate 命令选中所有还没有执行过的迁移(Django 通过在数据库中创建一个特殊的表 django_migrations 来跟踪执行过哪些迁移)并应用在数据库上 - 也就是将你对模型的更改同步到数据库结构上。(即将对模型的更改转成数据库的表结构)

    • 迁移是非常强大的功能,它能让你在开发过程中持续的改变数据库结构而不需要重新删除和创建表 - 它专注于使数据库平滑升级而不会丢失数据。改变模型需要这三步:

      • 编辑 models.py 文件,改变模型。
      • 运行 python manage.py makemigrations 为模型的改变生成迁移文件。
      • 运行 python manage.py migrate 来应用数据库迁移。
    • 数据库迁移被分解成生成和应用两个命令是为了让你能够在代码控制系统上提交迁移数据并使其能在多个应用里使用;这不仅仅会让开发更加简单,也给别的开发者和生产环境中的使用带来方便。

8、初试 API(即通过命令来给应用插入数据)

  • 通过以下命令进入交互式 Python 命令行

      python manage.py shell
    
  • 我们使用这个命令而不是简单的使用 "Python" 是因为 manage.py 会设置 DJANGO_SETTINGS_MODULE 环境变量,这个变量会让 Django 根据 mysite/settings.py 文件来设置 Python 包的导入路径。

9、介绍 Django 管理页面

  • 创建一个管理员账号
    • 首先,我们得创建一个能登录管理页面的用户。请运行下面的命令:

        python manage.py createsuperuser
      
    • 键入你想要使用的用户名,然后按下回车键,如下:

        Username: admin
      
    • 然后根据提示输入想要使用的邮件地址:

        Email address: admin@example.com
      
    • 最后一步是输入密码。你会被要求输入两次密码,第二次的目的是为了确认第一次输入的确实是你想要的密码。

  • 启动开发服务器
    • Django 的管理界面默认就是启用的。如果开发服务器未启动,用以下命令启动它:

        python manage.py runserver
      
    • 现在,打开浏览器,转到你本地域名的 "/admin/" 目录,访问管理页面 -- 比如

        http://127.0.0.1:8000/admin/
      

10、向管理页面中加入自己的应用(投票应用)

  • 只需要做一件事:我们得告诉管理页面,问题 Question 对象需要被管理。打开 polls/admin.py 文件,把它编辑成下面这样:
  • polls/admin.py

from django.contrib import admin
from .models import Question

admin.site.register(Question)
  • 然后刷新下页面,即可看到自己的应用。
  • 注意事项:
    • 这个表单是从问题 Question 模型中自动生成的
    • 不同的字段类型(日期时间字段 DateTimeField 、字符字段 CharField)会生成对应的 HTML 输入控件。每个类型的字段都知道它们该如何在管理页面里显示自己。
    • 每个日期时间字段 DateTimeField 都有 JavaScript 写的快捷按钮。日期有转到今天(Today)的快捷按钮和一个弹出式日历界面。时间有设为现在(Now)的快捷按钮和一个列出常用时间的方便的弹出式列表。
  • 页面的底部提供了几个选项:
    • 保存(Save) - 保存改变,然后返回对象列表。
    • 保存并继续编辑(Save and continue editing) - 保存改变,然后重新载入当前对象的修改界面。
    • 保存并新增(Save and add another) - 保存改变,然后添加一个新的空对象并载入修改界面。
    • 删除(Delete) - 显示一个确认删除页面。

11、创建视图(公用界面,响应部分)

  • polls/views.py

from django.shortcuts import render, get_object_or_404

# Create your views here.
from django.http import HttpResponse
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)

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

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.urls 模块里,只要添加几个 url() 函数调用就行:

polls/urls.py

from django.urls import path
from . import views

# 该当前的urls模块定义一个app_name,用于区分当前的URLCONF是属于哪个应用,方便后面模块引用这里的别名如detail时,能准确定位到该别名属于哪个URLCONF
app_name = 'polls'
urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]
  • 然后看看你的浏览器,如果你转到 "/polls/1/" ,Django 将会运行 detail() 方法并且展示你在 URL 里提供的问题 ID。

  • 当某人请求你网站的某一页面时——比如说, "/polls/1/" ,Django 将会载入 mysite.urls 模块,因为这在配置项 ROOT_URLCONF 中设置了。然后 Django 寻找名为 urlpatterns 变量并且按序匹配正则表达式。在找到匹配项 'polls/',它切掉了匹配的文本("polls/"),将剩余文本——"1/",发送至 'polls.urls' URLconf 做进一步处理。在这里剩余文本匹配了 'int:question_id/',使得我们 Django 以如下形式调用 detail():

      detail(request=<HttpRequest object>, question_id=1)
    
  • question_id=1<int:question_id> 匹配生成。使用尖括号“捕获”这部分 URL,且以关键字参数的形式发送给视图函数。上述字符串的 :question_id> 部分定义了将被用于区分匹配模式的变量名(一般与视图中相应函数对应的参数名相同),而 int: 则是一个转换器决定了应该以什么变量类型匹配这部分的 URL 路径。

12、创建一个 templates 目录(存放html模板页面)

  • 你项目的 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 就可以引用到这一模板了。

  • 模板命名空间

    • 虽然我们现在可以将模板文件直接放在 polls/templates 文件夹中(而不是再建立一个 polls 子文件夹),但是这样做不太好。Django 将会选择第一个匹配的模板文件,如果你有一个模板文件正好和另一个应用中的某个模板文件重名,Django 没有办法 区分 它们。我们需要帮助 Django 选择正确的模板,最简单的方法就是把他们放入各自的 命名空间 中,也就是把这些模板放入一个和 自身 应用重名的子文件夹里。
  • 在模板文件中写入以下代码:


polls/templates/polls/index.html

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
{#        href="{% url ‘name’ params %}" 其中:name为url.py 文件中某个url配置的name别名,params某个url对应的各个参数具体值;而polls:detail表示某个应用的某个别名#}
        <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}
  • 然后,就可以在 polls/views.py 里的 index 视图来使用该模板了:

polls/views.py

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    # 这里的字典项context,在后面的模板页面如index.html引用时,会把这里的键如 latest_question_list的值 传给index.html中对应的变量
    context = {'latest_question_list': latest_question_list}
	return render(request, 'polls/index.html', context)
  • 上述代码的作用是,载入 polls/index.html 模板文件,并且向它传递一个上下文(context)。这个上下文是一个字典,它将模板内的变量映射为 Python 对象。
  • 用你的浏览器访问 "/polls/" ,你将会看见一个无序列表。
  • 一个快捷函数: render()
  • 「载入模板,填充上下文,再返回由它生成的 HttpResponse 对象」是一个非常常用的操作流程。于是 Django 提供了一个快捷函数,我们用它来重写 index() 视图:
    这个render()函数,拿请求对象作为它的第一个参数,一个模板名作为它的第二个参数,一个字典作为它的第三个参数,把字典里的键值对传给模板对象,然后它返回一个该模板html对应的响应对象。
  • 一个快捷函数: get_object_or_404()
def detail(request, question_id):

    # get_object_or_404()函数,拿一个Django模型作为它的第一个参数,和一个任意数量的关键字参数,它是通过模型里的get()函数来管理的,如果对象不存在,则抛出 Http404异常。
    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() 一样,除了 get() 函数被换成了 filter() 函数。如果列表为空的话会抛出 Http404 异常。
  • 使用模板系统
  • 回过头去看看我们的 detail() 视图。它向模板传递了上下文变量 question 。下面是 polls/detail.html 模板里正式的代码:

polls/templates/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 对象使用字典查找(也就是使用 obj.get(str) 操作),如果失败了就尝试属性查找(也就是 obj.str 操作),结果是成功了。如果这一操作也失败的话,将会尝试列表查找(也就是 obj[int] 操作)。

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

  • 在模板文件中通过应用名来引用和定位应用URLconf 中的别名


polls/templates/polls/index.html

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
{#        href="{% url ‘name’ params %}" 其中:name为url.py 文件中某个url配置的name别名,params某个url对应的各个参数具体值;而polls:detail表示某个应用的某个别名#}
        <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

13、表单处理

  • Polls/urls.py

# as_view方法,这个方法只做一件事就是返回一个闭包,这个闭包像视图函数一样接收url解析器传送过来的参数
# 如果我们通过 def 定义视图函数,那么传入的这个可调用对象就是这个函数本身;而如果我们定义的是类视图,则必须调用类视图的 as_view 方法返回一个根据这个类生成的可调用对象
# DetailView 会根据 URLConf 中的 <int:pk> 或 <slug:slug> 筛选出一个 object;这里是依据主键值PK筛选出视图中model指定的对象
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'),
]
  • Polls/views.py

# 通用的基于类的视图(Class Based View)如下:
# 展示对象列表(比如所有用户,所有文章)- ListView
# 展示某个对象的详细信息(比如用户资料,比如文章详情) - DetailView
# 通过表单创建某个对象(比如创建用户,新建文章)- CreateView
# 通过表单更新某个对象信息(比如修改密码,修改文字内容)- UpdateView
# 用户填写表单后转到某个完成页面 - FormView
# 删除某个对象 - DeleteView
# 通用的基于类的视图(Class Based View)如下:
# 展示对象列表(比如所有用户,所有文章)- ListView
# 展示某个对象的详细信息(比如用户资料,比如文章详情) - DetailView
# 通过表单创建某个对象(比如创建用户,新建文章)- CreateView
# 通过表单更新某个对象信息(比如修改密码,修改文字内容)- UpdateView
# 用户填写表单后转到某个完成页面 - FormView
# 删除某个对象 - DeleteView
# template_name、context_object_name、model这些名字都是固定的
# 用generic继承基类视图,这里表示继承基类视图ListView
class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    # listview默认使用object_list作为上下文变量。可使用context_object_name重命名。
    context_object_name = 'latest_question_list'

    # ListView视图默认取出该表所有数据。想要过滤自定义的数据,只能在get_queryset()中过滤
    # get_context_data(self,**kwargs)#这个方法用来添加额外的内容到上下文变量中。
    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]


# DetailView用来展示一个具体对象的详细信息。它需要URL提供访问某个对象的具体参数(如pk, slug值);即用于显示某一 Model 中的一个 object 的详细信息。
# 用属性 model 或 queryset 指定要操作的 Model 或 queryset;
# DetailView 会根据 URLConf 中的 <int:pk> 或 <slug:slug> 筛选出一个 object;
class DetailView(generic.DetailView):
    # model指定了数据表。他的功能相当于取出了Question中的所有数据;也即定义要展示的数据模型
    model = Question
    # template_name指定跳转页面;也即定义要将数据模型展示在哪个模板上,用数据模型的数据,覆盖掉该模板上的属性变量
    template_name = 'polls/detail.html'


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


def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        # request.POST是一个类字典对象,让你可以通过关键字的名字获取提交的数据。 这个例子中
        # request.POST['choice']以字符串形式返回选择的Choice的ID。 request.POST的值永远是字符串。即获取经由choice关键字提交过来的内容
        # request.POST,request.GET: 分别可获得POST或GET方法的参数值,如上 request.POST['choice'] 可取得POST请求中,name值为choice的Value值。若POST参数中没有choice,则会产生KeyError。
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        # KeyError异常时,返回detail页面,并传递question和error_message这两个参数给detail页面
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        # 没有异常,则选中的选项的得票数加一
        selected_choice.votes += 1
        # 调用save()方法,把对象保存到数据库中
        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.
        # HttpResponseRedirect:响应重定向,重定向的 URL 将调用 'results' 视图来显示最终的页面;只接收一个参数:用户将要被重定向的 URL
        # 在 HttpResponseRedirect 的构造函数中使用 reverse() 函数。这个函数避免了我们在视图函数中硬编码 URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。
        # 这里的polls:results表示,跳转到URLconf中app_name='polls',别名为results的链接,并给该链接路径传入需要的参数question.id
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
  • Polls/results.html

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

<ul>
{% for choice in question.choice_set.all %}
{# 过滤器{{ 列表或数字|pluralize }} 单词的复数形式,如列表字符串个数大于1,返回s,否则返回空串#}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
  • Polls/detail.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 %} 循环中发生的函数调用:question.choice_set.all 被解释为 Python 代码 question.choice_set.all() , #}
{#将会返回一个可迭代的 Choice 对象,这一对象可以在 {% for %} 标签内部使用。#}
{#forloop.counter 表示 for 标签已经循环多少次#}
{#{% csrf_token %} 模板标签防御跨站请求伪造#}
{#<label>标签起绑定关联文本作用;<label>标签在单选按钮和复选按钮上经常被使用,使用该标签后,你点击单选按钮或复选按钮的文本也是可以选中的。#}
{#<label for="关联控件的id" form="所属表单id列表">文本内容</label>#}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" >
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
<input type="submit" value="Vote">
</form>

14、自动化测试django应用

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

from django.test import TestCase

# Create your tests here.

import datetime
from django.test import TestCase
from django.utils import timezone
from django.urls import reverse
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)
        future_question = Question(pub_date=time)
        # 断言是否是False
        self.assertIs(future_question.was_published_recently(), True)

    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)


# 视图测试
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.>']
        )


class QuestionDetailViewTests(TestCase):

    def test_future_question(self):
        """
        The detail view of a question with a pub_date in the future
        returns a 404 not found.
        """
        future_question = create_question(question_text='Future question.', days=5)
        url = reverse('polls:detail', args=(future_question.id,))
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_past_question(self):
        """
        The detail view of a question with a pub_date in the past
        displays the question's text.
        """
        past_question = create_question(question_text='Past Question.', days=-5)
        url = reverse('polls:detail', args=(past_question.id,))
        response = self.client.get(url)
        self.assertContains(response, past_question.question_text)
  • 我们创建了一个 django.test.TestCase 的子类,并添加了一个方法,此方法创建一个 pub_date 时未来某天的 Question 实例。然后检查它的 was_published_recently() 方法的返回值——它 应该 是 False。

  • 运行测试代码

      python manage.py test polls
    
  • 运行结果如下:


E:\Python_Selenium\project\DjangoProject\mysite>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.QuestionMode
lTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "E:\Python_Selenium\project\DjangoProject\mysite\polls\tests.py", line 24
, in test_was_published_recently_with_future_question
    self.assertIs(future_question.was_published_recently(), True)
AssertionError: False is not True

----------------------------------------------------------------------
Ran 10 tests in 0.703s

FAILED (failures=1)
Destroying test database for alias 'default'...

15、样式应用

  • polls/static/polls/style.css

/*li表示列表li的样式*/
li a {
    color: green;
}
/*body表示页面主体部分的样式,images/background.gif表示相对于当前文件路径的相对路径,静态文件之间的相互引用,使用相对于当前文件路径的相对路径即可*/
body {
    background: white url("images/background.gif") no-repeat;
}

16、自定义后台表单

  • polls/admin.py

from django.contrib import admin

# Register your models here.

from .models import Question

# admin.site.register(Question)
# class QuestionAdmin(admin.ModelAdmin):
#     # fields:控制要显示的字段,可用小括号,也可用中括号
#     fields = ['pub_date', 'question_text']

# fieldsets 元组中的第一个元素是字段集的标题,第二个元素是该标题下要显示的字段
class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('Date information', {'fields': ['pub_date']}),
    ]

# 注册模型Question,并且按QuestionAdmin定义的样式显示(这里是按里面定义的次序排列模型字段)
# 这里为:注册Question模型,并按QuestionAdmin规定的样式显示
admin.site.register(Question, QuestionAdmin)
  • 添加关联的对象
    • polls/admin.py

# 第二种添加关联对象,两个类的普通关联使用StackedInline;TabularInline 是表格的方式显示;这里通过 TabularInline(替代 StackedInline ),关联对象以一种表格式的方式展示,显得更加紧凑
class ChoiceInline(admin.TabularInline):
    # 需要将哪个类嵌入,这里为嵌入3个Choice类
    model = Choice
    # 嵌入的数量
    extra = 3


# fieldsets 元组中的第一个元素是字段集的标题,第二个元素是该标题下要显示的字段
# classes,一个列表包含额外的CSS classes 应用到 fieldset;通过默认的管理站点样式表定义的两个有用的classes 是 collapse 和 wide. Fieldsets 使用 collapse(默认展开) 样式将会在初始化时展开并且替换掉一个 “click to expand” 链接. Fieldsets 使用 wide 样式将会有额外的水平空格.
class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
    # inlines内联,关联上面定义的类;这里是本类内联ChoiceInline类;这样,每次进入Question这个界面的时候,都会伴随着三个新增的Choice插槽
    inlines = [ChoiceInline]
    # list_display,控制在管理员的更改列表页面上显示哪些字段
    list_display = ('question_text', 'pub_date', 'was_published_recently')
    # list_filter,列表过滤器,这里表示按pub_date字段过滤列表
    list_filter = ['pub_date']
    # search_fields,搜索框,这里表示针对question_text字段的搜索框
    search_fields = ['question_text']


# 注册模型Question,并且按QuestionAdmin定义的样式显示(这里是按里面定义的次序排列模型字段)
# 这里为:注册Question模型,并按QuestionAdmin规定的样式显示
admin.site.register(Question, QuestionAdmin)

# # 第一种添加关联对象Choice,仿照我们向后台注册 Question 一样注册 Choice
# admin.site.register(Choice)
  • 自定义后台界面和风格
    • 在你的工程目录(指包含 manage.py 的那个文件夹)内创建一个名为 templates 的目录。模板可放在你系统中任何 Django 能找到的位置。(谁启动了 Django,Django 就以他的用户身份运行。)不过,把你的模板放在工程内会带来很大便利,推荐你这样做。
      然后在 templates 目录内创建名为 admin 的目录,随后,将存放 Django 默认模板的目录(django/contrib/admin/templates)内的模板文件 admin/base_site.html 复制到这个目录内。最后的项目路径如下:

        \mysite\templates\admin\base_site.html
      
    • 接着,用你站点的名字替换文件内的 {{ site_header|default:_('Django administration') }}(包含大括号)。完成后,你应该看到如下代码:


{% block branding %}
<h1 >Polls Administration</a></h1>
{% endblock %}
  • 然后打开你的设置文件(mysite/settings.py,牢记),在 TEMPLATES 设置中添加 DIRS 选项:
  • mysite/settings.py

# 将 APP_DIRS 设置成了 True。这一选项将会让 DjangoTemplates 在每个 INSTALLED_APPS 文件夹中寻找 "templates" 子目录
# DIRS默认是空的,Django是怎么找到默认的后台模板的?因为APP_DIRS被置为True,Django会自动在每个应用包内递归查找templates / 子目录(不要忘了
# django.contrib.admin也是一个应用)
# DIRS 是一个包含多个系统目录的文件列表,用于在载入 Django 模板时使用,是一个待搜索路径
# BASE_DIR是你的工程目录(指包含 manage.py 的那个文件夹)
# 组织模板:就像静态文件一样,我们 可以 把所有的模板文件放在一个大模板目录内,这样它也能工作的很好。
# 但是,属于特定应用的模板文件最好放在应用所属的模板目录(例如 polls/templates),而不是工程的模板目录(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',
            ],
        },
    },
]
  • 注意,所有的 Django 默认后台模板均可被复写。若要复写模板,像你修改 base_site.html 一样修改其它文件——先将其从默认目录中拷贝到你的自定义目录,再做修改。

  • 自定义后台主页

    • 在类似的说明中,你可能想要自定义 Django 后台索引页的外观。
    • 默认情况下,它展示了所有配置在 INSTALLED_APPS 中,已通过后台应用注册,按拼音排序的应用。你可能想对这个页面的布局做重大的修改。毕竟,索引页是后台的重要页面,它应该便于使用。
    • 需要自定义的模板是 admin/index.html。(像上一节修改 admin/base_site.html 那样修改此文件——从默认目录中拷贝此文件至自定义模板目录)。打开此文件,你将看到它使用了一个叫做 app_list 的模板变量。这个变量包含了每个安装的 Django 应用。你可以用任何你期望的硬编码链接(链接至特定对象的管理页)替代使用这个变量。

17、编写可重用程序

打包应用程序

  • 打包环境,需要安装 pip install setuptools
    1. 首先,在你的 Django 项目目录外创建一个名为 django-polls 的文件夹,用于盛放 polls应用。
    2. 将 polls 目录移入 django-polls 目录
    3. 创建一个名为 django-polls/README.rst 的文件
    4. 创建一个 django-polls/LICENSE 文件。
    5. 下一步我们将创建 setup.py 用于说明如何构建和安装应用的细节。
    6. 默认包中只包含 Python 模块和包。为了包含额外文件,我们需要创建一个名为 MANIFEST.in 的文件。
    7. 试着构建你自己的应用包通过 ptyhon setup.py sdist (在 django-polls目录内)。这将创建一个名为 dist 的目录并构建你自己的应用包, django-polls-0.1.tar.gz。

使用打包程序

  1. 安装打包程序

     pip install --user django-polls/dist/django-polls-0.1.tar.gz
    
  2. 将程序注册到django平台

    • \mysite\mysite\settings.py文件中,添加安装应用,如下
      Django的安装、使用详解、自动化测试应用以及程序打包
      Django的安装、使用详解、自动化测试应用以及程序打包
  3. 将访问该应用的URI添加到django的URLconf中,在\mysite\mysite\urls.py文件中如下:
    Django的安装、使用详解、自动化测试应用以及程序打包

  4. 重启django平台,访问该URI即可打开该应用。