编写你的第一个Django app,部分1
【中括号中的绿色字是我自己添加的辅助内容,非官方文档内容,仅供参考】
【英文原文地址:https://docs.djangoproject.com/en/1.6/intro/tutorial01/】
【转载本文时请注明出处:http://my.oschina.net/mayepythoner/blog/297550】
我们通过一个例子开始学习。
通过这个教程,我们将带领你创建一个基本的投票应用。
它包括两个部分:
一个公开的站点,让人们查看投票和进行投票。
一个管理站点,让你添加、更改、删除投票。
我们假设你已经安装的Django。你可以通过运行下面的命令查看Django是否安装和它的版本:
$ python -c "import django; print(django.get_version())"
1.6.5
如果Django已经安装,你可以看到你安装的版本【如我的结果,我安装的是1.6.5】。如果没有安装,你会得到错误信息:“No module named django”。
这个教程基于Django1.6 和 Python2.X 写成。如果你的Django版本不匹配,你可以利用页面右侧的版本切换器切换到你所用的版本的教程,或者升级你的Django版本到最新。如果你正在用 Python3.X,要意识到你的代码可能需要与教程中的有一定差异,并且你要知道如何使用Python3.X才能继续使用这个教程。
查看 How to install Django 去了解怎样删除老版本的Django和安装一个更新的版本。
在哪里能获得帮助
如果你在学习教程中遇到困难,请发送消息到 django-users 或者加入 #django on irc.freenode.net ,和那些可能帮助到你的其他Django用户交谈。
创建一个项目
如果这是你第一次用Django,你必须认真注意最初的几步。也就是说,你将需要自动产生一些代码去建立一个Django项目,包括数据库配置、Django定制选项和应用定制配置。
在命令行下,cd到你喜欢储存代码的目录,然后运行下面的命令【如果成功这行代码没有任何输出,但你的文件夹下面会多了个mysite的目录】:
django-admin.py startproject mysite
这条命令在你所在的文件夹下创建了一个 mysite 目录。如果它没有起作用,参看 Problems running django-admin.py
注意
你将需要避免使用 built-in Python 或者 Django 的组件命名。特别是,这意味着你应该避免用django这样的名字(这与Django自身产生冲突) 或者 test (这与 built-in Python package 产生冲突)。
代码应该存放在哪里?
如果你的背景是平凡老旧的PHP(没有使用现代的框架),你可能习惯于把代码放在Web server的文档根目录下(例如 /var/www)。而使用Django,你无需这么做。把所有的Python代码都放到你的Web server文档根目录下不是一个好的想法,因为这增大了人们通过Web看到你的代码的可能性。这不利于安全性。
把你的代码放到文档根目录之外的某个目录,例如 /home/mycode。
我们看一下 startproject 为我们产生了什么:
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
和你所看见的不匹配?
最近默认的项目布局已经改变。如果你看见一个“单调”的布局(没有里层的mysite目录),你可能正在使用和本教程不匹配的版本。你应该寻找老版的教程或者新的Django版本。
这些文件是:
外层的 mysite/ 根目录只是你项目的一个容器。它的名字不与Django对应;你可以把它改成任何你喜欢的名字。
manage.py:一个让你可以通过多种方式与Django项目交互的命令行工具。你可以在 django-admin.py and manage.py 中读到关于manage.py的全部细节。
内层的 mysite/ 目录是你项目真实的 Python package。它的名字就是你需要import到各处的 Python package 的名字。(例如:mysite.urls)。
mysite/__init__.py:这是一个空文件,他告诉 Python 这个目录应该被当做一个 Python package。(如果你是个Python的初学者,通过 Python的官方文档了解更多packages的信息。)
mysite/settings.py:这个 Django 项目的配置文件。Django settings 将会告诉你关于settings如何工作的全部内容。
mysite/urls.py:这个Django项目的 url 声明;是你的 Django-powered 站点的一个“内容列表”。你可以了解更多关于 URLs的内容在 URL dispatcher 。
mysite/wsgi.py:一个为 WSGI 兼容的 Web server 服务你的项目的入口点。更多细节请参见 How to deploy with WSGI 。
用于开发的服务器
让我们验证它可行。进入外层的mysite目录。执行命令 python manage.py runserver(如果你还没有这样做的话)。你将看到下面的输出信息:
Validating models...
0 errors found
August 02, 2014 - 15:50:53
Django version 1.6, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
你已经开启了Django的开发服务器,这是用纯Python编写的一个轻量级 Web server。我们把他包括在Django里以便你可以更快速的开发东西,而不需要去处理服务器产品的配置(例如Apache),直到你已经准备好用于生产环境。
是时候该告诉你这个注意事项了: 不能将这个服务器用到任何生产环境当中。他的目的只是用于开发。(我们做的是一个Web框架,而不是一个Web 服务器。)
现在这个程序正在运行, 用你的浏览器访问 http://127.0.0.1:8000/ 你将看到 “Welcome to Django”页面, 令人愉快的浅蓝色,它工作了!【下图为我个人截图,为了读者能更直观感受到Django的魅力】
改变端口
在默认情况下,runserver 命令在内置IP的8000端口开始开发服务器。
如果你想改变服务器端口,把他当做参数传给命令行。例如,下面的命令将在8080端口开启服务器:
$ python manage.py runserver 8080
如果你想改变服务器IP,把他当做参数传给命令行,所以如果你想监听所有公共IP(如果你想在其他电脑上查看它是否工作,这个选项很有用。)利用下面的命令:
$ python manage.py runserver 0.0.0.0:8000
关于开服文档的全部说明,请参看 runserver
runserver的自动重载
当每次请求时,开发服务器会自动重载你的python代码。你不需要重启服务器去让改变了的代码生效。然而,像添加文件或编译转换文件这些动作不会触发重启,所以在这种情况下你不得不重启服务器。
数据库设置
现在,编辑 mysite/settings.py. 这是一个常规的 Python 模块,用模块级别的变量代表 Django 的配置。
在默认情况下,配置利用 SQLite。如果你是一个数据库方面的新手,或者你只是因为兴趣而尝试Django。这是最简单的选择。SQLite 被包含在 Python 里 。所以你不需要安装任何其他东西去支持你的数据库。
如果你希望使用其他的数据库,安装合适的 database bindings ,并且在 DATABASES 的‘default’条目中改变下面的键值,用来匹配你的数据库连接设置:
ENGINE - 可以是 'django.db.backends.sqlite3', 'django.db.backends.postgresql_psycopg2', 'django.db.backends.mysql', 或者 'django.db.backends.oracle',其他引擎串可以参看 also available 。
NAME - 你数据库的名字。如果你使用的是SQLite,数据库将只是在你电脑上的一个文件;在这种情况下,NAME应该是完整的绝对路径,包括这个文件的文件名。默认情况下,路径 os.path.join(BASE_DIR, 'db.sqlite3') 将会储存这个文件在你的项目文件夹中。
如果你没有使用 SQLite 作为你的数据库。额外的设置例如 USER, PASSWORD, HOST 必须被添加。 更多细节,可以参见 DATABASES 的说明文档。
注意
如果你使用的是 PostgreSQL 或是 MySQL,确保在这一时刻你已经创建了一个数据库。利用你的数据库交互工具,输入 “CREATE DATABASE database_name;”完成这个操作。
如果你使用的是 SQLite ,你在这之前不需要创建人和东西 —— 当你需要时,数据库文件将会被自动创建。
编写 mysite/settings.py,通过 TIME_ZONE 设置时间区域。
同时,注意在你文件顶端的 INSTALLED_APPS 设置。他用来控制在这个Django实例中被激活的全部 Django 应用的名字。 Apps 可以被用到多个项目中,你可以把它们打包并发布到其他项目当中。
默认情况下, INSTALLED_APPS 包括以下 apps,他们全部来自 Django:
django.contrib.admin - 管理员站点。 你将在 part 2 of this tutorial 中用到它。
django.contrib.auth - 一个身份验证系统。
django.contrib.contenttypes - 一个内容类型框架。
django.contrib.sessions - 一个 session 框架。
django.contrib.messages- 一个消息框架。
django.contrib.staticfiles - 一个管理静态文件的框架。
为了普通情况的便捷性,这些应用将被包括在默认选项中。
这些应用中的一些至少要用到一张数据库表,所以我们需要在使用它们之前创建数据库表。这做到这一点,利用下面的命令:
$ python manage.py syncdb
syncdb 命令会查看 INSTALLED_APPS 的配置情况,然后在你在 mysite/settings.py 中配置的数据库中创建所有所需要的数据库表。你可以看到每个数据表创建时的信息,并且你会被询问是否要为你你身份认证系统设置一个超级用户权限,继续并操作它。
如果你有兴趣,在你数据库的客户端命令行运行命令展示Django为你创建的数据表, 输入 \dt (PostgreSQL), SHOW TABLES; (MySQL), or .schema(SQLite)。
最低配置
正如我们上面所说,默认的应用将包括标准的配置,但是不是所有人都需要它们。如果你不需要它们中的一些或者全部,在使用 sybcdb 之前,注释掉或删掉 INSTALLED_APPS 中的那些行。sybcdb 命令只会创建 INSTALLED_APPS 中的apps所需要的数据表。
创建 models
现在,在你的环境中,一个“项目”已经被创建,你可以开始着手你的工作了。
你在 Django 里写的每一个应用都组成了遵循某些管理的一个 Python package。Django 拥有可以自动产生 app 目录结构的工具,所以你可以专注于写代码而不是创建目录。
Projects vs. apps
项目和 app 之间有什么区别?一个 app 是一个可以完成某些任务的 Web应用 —— 例如,一个网络博客系统,一个公共记录的数据库或者是一个简单的投票应用。一个项目是配置与特殊网络站点应用的集合。一个项目可以包括很多个 app。 一个 app 可以被应用到多个项目当中。
你的 apps 可以存在在你 Python 路径中的任何位置。 在本教程中,我们将通过 manage,.py 创建我们的投票 app,以便于他可以被当做一个顶层模块导入,而不是以 mysite 的下级模块。
为了创建的你 app,确保你在 manage.py 的同目录下,运行下面命令:
$ python manage.py startapp polls
这将产生一个 polls 目录,像下面这样的布局:
polls/
__init__.py
admin.py
models.py
tests.py
views.py
这个目录将是 polls 应用的家目录。
在 Django 中编写一个数据库 Web app 的第一步就是定义你的 models - 其本质上就是你的数据库布局再加上元数据。
原理思想
一个 model 就是你的数据的一个单独且确定的数据元。它包括你存储的数据的基本阈值及特征。 Django 遵循 DRY Principle 【Don't Repeat Yourself】。其目的就是定义你的数据 model 在一个位置,然后自动的驱动它。
在你简单的投票 app 中, 我们将建立两个 models: Poll 和 Choice。 Poll 拥有一个问题和一个发布日期。 Choice拥有两个字段:选择的内容和投票个数。每个 Choice 对应一个 Poll。
这些概念将被一个简单的 Python 类表示。编辑 polls/models.py 文件让他变成下面的样子:
from django.db import models
class Poll(models.Model):
question = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
这个代码是直白的。每个model被表示成一个类, 它们都是 django.db.models.Model 的子类。每个model 有一系列类变量,每个类变量代表一个数据库字段。
每个字段被表示成一个 Field 类的实例 —— 例如,用作字符字段的 ChatField 和用作 datetimes 的 DateTimeField。他们告诉 Django 每个字段用哪种类型。
每个 Field 实例的名字(例如, question 和 pub_date)就是字段的名字,利用机器友好的格式。你将在 Python 代码中应用到它们的值,而数据库将把它们用作列名。
你可以使用 Field 的可选择的第一个潜在参数去设计一个人类可读的名字。他会同时应用到 Django 的两个部分,同时还可以作为文档。如果这个字段不被提供,Django 会使用一个机器可读的名字。在我们的例子里,我们仅为 Poll.pub_date定义了一个人类可读的名字,对于这个 model 里的其他的字段,字段的机器可读名字已经满足于人类可读的名字。
一些 Field 类需要参数。例如,CharField 需要你给他一个 max_hength。它不仅用于数据库模式,而且还用于验证,我们马上就会看到。
一个 Field 也可以拥有多个可选的参数;在这个例子中,我们把 votes 的默认值设为 0 。
最后,注意一个关系被定义,利用 ForeignKey 。他告诉 Django 每个 Choice 与一个单独的 Poll关联。Django 支持所有通用的数据库关系: 多对一、多对多、一对一。
激活 models
这一小段代码给予了 Django 很多信息。 通过它, Django可以做到:
为 app 创建一个数据库模式(CREATE TABLE 语句)。
为 Poll 和 Choice 对象创建了一个 Python 数据库调用 API 入口。
但是首先我们要告诉我们的项目 polls app 已经被安装。
原理思想
Django apps 是可插拔式的:你可以在多个项目中使用同一个 app,并且你可以发布 apps, 因为它们不需要绑定在一个 Django的安装实例上。
再次编辑 mysite/settings.py 文件, 改变 INSTALLED_APP设置,让它包括字符串‘polls’,如下所述:
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'polls',
)
现在 Django 知道你包含了 polls app。让我们运行下面命令:
$ python manage.py sql polls
你可以看到一些类似于下面的结果(poll app 的 CREATE TABLE SQL 语句):
BEGIN;
CREATE TABLE "polls_poll" (
"id" integer NOT NULL PRIMARY KEY,
"question" varchar(200) NOT NULL,
"pub_date" datetime NOT NULL
);
CREATE TABLE "polls_choice" (
"id" integer NOT NULL PRIMARY KEY,
"poll_id" integer NOT NULL REFERENCES "polls_poll" ("id"),
"choice_text" varchar(200) NOT NULL,
"votes" integer NOT NULL
);
COMMIT;
【下图为我的结果,和教程完全一致。】
注意下面几点:
这个精准的输出依赖于你所使用的数据库而变化。上面的例子基于 SQLite 所产生。
表明会自动的根据两部分拼装而成:app 的名字与小写的 model 的名字 —— poll 和 choice。(你可以重写这里。)
主键(IDs)会被自动添加(你也可以重写这里。)
根据惯例,Django 增加 "_id" 作为外检的名字。(是的,你也可以重写这里。)
外键关系被 REFERENCES 语句定义的很明确。
它剪裁了你对数据库的使用,很多数据库特性的字段类型, 例如, auto_increment (MySQL), serial (PostgreSQL), or integer primary key (SQLite) 被自动控制。同样的原理试用于字段名称对于引号的使用 —— 例如,困惑于用双引号还是单引号。【这里 Django 都为你自动解决。】
sql 命令并非真的在你的数据库中运行了sql语句。它只是把语句打印在屏幕上以便让你看见 Djaogo 所获得的 sql 语句是什么样子的。如果你想执行它,可以把它复制并粘贴到你的数据库命令交互窗口。然而,正如我们不久前看到的,Django 为我们提供货了提交SQL到数据库的更简单的方法。
如果你感兴趣,也试试下面的命令:
python manage.py validate - 检查你的 models 结构的任何错误。
python manage.py sqlcustom polls - 输出任何为这个应用定义的惯例SQL语句。(例如表的修改和限制)
python manage.py sqlclear polls - 通过你在数据库中已经存在的表(如果有的话),输出这个 app 必要的 DROP TABLE 语句。
python manage.py sqlindexes polls - 为你的 app 输出 CREAT INDEX 语句。
python manage.py sqlall polls - 所有SQL 语句的集合, 包括: sql, sqlcustom, and sqlindexes 命令 。
查看这些命令的输出有助于你了解背后真正发生了什么。
现在,运行再次 syncdb 去在你的数据库中创建 model tables:
$ python manage.py syncdb
syncdb 命令运行来源于sqlall的SQL语句,它们对应于 INSTALLED_APPS 中的所有在数据库中还没有存在的apps。他创建所有的表、初始值和序列,对应于你上次运行syncdb之后所向项目里增加的任何 apps。 syncdb 可以随时被执行,他智慧创建还没有被创建的表。
【这里同样给各位看下我的输出吧,因为这是我这个项目第一次运行 syncdb,教程中的第一回我没有做,所以所有 app 的表都在这里创建了~】
Creating tables ...
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session
Creating table polls_poll
Creating table polls_choice
You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (leave blank to use 'root'): root
Email address: mayuanchi1989@163.com
Password:
Password (again):
Superuser created successfully.
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)
阅读 django-admin.py documentation 以了解关于 m,anage.py 所能做的所有信息。
调戏API
【playing with the API。 只能这么翻译了。。。囧】
现在,我们进入 Python 的交互 shell,调戏一下 Django 提供给我们的免费API。进入 Python shell, 利用一下命令:
$ python manage.py shell
我们利用这个代替了简单的输入"python",因为 manage,py 设置了 DJANGO_SETTINGS_MODULE 环境变量,此环境变量给予Django Python路径到你的mysite/settings.py 文件。
绕过 manage.py
如果你不喜欢用manage.py, 没关系。 只需设置 DJANGO_SETTINGS_MODULE 环境变量到 mysite/settings 且运行 Python 在manage.py相同的文件夹下。(或者保证目录在Python path中,以便于 import mysite 可用)。
更多信息,参考 django-admin.py documentation
当你进入这个shell时,先导入 database API :
>>> from polls.models import Poll, Choice # Import the model classes we just wrote.
# 系统中还没有 polls
>>> Poll.objects.all()
[]
# 创建一个 Poll.
# 时间区域已经被默认支持到配置文件中,
# 所以 Django 为 pub_data 期望一个时间区域信息的datetime。
# 我们用 timezone.now() 代替 datetime.datetime.now(),它会为我们做正确的事情。
>>> from django.utils import timezone
>>> p = Poll(question="What's new?", pub_date=timezone.now())
# 保存对象到数据库。你可以明确的调用save()。
>>> p.save()
# 现在它有了一个ID,注意它可能是"1L"而不是"1",这取决于你正在使用的数据库。
# 这不是什么大事,这值意味着你的数据库是喜欢返回一个整数作为 Python 的长整型对象。
>>> p.id
1
# 通过 Python 属性获取数据库的列。
>>> p.question
"What's new?"
>>> p.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
# 通过改变属性来改变数值,然后调用 save()
>>> p.question = "What's up?"
>>> p.save()
# objects.all() 展示你数据库中的所有 polls
>>> Poll.objects.all()
[<Poll: Poll object>]
【对于上面命令我的输出】
>>> from polls.models import Poll, Choice
>>> Poll.objects.all()
[]
>>> from django.utils import timezone
>>> p = Poll(question="What's new?", pub_date=timezone.now())
>>> p.save()
>>> p.id
1
>>> p.question
"What's new?"
>>> p.pub_date
datetime.datetime(2014, 8, 3, 8, 26, 23, 407698, tzinfo=<UTC>)
>>> p.question = "What's up?"
>>> p.save()
>>> Poll.objects.all()
[<Poll: Poll object>]
等一下,<Poll: Poll object> 完全是一个没有帮助的对对象的表示。我们通过编辑 polls model 修正它(在 poll/models.py文件中),增加 __unicode__() 方法到 Poll 和 Choice。在 Python3中,只要将例子中的 __unicode__改为 __str__就可以了:
from django.db import models
class Poll(models.Model):
# ...
def __unicode__(self): # Python 3: def __str__(self):
return self.question
class Choice(models.Model):
# ...
def __unicode__(self): # Python 3: def __str__(self):
return self.choice_text
在你的 models 中增加 __unicode__() ( 在 Python3中是 __str__() )方法是很必要的,不仅是因为当你与命令行交互时更加清楚,也是因为对象的表达被用在 Django 自动生成的 admin 系统中。
__unicode__ 还是 __str__ ?
在 Python3 中,事情变得简单,只需应用 __str__() 而可以忘记掉 __unicode__()。
如果你很熟悉 Python2, 你也许会习惯于增加 __str__()方法到你的类,而不是 __unicode__()方法。我们这里应用 __unicode__() 是因为 Django models 默认使用 Unicode。所有你数据库上的数据被返回时都会被转成 Unicode。
Django models 有一个默认的 __str__()方法,他会调用 __unicode__() 并且把结果转换成 UTF-8 字节流。这意味着 unicode(p) 将会返回 一个 Unicode 字符串,而str(p)会返回一个字符编码为UTF-8的正常字符串。
如果你认为这一切令人费解,只要记住增加 __unicode__()方法到你的 models。 非常幸运,事情总是会正常运转。
注意这些都是标准的Python方法。我们增加个惯例方法,仅仅是为了示范:
import datetime
from django.utils import timezone
# ...
class Poll(models.Model):
# ...
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
注意所添加的 import datetime 和 from django.utils import timezone,分别引入了Python的标准 datatime model 和 Django 在 from django.utils 中的 timezone 相关工具。如果你不熟悉Python 中的时区控制,可以参考 time zone support docs。
保存这些变更,开启一个新的 Python shell:、
>>> from polls.models import Poll, Choice
# Make sure our __unicode__() addition worked.
>>> Poll.objects.all()
[<Poll: What's up?>]
# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Poll.objects.filter(id=1)
[<Poll: What's up?>]
>>> Poll.objects.filter(question__startswith='What')
[<Poll: What's up?>]
# Get the poll that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Poll.objects.get(pub_date__year=current_year)
<Poll: What's up?>
# Request an ID that doesn't exist, this will raise an exception.
>>> Poll.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Poll matching query does not exist.
# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Poll.objects.get(id=1).
>>> Poll.objects.get(pk=1)
<Poll: What's up?>
# Make sure our custom method worked.
>>> p = Poll.objects.get(pk=1)
>>> p.was_published_recently()
True
# Give the Poll a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a poll's choices) which can be accessed via the API.
>>> p = Poll.objects.get(pk=1)
# Display any choices from the related object set -- none so far.
>>> p.choice_set.all()
[]
# Create three choices.
>>> p.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> p.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = p.choice_set.create(choice_text='Just hacking again', votes=0)
# Choice objects have API access to their related Poll objects.
>>> c.poll
<Poll: What's up?>
# And vice versa: Poll objects get access to Choice objects.
>>> p.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> p.choice_set.count()
3
# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any poll whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(poll__pub_date__year=current_year)
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
# Let's delete one of the choices. Use delete() for that.
>>> c = p.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
更多关于 model 关系的信息,请参看 Accessing related objects 。关于怎样使用 double underscores 【双下划线?】去通过API完成字段查找,请参看 Field lookups 。 关于数据库 API 的全部信息,请参看 Database API reference。
当你适应于API,可以开始阅读 part 2 of this tutorial 去获取 Django 的 admin 是如何自动工作的。
来源:oschina
链接:https://my.oschina.net/u/1993835/blog/297550