通用技术 《Django By Example》第一章 创建一个 blog 应用

夜夜月月 · 2018年09月28日 · 最后由 群主是狗 回复于 2018年09月30日 · 3752 次阅读

书籍出处:https://www.packtpub.com/web-development/django-example
原作者:Antonio Melé

(译者注:本人目前在杭州某家互联网公司工作,岗位是测试研发,非常喜欢 python,目前已经使用 Django 为公司内部搭建了几个自动化平台,因为没人教没人带,基本靠野路子自学,走过好多弯路,磕磕碰碰一路过来,前段时间偶尔看到《Django By Example》这本书,瞬间泪流满面,当初怎么没有找到这么好的 Django 教程。在看书的过程中不知道怎么搞的突然产生了翻译全书的想法,正好网上找了下也没有汉化的版本,所以准备踏上这条不归路。鉴于本人英文水平极低(四级都没过),单纯靠着有道词典和自己对上下文的理解以及对书中每行代码都保证敲一遍并运行的情况下,请各位在读到语句不通的时候或看不懂的地方请告诉我,我会及时进行改正。翻译全书,主要也是为了培养自己的英文阅读水平(口语就算了),谁叫好多最新最有用的计算机文档都是用英文写的,另外也可以培养自己的耐心,还可以分享给其他人,就这样。)

第一章

创建一个 blog 应用

在这本书中,你将学习如何创建完整的 Django 项目,可以在生产环境中使用。假如你还没有安装 Django,在本章的第一部分你将学习如何安装。本章会覆盖如何使用 Django 去创建一个简单的 blog 应用。本章的目的是使你对该框架的工作有个基本概念,了解不同的组件之间是如何产生交互,并且教你一些技能通过使用一些基本功能方便地创建 Djang 项目。你会被引导创建一个完整的项目但是不会对所有的细节都进行详细说明。不同的框架组件将在本书接下来的章节中进行介绍。
本章会覆盖以下几点:

  • 安装 Django 并创建你的第一个项目
  • 设计模型(models)并且生成模型(model)数据库迁移
  • 给你的模型(models)创建一个管理站点
  • 使用查询集(QuerySet)和管理器(managers)
  • 创建视图(views),模板(templates)和 URLs
  • 给列表视图(views)添加页码
  • 使用 Django 内置的视图(views)

安装 Django

如果你已经安装好了 Django,你可以直接略过这部分跳到创建你的第一个项目。Django 是一个 Python 包因此可以安装在任何的 Python 的环境中。如果你还没有安装 Django,这里有一个快速的指南帮助你安装 Django 用来本地开发。

Django 需要在 Python2.7 或者 3 版本上才能更好的工作。在本书的例子中,我们将使用 Python 3。如果你使用 Linux 或者 Max OSX,你可能已经有安装好的 Python。如果你不确定你的计算机中是否安装了 Python,你可以在终端中输入 python 来确定。如果你看到以下类似的提示,说明你的计算机中已经安装好了 Python:

Python 3.5.0 (v3.5.0:374f501f4567, Sep 12 2015, 11:00:19)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

如果你计算机中安装的 Python 版本低于 3,或者没有安装,下载并安装 Python 3.5.0 从http://www.python.org/download/ (译者注:最新已经是 3.6.0 了,Django2.0 将不再支持 pytyon2.7,所以大家都从 3 版本以上开始学习吧)

由于你使用的是 Python3,所以你没必要再安装一个数据库。这个 Python 版本自带 SQLite 数据库。SQLLite 是一个轻量级的数据库,你可以在 Django 中进行使用用来开发。如果你准备在生产环境中部署你的应用,你应该使用一个更高级的数据库,比如 PostgreSQL,MySQL 或 Oracle。你能获取到更多的信息关于数据库和 Django 的集成通过访问 https://docs.djangoproject.com/en/1.8/topics/install/#database-installation

创建一个独立的 Python 环境

强烈建议你使用 virtualenv 来创建独立的 Python 环境,这样你可以使用不同的包版本对应不同的项目,这比直接在真实系统中安装 Python 包更加的实用。另一个高级之处在于当你使用 virtualenv 你不需要任何管理员权限来安装 Python 包。在终端中运行以下命令来安装 virtualenv:

pip install virtualenv

(译者注:如果你本地有多个 python 版本,注意 Python3 的 pip 命令可能是 pip3)

当你安装好 virtualenv 之后,通过以下命令来创建一个独立的环境:

virtualenv my_env

以上命令会创建一个包含你的 Python 环境的 my_env/目录。当你的 virtualenv 被激活的时候所有已经安装的 Python 库都会带入 my_env/lib/python3.5/site-packages 目录中。
如果你的系统自带 Python2.X 然后你又安装了 Python3.X,你必须告诉 virtualenv 使用后者 Python3.X。通过以下命令你可以定位 Python3 的安装路径然后使用该安装路径来创建 virtualenv:

zenx\$ *which python3* 
/Library/Frameworks/Python.framework/Versions/3.5/bin/python3
zenx\$ *virtualenv my_env -p 
/Library/Frameworks/Python.framework/Versions/3.5/bin/python3*

通过以下命令来激活你的 virtualenv:

source my_env/bin/activate

shell 提示将会附上激活的 virtualenv 名,被包含在括号中,如下所示:

(my_evn) laptop:~ zenx$

你可以使用deactivate命令随时停用你的 virtualenv。

你可以获取更多的信息关于 virtualenv 通过访问 https://virtualenv.pypa.io/en/latest/

在 virtualenv 之上,你可以使用 virtualenvwrapper 工具。这个工具提供一些封装用来方便的创建和管理你的虚拟环境。你可以在 http://virtualenvwrapper.readthedocs.org/en/latest/ 下载该工具。

使用 pip 安装 Django

(译者注:请注意以下的操作都在激活的虚拟环境中使用)

pip 是安装 Django 的第一选择。Python3.5 自带预安装的 pip,你可以找到 pip 的安装指令通过访问 https://pip.pypa.io/en/stable/installing/ 。运行以下命令通过 pip 安装 Django:

pip install Django==1.8.6

Django 将会被安装在你的虚拟环境的 Python 的site-packages/目录下。

现在检查 Django 是否成功安装。在终端中运行python并且导入 Django 来检查它的版本:

>>> import django
>>> django.VERSION
DjangoVERSION(1, 8, 5, 'final', 0)

如果你获得了以上输出,Django 已经成功安装在你的机器中。

Django 也可以使用其他方式来安装。你可以找到更多的信息通过访问 https://docs.djangoproject.com/en/1.8/topics/install/

创建你的第一个项目

我们的第一个项目将会是一个完整的 blog 站点。Django 提供了一个命令允许你方便的创建一个初始化的项目文件结构。在终端中运行以下命令:

django-admin startproject mysite

该命令将会创建一个名为mysite的项目。
让我们来看下生成的项目结构:

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

让我们来了解一下这些文件:

  • manage.py:一个实用的命令行,用来与你的项目进行交互。它是一个对django-admin.py工具的简单封装。你不需要编辑这个文件。
  • mysite/:你的项目目录,由以下的文件组成:
    • init.py:一个空文件用来告诉 Python 这个mysite目录是一个 Python 模块。
    • settings.py:你的项目的设置和配置。里面包含一些初始化的设置。
    • urls.py:你的 URL 模式存放的地方。这里定义的每一个 URL 都映射一个视图(view)。
    • wsgi.py:配置你的项目运行如同一个 WSGI 应用。

默认生成的settings.py文件包含一个使用一个 SQLite 数据库的基础配置以及一个 Django 应用列表,这些应用会默认添加到你的项目中。我们需要为这些初始应用在数据库中创建表。

打开终端执行以下命令:

cd mysite
python manage.py migrate

你将会看到以下的类似输出:

Rendering model states... DONE
Applying contenttypes.ooo1_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length...OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying sessions.0001_initial... OK

这些初始应用表将会在数据库中创建。过一会儿你就会学习到一些关于migrate的管理命令。

运行开发服务器

Django 自带一个轻量级的 web 服务器来快速运行你的代码,不需要花费额外的时间来配置一个生产服务器。当你运行 Django 的开发服务器,它会一直检查你的代码变化。当代码有改变,它会自动重启,将你从手动重启中解放出来。但是,它可能无法注意到一些操作,例如在项目中添加了一个新文件,所以你在某些场景下还是需要手动重启。

打开终端,在你的项目主目录下运行以下代码来开启开发服务器:

python manage.py runserver

你会看到以下类似的输出:

Performing system checks...

System check identified no issues (0 silenced).
November 5, 2015 - 19:10:54
Django version 1.8.6, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

现在,在浏览器中打开 http://127.0.0.1:8000/ ,你会看到一个告诉你项目成功运行的页面,如下图所示:
django-1-1

你可以指定 Django 在定制的 host 和端口上运行开发服务,或者告诉它你想要运行你的项目通过读取一个不同的配置文件。例如:你可以运行以下 manage.py 命令:

python manage.py runserver 127.0.0.1:8001 \
--settings=mysite.settings

这个命令迟早会对处理需要不同设置的多套环境启到作用。记住,这个服务器只是单纯用来开发,不适合在生产环境中使用。为了在生产环境中部署 Django,你需要使用真实的 web 服务让它运行成一个 WSGI 应用例如 Apache,Gunicorn 或者 uWSGI*(译者注:强烈推荐 nginx+uwsgi+Django)*。你能够获取到更多关于如何在不同的 web 服务中部署 Django 的信息,访问 https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/

本书外额外的需要下载的章节第十三章,Going Live包含为你的 Django 项目设置一个生产环境。

项目设置

让我们打开 settings.py 文件来看看你的项目的配置。在该文件中有许多设置是 Django 内置的,但这些只是所有 Django 可用配置的一部分。你可以通过访问 https://docs.djangoproject.com/en/1.8/ref/settings/ 看到所有的设置和它们默认的值。

以下列出的设置非常值得一看:

  • DEBUG 一个布尔型用来开启或关闭项目的 debug 模式。如果设置为 True,当你的应用抛出一个未被捕获的异常时 Django 将会显示一个详细的错误页面。当你准备部署项目到生产环境,请记住一定要关闭 debug 模式。永远不要在生产环境中部署一个打开 debug 模式的站点因为那会暴露你的项目中的敏感数据。
  • ALLOWED_HOSTS 当 debug 模式开启或者运行测试的时候不会起作用(译者注:最新的 Django 版本中,不管有没有开启 debug 模式该设置都会启作用)。一旦你准备部署你的项目到生产环境并且关闭了 debug 模式,为了允许访问你的 Django 项目你就必须添加你的域或 host 在这个设置中。
  • INSTALLED_APPS 这个设置你在所有的项目中都需要编辑。这个设置告诉 Django 有哪些应用会在这个项目中激活。默认的,Django 包含以下应用:
    • django.contrib.admin:这是一个管理站点。
    • django.contrib.auth:这是一个权限框架。
    • django.contrib.contenttypes:这是一个内容类型的框架。
    • django.contrib.sessions:这是一个会话(session)框架。
    • django.contrib.messages:这是一个消息框架。
    • django.contrib.staticfiles:这是一个用来管理静态文件的框架
  • MIDDLEWARE_CLASSES 是一个包含可执行中间件的元组。
  • ROOT_URLCONF 指明你的应用定义的主 URL 模式存放在哪个 Python 模块中。
  • DATABASES 是一个包含了所有在项目中使用的数据库的设置的字典。里面一定有一个默认的数据库。默认的配置使用的是 SQLite3 数据库。
  • LANGUAGE_CODE 定义 Django 站点的默认语言编码。

不要担心你目前还看不懂这些设置的含义。你将会在之后的章节中熟悉这些设置。

项目和应用

贯穿全书,你会反复的读到项目和应用的地位。在 Django 中,一个项目被认为是一个安装了一些设置的 Django;一个应用是一个包含模型(models),视图(views),模板(templates)以及 URLs 的组合。应用之间的交互通过 Django 框架提供的一些特定功能,并且应用可能被各种各样的项目重复使用。你可以认为项目就是你的网站,这个网站包含多个应用,例如 blog,wiki 或者论坛,这些应用都可以被其他的项目使用。(译者注:我去,我竟然漏翻了这一节- -|||,罪过罪过,阿米头发)

创建一个应用

现在让我们创建你的第一个 Django 应用。我们将要创建一个勉强凑合的 blog 应用。在你的项目主目录下,运行以下命令:

python manage.py startapp blog

这个命令会创建 blog 应用的基本目录结构,如下所示:

blog/
    __init__.py
    admin.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

这些文件的含义:

  • admin.py: 在这儿你可以注册你的模型(models)并将它们包含到 Django 的管理页面中。使用 Django 的管理页面是可选的。
  • migrations: 这个目录将会包含你的应用的数据库迁移。Migrations 允许 Django 跟踪你的模型(model)变化并因此来同步数据库。
  • models.py: 你的应用的数据模型(models)。所有的 Django 应用都需要拥有一个models.py文件,但是这个文件可以是空的。
  • tests.py:在这儿你可以为你的应用创建测试。
  • views.py:你的应用逻辑将会放在这儿。每一个视图(view)都会接受一个 HTTP 请求,处理该请求,最后返回一个响应。

设计 blog 数据架构

我们将要开始为你的 blog 设计初始的数据模型(models)。一个模型(model)就是一个 Python 类,该类继承了django.db.models.model,在其中的每一个属性表示一个数据库字段。Django 将会为models.py中的每一个定义的模型(model)创建一张表。当你创建好一个模型(model),Django 会提供一个非常实用的 API 来方便的查询数据库。

首先,我们定义一个POST模型(model)。在 blog 应用下的models.py文件中添加以下内容:

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User


class Post(models.Model):
    STATUS_CHOICES = (
        ('draft', 'Draft'),
        ('published', 'Published'),
    )
    title = models.CharField(max_length=250)
    slug = models.SlugField(max_length=250,
                            unique_for_date='publish')
    author = models.ForeignKey(User,
                                related_name='blog_posts')
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=10,
                                choices=STATUS_CHOICES,
                                default='draft')

    class Meta:
        ordering = ('-publish',)

    def __str__(self):
        return self.title

这就是我们给 blog 帖子使用的基础模型(model)。让我们来看下刚才在这个模型(model)中定义的各个字段含义:

  • title: 这个字段对应帖子的标题。它是CharField,在 SQL 数据库中会被转化成 VARCHAR。
  • slug:这个字段将会在 URLs 中使用。slug 就是一个短标签,该标签只包含字母,数字,下划线或连接线。我们将通过使用 slug 字段给我们的 blog 帖子构建漂亮的,友好的 URLs。我们给该字段添加了unique_for_date参数,这样我们就可以使用日期和帖子的 slug 来为所有帖子构建 URLs。在相同的日期中 Django 会阻止多篇帖子拥有相同的 slug。
  • author:这是一个ForeignKey。这个字段定义了一个多对一(many-to-one)的关系。我们告诉 Django 一篇帖子只能由一名用户编写,一名用户能编写多篇帖子。根据这个字段,Django 将会在数据库中通过有关联的模型(model)主键来创建一个外键。在这个场景中,我们关联上了 Django 权限系统的User模型(model)。我们通过related_name属性指定了从UserPost的反向关系名。我们将会在之后学习到更多关于这方面的内容。
  • body:这是帖子的主体。它是TextField,在 SQL 数据库中被转化成TEXT
  • publish:这个日期表明帖子什么时间发布。我们使用 Djnago 的timezonenow方法来设定默认值。This is just a timezone-aware datetime.now*(译者注:这句该咋翻译好呢)*。
  • created:这个日期表明帖子什么时间创建。因为我们在这儿使用了auto_now_add,当一个对象被创建的时候这个字段会自动保存当前日期。
  • updated:这个日期表明帖子什么时候更新。因为我们在这儿使用了auto_now,当我们更新保存一个对象的时候这个字段将会自动更新到当前日期。
  • status:这个字段表示当前帖子的展示状态。我们使用了一个choices参数,这样这个字段的值只能是给予的选择参数中的某一个值。(译者注:传入元组,比如(1,2),那么该字段只能选择 1 或者 2,没有其他值可以选择)

就像你所看到的的,Django 内置了许多不同的字段类型给你使用,这样你就能够定义你自己的模型(models)。通过访问 https://docs.djangoproject.com/en/1.8/ref/models/fields/ 你可以找到所有的字段类型。

在模型(model)中的类Meta包含元数据。我们告诉 Django 查询数据库的时候默认返回的是根据publish字段进行降序排列过的结果。我们使用负号来指定进行降序排列。

str()方法是当前对象默认的可读表现。Django 将会在很多地方用到它例如管理站点中。

如果你之前使用过 Python2.X,请注意在 Python3 中所有的 strings 都使用 unicode,因此我们只使用str()方法。unicode()方法已经废弃。(译者注:Python3 大法好,Python2 别再学了,直接学 Python3 吧)

在我们处理日期之前,我们需要下载pytz模块。这个模块给 Python 提供时区的定义并且 SQLite 也需要它来对日期进行操作。在终端中输入以下命令来安装pytz

pip install pytz

Django 内置对时区日期处理的支持。你可以在你的项目中的settings.py文件中通过USE_TZ来设置激活或停用对时区的支持。当你通过startproject命令来创建一个新项目的时候这个设置默认为True

激活你的应用

为了让 Django 能保持跟踪你的应用并且根据你的应用中的模型(models)来创建数据库表,我们必须激活你的应用。因此,编辑settings.py文件,在INSTALLED_APPS设置中添加blog。看上去如下所示:

INSTALLED_APPS = ( 
    'django.contrib.admin',    
    'django.contrib.auth', 
    'django.contrib.contenttypes', 
    'django.contrib.sessions', 
    'django.contrib.messages', 
    'django.contrib.staticfiles',
    'blog',
 )

(译者注:该设置中应用的排列顺序也会对项目的某些方面产生影响,具体情况后几章会有介绍,这里提醒下)

现在 Django 已经知道在项目中的我们的应用是激活状态并且将会对其中的模型(models)进行自审。

创建和进行数据库迁移

让我们为我们的模型(model)在数据库中创建一张数据表格。Django 自带一个数据库迁移(migration)系统来跟踪你对模型(models)的修改,然后会同步到数据库。migrate命令会应用到所有在INSTALLED_APPS中的应用,它会根据当前的模型(models)和数据库迁移(migrations)来同步数据库。

首先,我们需要为我们刚才创建的新模型(model)创建一个数据库迁移(migration)。在你的项目主目录下,执行以下命令:

python manage.py makemigrations blog

你会看到以下输出:

Migrations for 'blog':
    0001_initial.py;
        - Create model Post

Django 在 blog 应用下的 migrations 目录中创建了一个0001——initial.py文件。你可以打开这个文件来看下一个数据库迁移的内容。

让我们来看下 Django 根据我们的模型(model)将会为在数据库中创建的表而执行的 SQL 代码。sqlmigrate命令带上数据库迁移(migration)的名字将会返回它们的 SQL,但不会立即去执行。运行以下命令来看下输出:

python manage.py sqlmigrate blog 0001

输出类似如下:

BEGIN;
CREATE TABLE "blog_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(250) NOT NULL, "slug" varchar(250) NOT NULL, "body" text NOT NULL, "publish" datetime NOT NULL, "created" datetime NOT NULL, "updated" datetime NOT NULL, "status" varchar(10) NOT NULL, "author_id" integer NOT NULL REFERENCES "auth_user" ("id"));
CREATE INDEX "blog_post_2dbcba41" ON "blog_post" ("slug");
CREATE INDEX "blog_post_4f331e2f" ON "blog_post" ("author_id");
COMMIT;

Django 会根据你正在使用的数据库进行以上精准的输出。以上 SQL 语句是为 SQLite 数据库准备的。如你所见,Django 生成的表名前缀为应用名之后跟上模型(model)的小写(blog_post),但是你也可以通过在模型(models)的Meta类中使用db_table属性来指定表名。Django 会自动为每个模型(model)创建一个主键,但是你也可以通过在模型(model)中的某个字段上设置primarry_key=True来指定主键。

让我们根据新模型(model)来同步数据库。运行以下的命令来应用已存在的数据迁移(migrations):

python manage.py migrate

你应该会看到以下行跟在输出的末尾:

Applying blog.0001_initial... OK

我们刚刚为INSTALLED_APPS中所有的应用进行了数据库迁移(migrations),包括我们的blog应用。在进行了数据库迁移(migrations)之后,数据库会反映我们模型的当前状态。

如果为了添加,删除,或是改变了存在的模型(models)中字段,或者你添加了新的模型(models)而编辑了models.py文件,你都需要通过使用makemigrations命令做一次新的数据库迁移(migration)。数据库迁移(migration)允许 Django 来保持对模型(model)改变的跟踪。之后你必须通过migrate命令来保持数据库与我们的模型(models)同步。

为你的模型(models)创建一个管理站点

现在我们已经定义好了Post模型(model),我们将要创建一个简单的管理站点来管理 blog 帖子。Django 内置了一个管理接口,该接口对编辑内容非常的有用。这个 Django 管理站点会根据你的模型(model)元数据进行动态构建并且提供一个可读的接口来编辑内容。你可以对这个站点进行自由的定制,配置你的模型(models)在其中如何进行显示。

请记住,django.contrib.admin已经被包含在我们项目的INSTALLED_APPS设置中,我们不需要再额外添加。

创建一个超级用户

首先,我们需要创建一名用户来管理这个管理站点。运行以下的命令:

python manage.py createsuperuser

你会看下以下输出。输入你想要的用户名,邮箱和密码:

Username (leave blank to use 'admin'): admin
Email address: admin@admin.com
Password: ********
Password (again): ********
Superuser created successfully.

Django 管理站点

现在,通过python manage.py runserver命令来启动开发服务器,之后在浏览器中打开 http://127.0.0.1:8000/admin/ 。你会看到管理站点的登录页面,如下所示:
django-1-2

使用你在上一步中创建的超级用户信息进行登录。你将会看到管理站点的首页,如下所示:
django-1-3

GroupUser 模型(models)位于django.contrib.auth,是 Django 权限管理框架的一部分。如果你点击Users,你将会看到你之前创建的用户信息。你的 blog 应用的Post模型(model)和User(model)关联在了一起。记住,它们是通过author字段进行关联的。

## 在管理站点中添加你的模型(models)
让我们在管理站点中添加你的 blog 模型(models)。编辑 blog 应用下的admin.py文件,如下所示:

from django.contrib import admin
from .models import Post

admin.site.register(Post)

现在,在浏览器中刷新管理站点。你会看到你的Post模型(model)已经在页面中展示,如下所示:
django-1-4

这很简单,对吧?当你在 Django 的管理页面注册了一个模型(model),Django 会通过对你的模型(models)进行内省然后提供给你一个非常友好有用的接口,这个接口允许你非常方便的排列,编辑,创建,以及删除对象。

点击Posts右侧的Add链接来添加一篇新帖子。你将会看到 Django 根据你的模型(model)动态生成了一个表单,如下所示:
django-1-5

Django 给不同类型的字段使用了不同的表单控件。即使是复杂的字段例如DateTimeField也被展示成一个简单的接口类似一个 JavaScript 日期选择器。

填写好表单然后点击Save按钮。你会被重定向到帖子列页面并且得到一条帖子成功创建的提示,如下所示:
django-1-6

定制 models 的展示形式

现在我们来看下如何定制管理站点。编辑 blog 应用下的admin.py文件,使之如下所示:

from django.contrib import admin
from .models import Post

class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'slug', 'author', 'publish',
                    'status')
admin.site.register(Post, PostAdmin)

我们使用继承了ModelAdmin的定制类来告诉 Django 管理站点中需要注册我们自己的模型(model)。在这个类中,我们可以包含一些关于如何在管理站点中展示模型(model)的信息以及如何与该模型(model)进行交互。list_display属性允许你在设置一些你想要在管理对象列表页面显示的模型(model)字段。

让我们通过更多的选项来定制管理模型(model),如使用以下代码:

class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'slug', 'author', 'publish',
                    'status')
    list_filter = ('status', 'created', 'publish', 'author')
    search_fields = ('title', 'body')
    prepopulated_fields = {'slug': ('title',)}
    raw_id_fields = ('author',)
    date_hierarchy = 'publish'
    ordering = ['status', 'publish']

回到浏览器刷新管理站点页面,现在应该如下所示:
django-1-7

你可以看到帖子列页面中展示的字段都是你在list-dispaly属性中指定的。这个列页面现在包含了一个右侧边栏允许你根据list_filter属性中指定的字段来过滤返回结果。一个搜索框也应用在页面中。这是因为我们还通过使用search_fields属性定义了一个搜索字段列。在搜索框的下方,有个可以通过时间层快速导航的栏,该栏通过定义date_hierarchy属性出现。你还能看到这些帖子默认的通过StatusPublish列进行排序。这是因为你通过使用ordering属性指定了默认排序。

现在,点击Add post链接。你还会在这儿看到一些改变。当你输入完成新帖子的标题,slug字段将会自动填充。我们通过使用prepopulated_fields属性告诉 Django 通过输入的标题来填充slug字段。同时,现在的author字段展示显示为了一个搜索控件,这样当你的用户量达到成千上万级别的时候比再使用下拉框进行选择更加的人性化,如下图所示:
django-1-8

通过短短的几行代码,我们就在管理站点中自定义了我们的模型(model)的展示形式。还有更多的方式可以用来定制 Django 的管理站点。在这本书的后面,我们还会进一步讲述。

使用查询集(QuerySet)和管理器(managers)

现在,你已经有了一个完整功能的管理站点来管理你的 blog 内容,是时候学习如何从数据库中检索信息并且与这些信息进行交互了。Django 自带了一个强大的数据库抽象 API 可以让你轻松的创建,检索,更新以及删除对象。Django 的Object-relational Mapper(ORM)可以兼容 MySQL,PostgreSQL,SQLite 以及 Oracle。请记住你可以在你项目下的setting.py中编辑DATABASES设置来指定数据库。Django 可以同时与多个数据库进行工作,这样你可以编写数据库路由通过任何你喜欢的方式来操作数据。

一旦你创建好了你的数据模型(models),Django 会提供你一个 API 来与它们进行交互。你可以找到数据模型(model)的官方参考文档通过访问 https://docs.djangoproject.com/en/1.8/ref/models/

创建对象

打开终端运行以下命令来打开 Python shell:

python manage.py shell

然后依次输入以下内容:

>>> from django.contrib.auth.models import User
>>> from blog.models import Post
>>> user = User.objects.get(username='admin')
>>> post = Post.objects.create(title='One more post',
                        slug='one-more-post',
                        body='Post body.',
                        author=user)
>>> post.save()

让我们来研究下这些代码做了什么。首先,我们取回了一个 username 是admin的用户对象:

user = User.objects.get(username='admin')

get()方法允许你从数据库取回一个单独的对象。注意这个方法只希望在查询中有唯一的一个匹配。如果在数据库中没有返回结果,这个方法会抛出一个DoesNotExist异常,如果数据库返回多个匹配结果,将会抛出一个MultipleObjectsReturned异常。当查询执行的时候,所有的异常都是模型(model)类的属性。

接着,我们来创建一个拥有定制标题标题,slug 和内容的Post实例,然后我们设置之前取回的 user 胃这篇帖子的作者如下所示:

post = Post(title='Another post', slug='another-post', body='Postbody.', author=user)

这个对象只是存在内存中不会执行到数据库中

最后,我们通过使用save()方法来保存该对象到数据库中:

post.save()

这步操作将会执行一段 SQL 的插入语句。我们已经知道如何在内存中创建一个对象并且之后才在数据库中进行插入,但是我们也可以通过使用create()方法直接在数据库中创建对象,如下所示:

Post.objects.create(title='One more post', slug='one-more-post',body='Post body.', author=user)

更新对象

现在,改变这篇帖子的标题并且再次保存对象:

>>> post.title = 'New title'
>>> post.save()

这一次,save()方法执行了一条更新语句。

你对对象的改变一直存在内存中直到你执行到save()方法。

取回对象

Django 的Object-relational mapping(ORM)是基于查询集(QuerySet)。查询集(QuerySet)是从你的数据库中根据一些过滤条件范围取回的结果对象进行的采集。你已经知道如何通过get()方法从数据库中取回单独的对象。如你所见:我们通过Post.objects.get()来使用这个方法。每一个 Django 模型(model)至少有一个管理器(manager),默认管理器(manager)叫做objects。你通过使用你的模型(models)的管理器(manager)就能获得一个查询集(QuerySet)对象。获取一张表中的所有对象,你只需要在默认的objects管理器(manager)上使用all()方法即可,如下所示:

>>> all_posts = Post.objects.all()

这就是我们如何创建一个用于返回数据库中所有对象的查询集(QuerySet)。注意这个查询集(QuerySet)并还没有执行。Django 的查询集(QuerySets)是惰性(lazy)的,它们只会被动的去执行。这样的行为可以保证查询集(QuerySet)非常有效率。如果我们没有把查询集(QuerySet)设置给一个变量,而是直接在 Python shell 中编写,因为我们迫使它输出结果,这样查询集(QuerySet)的 SQL 语句将立马执行:

>>> Post.objects.all()

使用 filter() 方法

为了过滤查询集(QuerySet),你可以在管理器(manager)上使用filter()方法。例如,我们可以返回所有在 2015 年发布的帖子,如下所示:

Post.objects.filter(publish__year=2015)

你也可以使用多个字段来进行过滤。例如,我们可以返回 2015 年发布的所有作者用户名为admin的帖子,如下所示:

Post.objects.filter(publish_year=2015, author_username='admin')

上面的写法和下面的写法产生的结果是一致的:

Post.objects.filter(publish_year=2015).filter(author_username='admin')

我们构建了字段的查找方法,通过使用两个下划线(publish__year)来查询,除此以外我们也可以通过使用两个下划线(author__username)访问关联的模型(model)字段。

使用 exclude()

你可以在管理器(manager)上使用exclude()方法来排除某些返回结果。例如:我们可以返回所有 2015 年发布的帖子但是这些帖子的题目开头不能是Why:

Post.objects.filter(publish_year=2015).exclude(title_startswith='Why')

使用 order_by()

通过在管理器(manager)上使用order_by()方法来对不同的字段进行排序,你可以对结果进行排序。例如:你可以取回所有对象并通过它们的标题进行排序:

Post.objects.order_by('title')

默认是升序。你可以通过负号来指定使用降序,如下所示:

Post.objects.order_by('-title')

删除对象

如果你想删除一个对象,你可以对对象实例进行下面的操作:

post = Post.objects.get(id=1)
post.delete()

请注意,删除对象也将删除任何的依赖关系

查询集(QuerySet)什么时候会执行

只要你喜欢,你可以连接许多的过滤给查询集(QuerySet)而且不会立马在数据库中执行直到这个查询集(QuerySet)被执行。查询集(QuerySet)只有在以下情况中才会执行:
* 在你第一次迭代它们的时候
* 当你对它们的实例进行切片:例如Post.objects.all()[:3]
* 当你对它们进行了打包或缓存
* 当你对它们调用了repr()len()方法
* 当你明确的对它们调用了list()方法
* 当你在一个声明中测试它,例如bool(), or, and, or if

创建 model manager

我们之前提到过, objects是每一个模型(models)的默认管理器(manager),它会返回数据库中所有的对象。但是我们也可以为我们的模型(models)定义一些定制的管理器(manager)。我们准备创建一个定制的管理器(manager)来返回所有状态为已发布的帖子。

有两种方式可以为你的模型(models)添加管理器(managers):你可以添加额外的管理器(manager)方法或者继承管理器(manager)的查询集(QuerySets)进行修改。第一种方法类似Post.objects.my_manager(),第二种方法类似Post.my_manager.all()。我们的管理器(manager)将会允许我们返回所有帖子通过使用Post.published

编辑你的 blog 应用下的models.py文件添加如下代码来创建一个管理器(manager):

class PublishedManager(models.Manager):
    def get_queryset(self):
        return super(PublishedManager,
                    self).get_queryset().filter(status='published')

class Post(models.Model):
    # ...
    objects = models.Manager() # The default manager.
    published = PublishedManager() # Our custom manager.

get_queryset()是返回执行过的查询集(QuerySet)的方法。我们通过使用它来包含我们定制的过滤到完整的查询集(QuerySet)中。我们定义我们定制的管理器(manager)然后添加它到Post 模型(model)中。我们现在可以来执行它。例如,我们可以返回所有标题开头为Who的并且是已经发布的帖子:

Post.published.filter(title__startswith='Who')

## 构建列和详情视图(views)
现在你已经学会了一些如何使用 ORM 的基本知识,你已经准备好为 blog 应用创建视图(views)了。一个 Django 视图(view)就是一个 Python 方法,它可以接收一个 web 请求然后返回一个 web 响应。在视图(views)中通过所有的逻辑处理返回期望的响应。

首先我们会创建我们的应用视图(views),然后我们将会为每个视图(view)定义一个 URL 模式,我们将会创建 HTML 模板(templates)来渲染这些视图(views)生成的数据。每一个视图(view)都会渲染模板(template)传递变量给它然后会返回一个经过渲染输出的 HTTP 响应。

## 创建列和详情 views
让我们开始创建一个视图(view)来展示帖子列。编辑你的 blog 应用下中views.py文件,如下所示:

from django.shortcuts import render, get_object_or_404
from .models import Post
def post_list(request):
    posts = Post.published.all()
    return render(request,
                  'blog/post/list.html',
                  {'posts': posts})

你刚创建了你的第一个 Django 视图(view)。post_list视图(view)将request对象作为唯一的参数。记住所有的的视图(views)都有需要这个参数。在这个视图(view)中,我们获取到了所有状态为已发布的帖子通过使用我们之前创建的published管理器(manager)。

最后,我们使用 Django 提供的快捷方法render()通过给予的模板(template)来渲染帖子列。这个函数将request对象作为参数,模板(template)路径以及变量来渲染的给予的模板(template)。它返回一个渲染文本(一般是 HTML 代码)HttpResponse对象。render()方法考虑到了请求内容,这样任何模板(template)内容处理器设置的变量都可以带入给予的模板(template)中。你会在第三章,扩展你的 blog 应用学习到如何使用它们。

让我们创建第二个视图(view)来展示一篇单独的帖子。添加如下代码到views.py文件中:

def post_detail(request, year, month, day, post):
    post = get_object_or_404(Post, slug=post,
                                   status='published',
                                   publish__year=year,
                                   publish__month=month,
                                   publish__day=day)
    return render(request,
                  'blog/post/detail.html',
                  {'post': post})

这是一个帖子详情视图(view)。这个视图(view)使用year,month,day以及post作为参数通过给予 slug 和日期来获取到一篇已经发布的帖子。请注意,当我们创建Post模型(model)的时候,我们给 slgu 字段添加了unique_for_date参数。这样我们可以确保在给予的日期中只有一个帖子会带有一个 slug,因此,我们能通过日期和 slug 取回单独的帖子。在这个详情视图(view)中,我们通过使用get_object_or_404()快捷方法来检索期望的Post。这个函数能取回匹配给予的参数的对象,或者当没有匹配的对象时返回一个 HTTP 404(Not found)异常。最后,我们使用render()快捷方法来使用一个模板(template)去渲染取回的帖子。

## 为你的视图(views)添加 URL 模式

一个 URL 模式是由一个 Python 正则表达,一个视图(view),一个全项目范围内的命名组成。Django 在运行中会遍历所有 URL 模式直到第一个匹配的请求 URL 才停止。之后,Django 导入匹配的 URL 模式中的视图(view)并执行它,使用关键字或指定参数来执行一个HttpRequest类的实例。
如果你之前没有接触过正则表达式,你需要去稍微了解下,通过访问 https://docs.python.org/3/howto/regex.html

在 blog 应用目录下创建一个urls.py文件,输入以下代码:

from django.conf.urls import url
from . import views
urlpatterns = [
    # post views
    url(r'^$', views.post_list, name='post_list'),
    url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<post>[-\w]+)/$',
        views.post_detail,
        name='post_detail'),
]

第一条 URL 模式没有带入任何参数,它映射到post_list视图(view)。第二条 URL 模式带上了以下 4 个参数映射到post_detail视图(view)中。让我们看下这个 URL 模式中的正则表达式:

  • year:需要四位数
  • month:需要两位数。不及两位数,开头带上 0,比如 01,02
  • day:需要两位数。不及两位数开头带上 0
  • post:可以由单词和连字符组成

为每一个应用创建单独的urls.py文件是最好的方法,可以保证你的应用能给别的项目再度使用。

现在你需要将你 blog 中的 URL 模式包含到项目的主 URL 模式中。编辑你的项目中的mysite文件夹中的urls.py文件,如下所示:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)), 
    url(r'^blog/', include('blog.urls',
        namespace='blog',
        app_name='blog')),
]

通过这样的方式,你告诉 Django 在blog/路径下包含了 blog 应用中的urls.py定义的 URL 模式。你可以给它们一个命名空间叫做blog,这样你可以方便的引用这个 URLs 组。

模型(models)的标准 URLs

你可以使用之前定义的post_detail URL 给Post对象构建标准 URL。Django 的惯例是给模型(model)添加get_absolute_url()方法用来返回一个对象的标准 URL。在这个方法中,我们使用reverse()方法允许你通过它们的名字和可选的参数来构建 URLS。编辑你的models.py文件添加如下代码:

from django.core.urlresolvers import reverse
Class Post(models.Model):
    # ...
    def get_absolute_url(self):
        return reverse('blog:post_detail',
                        args=[self.publish.year,
                              self.publish.strftime('%m'),
                              self.publish.strftime('%d'),
                              self.slug])

请注意,我们通过使用strftime()方法来保证个位数的月份和日期需要带上 0 来构建 URL*(译者注:也就是 01,02,03)。我们将会在我们的模板(templates)中使用 *get_absolute_url()方法。

## 为你的视图(views)创建模板(templates)

我们为我们的应用创建了视图(views)和 URL 模式。现在该添加模板(templates)来展示界面友好的帖子了。

在你的 blog 应用目录下创建以下目录结构和文件:

templates/
    blog/
        base.html
        post/
            list.html
            detail.html

以上就是我们的模板(templates)的文件目录结构。base.html文件将会包含站点主要的 HTML 结构以及分割内容区域和一个导航栏。list.htmldetail.html文件会继承base.html文件来渲染各自的 blog 帖子列和详情视图(view)。

Django 有一个强大的模板(templates)语言允许你指定数据的如何进行展示。它基于模板标签(templates tags), 例如 {% tag %}, {{ variable }}以及模板过滤器(templates filters),可以对变量进行过滤,例如 {{ variable|filter }}。你可以通过访问 https://docs.djangoproject.com/en/1.8/ ref/templates/builtins/ 找到所有的内置模板标签(templates tags)和过滤器(filters)。

让我们来编辑base.html文件并添加如下代码:

{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
  <title>{% block title %}{% endblock %}</title>
  <link href="{% static "css/blog.css" %}" rel="stylesheet">
</head>
<body>
  <div id="content">
    {% block content %}
    {% endblock %}
  </div>
  <div id="sidebar">
    <h2>My blog</h2>
      <p>This is my blog.</p>
  </div>
</body>
</html>

{% load staticfiles %}告诉 Django 去加载django.contrib.staticfiles应用提供的staticfiles 模板标签(temaplate tags)。通过加载它,你可以在这个模板(template)中使用{% static %}模板过滤器(template filter)。通过使用这个模板过滤器(template filter),你可以包含一些静态文件比如说blog.css文件,你可以在本书的范例代码例子中找到该文件,在 blog 应用的static/目录中(译者注:给大家个地址去拷贝 https://github.com/levelksk/django-by-example-book拷贝这个目录到你的项目下的相同路径来使用这些静态文件。

你可以看到有两个{% block %}标签(tags)。这些是用来告诉 Django 我们想在这个区域中定义一个区块(block)。继承这个模板(template)的其他模板(templates)可以使用自定义的内容来填充区块(block)。我们定义了一个区块(block)叫做title,另一个区块(block)叫做content

让我们编辑post/list.html文件使它如下所示:

{% extends "blog/base.html" %}

{% block title %}My Blog{% endblock %}

{% block content %}
  <h1>My Blog</h1>
  {% for post in posts %}
    <h2>
      <a href="{{ post.get_absolute_url }}">
        {{ post.title }}
      </a>
    </h2>
    <p class="date">
      Published {{ post.publish }} by {{ post.author }}
    </p>
    {{ post.body|truncatewords:30|linebreaks }}
  {% endfor %}
{% endblock %}

通过{% extends %}模板标签(template tag),我们告诉 Django 需要继承blog/base.html 模板(template)。然后我们在titlecontent区块(blocks)中填充内容。我们通过循环迭代帖子来展示它们的标题,日期,作者和内容,在标题中还集成了帖子的标准 URL 链接。在帖子的内容中,我们应用了两个模板过滤器(template filters): truncatewords用来缩短内容限制在一定的字数内,linebreaks用来转换内容中的换行符为 HTML 的换行符。只要你喜欢你可以连接许多模板标签(tempalte filters),每一个都会应用到上个输出生成的结果上。

打开终端执行命令python manage.py runserver来启动开发服务器。在浏览器中打开 http://127.0.0.1:8000/blog/ 你会看到运行结果。注意,你需要添加一些发布状态的帖子才能在这儿看到它们。你会看到如下图所示:
django-1-9

这之后,让我们来编辑post/detail.html文件使它如下所示:

{% extends "blog/base.html" %}

{% block title %}{{ post.title }}{% endblock %}

{% block content %}
  <h1>{{ post.title }}</h1>
  <p class="date">
    Published {{ post.publish }} by {{ post.author }}
  </p>
  {{ post.body|linebreaks }}
{% endblock %}

现在,你可以在浏览器中点击其中一篇帖子的标题来看帖子的详细视图(view)。你会看到类似以下页面:
django-1-10

添加页码

当你开始给你的 blog 添加内容,你很快会意识到你需要将帖子分页显示。Django 有一个内置的Paginator类允许你方便的管理分页。

编辑 blog 应用下的views.py文件导入 Django 的页码类修改post_list如下所示:

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

def post_list(request):
    object_list = Post.published.all()
    paginator = Paginator(object_list, 3) # 3 posts in each page
    page = request.GET.get('page')
    try:
        posts = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer deliver the first page
        posts = paginator.page(1)
    except EmptyPage:
        # If page is out of range deliver last page of results
        posts = paginator.page(paginator.num_pages)
    return render(request,
                  'blog/post/list.html',
                  {'page': page, 
                   'posts': posts})

Paginator是如何工作的:

  • 我们使用希望在每页中显示的对象的数量来实例化Paginator类。
  • 我们获取到page GET 参数来指明页数
  • 我们通过调用Paginatorpage()方法在期望的页面中获得了对象。
  • 如果page参数不是一个整数,我们就返回第一页的结果。如果这个参数数字超出了最大的页数,我们就展示最后一页的结果。
  • 我们传递页数并且获取对象给这个模板(template)。

现在,我们必须创建一个模板(template)来展示分页处理,它可以被任意的模板(template)包含来使用分页。在 blog 应用的templates文件夹下创建一个新文件命名为pagination.html。在该文件中添加如下 HTML 代码:

<div class="pagination">
  <span class="step-links">
    {% if page.has_previous %}
      <a href="?page={{ page.previous_page_number }}">Previous</a>
    {% endif %}
    <span class="current">
      Page {{ page.number }} of {{ page.paginator.num_pages }}.
    </span>
      {% if page.has_next %}
        <a href="?page={{ page.next_page_number }}">Next</a>
      {% endif %}
  </span>
</div>    

为了渲染上一页与下一页的链接并且展示当前页面和所有页面的结果,这个分页模板(template)期望一个Page对象。让我们回到blog/post/list.html模板(tempalte)中将pagination.html模板(template)包含在{% content %}区块(block)中,如下所示:

{% block content %}
  ...
  {% include "pagination.html" with page=posts %}
{% endblock %}

我们传递给模板(template)的Page对象叫做posts,我们将分页模板(tempalte)包含在帖子列模板(template)中指定参数来对它进行正确的渲染。这种方法你可以反复使用,用你的分页模板(template)对不同的模型(models)视图(views)进行分页处理。

现在,在你的浏览器中打开 http://127.0.0.1:8000/blog/。 你会看到帖子列的底部已经有分页处理:
django-1-11

使用基于类的视图(views)

因为一个视图(view)的调用就是得到一个 web 请求并且返回一个 web 响应,你可以将你的视图(views)定义成类方法。Django 为此定义了基础的视图(view)类。它们都从View类继承而来,View类可以操控 HTTP 方法调度以及其他的功能。这是一个可替代的方法来创建你的视图(views)。

我们准备通过使用 Django 提供的通用ListView使我们的post_list视图(view)转变为一个基于类的视图。这个基础视图(view)允许你对任意的对象进行排列。

编辑你的 blog 应用下的views.py文件,如下所示:

from django.views.generic import ListView
class PostListView(ListView):
    queryset = Post.published.all()
    context_object_name = 'posts'
    paginate_by = 3
    template_name = 'blog/post/list.html'

这个基于类的的视图(view)类似与之前的post_list视图(view)。在这儿,我们告诉ListView做了以下操作:

  • 使用一个特定的查询集(QuerySet)代替取回所有的对象。代替定义一个queryset属性,我们可以指定model = Post然后 Django 将会构建Post.objects.all() 查询集(QuerySet)给我们。
  • 使用环境变量posts给查询结果。如果我们不指定任意的context_object_name默认的变量将会是object_list
  • 对结果进行分页处理每页只显示 3 个对象。
  • 使用定制的模板(template)来渲染页面。如果我们不设置默认的模板(template),ListView将会使用blog/post_list.html

现在,打开你的 blog 应用下的urls.py文件,注释到之前的post_list*URL 模式,在之后添加一个新的 URL 模式来使用 *PostlistView类,如下所示:

urlpatterns = [
    # post views
    # url(r'^$', views.post_list, name='post_list'),
    url(r'^$', views.PostListView.as_view(),name='post_list'),
    url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/'\
        r'(?P<post>[-\w]+)/$',
        views.post_detail,
        name='post_detail'),
]

为了保持分页处理能工作,我们必须将正确的页面对象传递给模板(tempalte)。Django 的ListView通过叫做page_obj的变量来传递被选择的页面,所以你必须编辑你的post_list.html模板(template)去包含使用了正确的变量的分页处理,如下所示:

{% include "pagination.html" with page=page_obj %}

在你的浏览器中打开 http://127.0.0.1:8000/blog/ 然后检查每一样功能是否都和之前的post_list视图(view)一样工作。这是一个简单的,通过使用 Django 提供的通用类的基于类视图(view)的例子。你将在第十章,创建一个在线学习平台以及相关的章节中学到更多的基于类的视图(views)。

总结

在本章中,你通过创建一个基础的 blog 应用学习了 Django web 框架的基础。你为你的项目设计了数据模型(models)并且进行了数据库迁移。你为你的 blog 创建了视图(views),模板(templates)以及 URLs,还包括对象分页。

在下一章中,你会学习到如何增强你的 blog 应用,例如评论系统,标签(tag)功能,并且允许你的用户通过邮件来分享帖子。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 10 条回复 时间 点赞

非常支持,楼主这样的学习态度能带动自己和所有对英语望而却步的同学们

真的非常详细,赞。另外一个建议,跟内容不相关的译者注,其实感觉有点出戏。另外楼主你有几个 markdown 标签挂掉了呀

不错,刚好这几天也在自己学习 Django

厉害!收藏先,慢慢看。个人是几度学习 Django 未果:-(

剪烛 回复

让我检查下,在简书上一切显示正常- -

夜兔君 回复

谢谢,其实全书都翻译好了,这里一天转一章过来

Felix 回复

加油,非常好用的框架

kunkunkun2018 回复

最好在公司有地方能用上,用上了才有成果

关注了,经常听到这个框架

flask 路过

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册