编写你的第一个Django应用, 第二部分
这个教程将从第一个分布结束地方开始。我们将设置数据库, 创建你的第一个模型model, 并且快速介绍一下Django自动创建的管理站点。
数据库设置
现在打开mysite/settings.py
。 这是一个不同的Python模块,里面模块级的变量用于表示Django的配置。
默认情况下面,这个配置将使用SQLite。如果你对于数据库接触不多,或者你只是对试用Django有兴趣,这是个最简单的选择。SQLite被包含在Python的标准库里面,所以你不需要安装任何其它的包来支持这个数据库的访问。当你开始第一个真实的项目的时候,你可能会从一开始就考虑比如PostgreSQL这种更具扩展性的数据库,以避免头痛的数据库切换问题。
如果你想用其他数据库,安装合适的数据库绑定,并且修改以下在DATABASES 'default'
项下面的关键字的值来匹配你的数据库连接设置。
ENGINE
-'django.db.backends.sqlite3', 'django.db.backends.postgresql', 'django.db.backends.mysql'
或者'django.db.backends.oracle'
这几个中的一个。其他类型的backends也有,请参看文档。NAME
- 你的数据库的名字。如果你使用SQLite, 这个数据库就是你电脑上的一个文件。这样的话Name
应该是一个完整的绝对路径,包括这个文件的文件名。默认值是,os.path.join(BASE_DIR, 'db.sqlite3')
, 会存在你 的项目目录下面。
如果你不使用SQLite作为你的数据库, 那就需要一个额外的设置,比如USER, PASSWORD, HOST
等。想要了解具体细节,请参看DATABASES
相关的文档。
对于SQLite以外的数据库
如果你在使用SQLite之外的数据库,请确保你在这个时候创建一个数据库。你可以在你的数据库命令行系统的提示符下执行"CREATE DATABASE <数据库名称>"
。
并且还要确保在你配置mysite/settings.py
里面的USER用户名具有”create database(创建数据库)”的权限。这对于接下来的教程中自动创建测试数据库是必须的。
如果你在使用SQLite, 你预先不用做任何操作 - 数据库文件将会在它被需要的时候自动创建。
当你修改mysite/settings.py
的时候,记得把TIME_ZONE
配置成你自己的时区。
还有你留意一下在文件开头部分INSTALLED_APPS
配置。这个地方配置了在这个Django实例里面被激活的Django应用名称。应用可以被用于多个项目,所以你可以把你自己的应用打包分发给其他人的项目使用。
默认情况下, INSTALLED_APPS
将包含Django包自带的以下几个应用。
django.contrib.admin
- 管理站点,我们马上就能看到这个的效果。django.contrib.auth
- 身份认证系统django.contrib.contenttyps
- 处理content types的框架系统django.contrib.sessions
- 会话框架django.contrib.messages
- 消息系统框架django.contrib.staticfiles
- 管理静态文件的框架。
这些应用被默认包含,满足一般应用的需求。
中间的一些应用将使用至少一个数据库表格, 所以我们在使用之前需要在数据库里面创建这些表格。为了做这个事,我们需要执行以下命令:
$ python manage.py migrate
这个migrate
命令将会查找INSTALLED_APP
的配置,然后根据在mysite/settings.py
里面的数据库配置和跟随应用带的数据库迁移文件(我们在后面会介绍数据库迁移文件)来创建必要的数据库表格。每一个迁移文件被应用,你都将看到一个消息。如果你有兴趣,你可以在你的数据库系统命令行里面输入命令来查看创建的数据库表格。\dt
用于Postgre SQL, SHOW TABLES;
用于MySQL, .schema
用于SQLite, SELECT TABLE_NAME FROM USER_TABLES
用于Oracle。
对于最小需求者
就好像我们之前说的, 默认的应用会被包括,但是不是每个人都需要使用他们的。如果你不需要某些或者全部,你可以在执行migrate
命令之前把他们从INSTALLED_APPS
配置中注释掉或者直接删除。migrate
命令只会在INSTALLED_APPS
里面的应用执行数据库迁移操作。
创建模型models
现在我们将定义我们的模型 - 也就是你的数据库的布局和一些额外的元数据。
理念
模型是你数据的一个单一,确定的来源表示。他包括了你需要存储的数据的基本字段和行为的定义。Django遵循DRY原则。目标就是在一个地方定义你的数据,然后自动从中获得其他东西。
其中就包括数据库迁移 - 不像Ruby On Rails, 迁移文件完全是从你的模型文件产生的, 仅仅是一堆历史数据, Django用来更新你的数据库结构用以匹配你当前的模型。
在我们简单的投票应用里面,我们将创建两个模型:Question
和Choice
。Question
里面包含了一个具体的问题和发布的日期。Choice
里面有两个字段:一个是选项的文本,还有一个是投票计数器。每个Choice
都需要和一个Question
关联。
这些概念都会用一个简单的Python类来描述。编辑polls/models.py
文件如下:
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)
这些代码很直接易懂。被一个模型都用一个django.db.models.Model
的子类表示。 每个模型都有一堆的类变量用来表示这个模型的数据库字段。
每个字段都用一个Field
类的实例表示 - 比如CharField
用来表示字符字段, DateTimeField
用来表示时间日期。这些用来告诉Django每个字段的类型。
每个字段实例的名字(比如question_text
或者pub_date)
)就是字段的名字,用机器友好的格式。你将在你的Python代码里面使用这些名字,并且你的数据库也将使用他们作为列名。
你可以使用一个可选的字符串用来表示一个人可以读的名字,放在第一个参数。这会用在Django的一堆内部组件里面,并且也可以作为文档。如果这个值没有被提供, Django将使用机器可读的名字。在这个例子里面,我们只给Question.pub_date
定义了人们可读的名字。对于其他的字段,那些机器可读的名字已经足够表达他的意思了。
一些Field
类会包含必填参数。比如CharField
,需要你给出一个max_length
(最大长度)参数。这个不单单用于数据库表格的类型,而且也会在Django数据验证里面起作用,我们很快就能看到。
一个Field
也可以包含很多可选参数,比如在这个例子里面
vote
的default
值我们设置为0。
最后,我们还使用ForeignKey
定义了一个关系。这将告诉Django每个Choice
都和一个Question
进行关联。Django支持所有一般的数据库关系:多对一, 多对多和一对一。
激活模型
这些少量的模型代码给了Django大量的信息。有了它,Django就可以:
- 给这个应用创建数据库表格(使用
CREATE TABLE
命令) - 创建Python数据库访问API用于访问
Question
和Choice
对象
但是首先我们先要告诉我们的项目,这个点名应用被安装了。
理念
Django的应用都是可插拔的:你可以在多个项目里面使用相同的应用,你也可以分发你的应用因为他们不需要和给定的Django安装绑定。
为了在我们的项目里面安装这个应用,我们需要添加一个引用到我们配置类的INSTALLED_APPS
设置。PollsConfig
在polls/apps.py
文件里面,所以它的Python引用路径是'polls.apps.PollsConfig
。编辑mysite/settings.py
文件,然后把这个引用添加到INSTALLED_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',
]
现在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你对于你的模型有了一些修改(在我们的情况中,我们是添加了新的模型)并且你想要把这个改动保存成一个迁移文件。
迁移时Django存储你的模型修改(也就是你的数据库结构)的一个方式 - 他们仅仅是磁盘上的文件。如果你想的话,你可以读取你新模型的迁移文件;文件是polls/migrations/0001_initial.py
。不要担心,你不是每次都需要读取这些Django生成的文件,但是这些文件被设计成人们可读的结构,用于在某些情况下面手工调整。
另外有个命令用于运行迁移,并且管理数据库的结构。我们称为迁移migrate
,我们接下来马上就能看到 - 但是首先让我们看看那些SQL语句会在迁移里面被运行。sqlmigrate
命令接收迁移名字作为参数并返回对应的SQL代码:
$ python manage.py sqlmigrate polls 0001
你应该能够看到类似下面的输出(为了便以阅读我们稍做了格式化):
BEGIN;
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
"id" serial NOT NULL PRIMARY KEY,
"choice_text" varchar(200) NOT NULL,
"votes" integer NOT NULL
);
--
-- Create model Question
--
CREATE TABLE "polls_question" (
"id" serial NOT NULL PRIMARY KEY,
"question_text" varchar(200) NOT NULL,
"pub_date" timestamp with time zone NOT NULL
);
--
-- Add field question to choice
--
ALTER TABLE "polls_choice" ADD COLUMN "question_id" integer NOT NULL;
ALTER TABLE "polls_choice" ALTER COLUMN "question_id" DROP DEFAULT;
CREATE INDEX "polls_choice_7aa0f6ee" ON "polls_choice" ("question_id");
ALTER TABLE "polls_choice"
ADD CONSTRAINT "polls_choice_question_id_246c99a640fbbd72_fk_polls_question_id"
FOREIGN KEY ("question_id")
REFERENCES "polls_question" ("id")
DEFERRABLE INITIALLY DEFERRED;
COMMIT;
注意下面这些:
- 具体的输出和你的数据库有关。上面例子里面用的是PostgreSQL。
- 表格的名字会自动使用应用的名称
polls
和模型的小写名字question
和choice
拼出来的名字。(你可以修改这个行为)。 - 主键(IDs)会被自动生成。(你也可以改变这个行为)
- 按惯例,Django给外键字段的名字添加“
_id
”。(当然你可以修改这个行为。) - 外键关系会被显示使用
FOREIGN KEY
约束。不要担心DEFERRABLE
部分;这只是告诉PostgreSQL不用应用外键约束知道事务完成。 - 它会根据你使用的数据库类型进行裁剪,所以一些数据库特有的字段类型比如
auto_increment(SQL), serial(PostgreSQL)
或者integer primary key autoincrement(SQLie)
会被自动处理。同样字段名的引用也会自动处理 - 比如使用单引号还是双引号。 sqlmigrate
这个命令并不会直接在你的数据库上运行数据库迁移 - 它只会打印对应的SQL语句到频幕上。这很有用如果你想要检查一下对应的SQL语句,或者你需要提交SQL脚本给数据库管理员。
如果你有兴趣,你还可以运行python manage.py check
来检查迁移是否会有问题,而不会真正执行迁移或者修改你的数据库。
现在,继续运行migrate
命令来将新的表格应用到数据库。
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
Rendering model states... DONE
Applying polls.0001_initial... OK
这条migrate
命令将会对所以还没有应用的迁移进行迁移操作(Django会使用你的数据库里面一个特殊的表格django_migrations
来跟踪你的迁移状态)。也就是同步你的模型代码和你的数据库结构。
迁移功能十分强大,可以让你在开发的过程中更新数据库结构而不用手动去删除,添加,修改数据库的表格 - 甚至可以让你在线升级数据库系统而不丢失数据。我们将在教程的后面深入介绍,但是现在,记住需要3步来更新你的模型修改。
- 修改模型文件(在
models.py
里面)。 - 运行
python manage.py makemigrations
来根据修改创建迁移文件。 - 运行
python manage.py migrate
来应用这些修改到数据库中。
分成多个命令来生成和应用迁移文件的理由是你将会提交你的迁移文件到你的版本管理系统中,然后和你的应用一起分发出去;这不仅是你的开发变得容易,同样对于其他的开发人员以及生产环境同样有用。
你可以阅读《django-admin文档》来获得如何使用manage.py
这个工具的完整信息。
试玩一下API
现在我们可以使用交互式的Python shell来试玩一下Django提供给你的API。使用以下命令启动对应的Python shell。
$ python manage.py shell
我们使用这个命令而不是简单的输入”python”,主要是由于manage.py
会设置DJANGO_SETTTINGS_MODULE
环境变量,这样就会导入mysite/settings.py
文件。
跳过manage.py
如果你并不打算使用manage.py
,没问题。只要你设置DJANGO_SETTINGS_MODULE
环境变量为mysite.settings
,然后开启一个普通的Python shell,然后设置Django:
>>> import django
>>> django.setup()
如果它抛出AttributeError
异常,那么你很有可能在使用一个不匹配当前教程的Django版本。你需要切换到一个旧版的教程或者更新到新版的Django版本。
你必须在manage.py
所在目录运行python
,或者确保你的项目目录被设置在Python搜索路径上,这个import mysite
才能工作。
如需获得更多信息,参见《django-admin文档》
一旦你进入到shell,你就可以探索这些数据库API:
>>> from polls.models import Question, Choice #导入我们所写的类
# 没有Question对象
>>> Question.objects.all()
<QuerySet []>
# 创建一个新的Question.
# 时区的支持在默认的配置文件里面已经开启,
# 所以Django会期望有tzinfo的datetime数据给pub_date参数。
# 使用timezone.now()而不是datetime.datetime.now(),它会做正确的事。
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
# 保存对象到数据库,你必须显示的调用save()方法
>>> q.save()
# 现在这个对象就有了一个ID。请注意它可能输出“1L”而不是“1”,取决于你使用了哪一个数据库。那不是什么问题。它只是表示你的数据库更愿意返回一个Python长整型对象。
>>> q.id
1
# 使用Python属性访问模型字段
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
# 通过属性,修改字段的值,然后调用save()
>>> q.question_text = "What's up?"
>>> q.save()
# objects.all()想显示所有在数据库里面的question对象。
>>> Question.objects.all()
<QuerySet [<Question: Question object>]>
停一下。<Question: Question object>
就是一个完全没什么帮助信息的对象显示。让我们通过修改Question
模型(在polls/models.py
文件里),给Question
和Choice
类添加__str__()
方法来修复这个问题:
polls/models.py
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible # 只在你需要支持Python 2的时候使用
class Question(models.Model):
# ...
def __str__(self):
return self.question_text
@python_2_unicode_compatible # 只在你需要支持Python 2的时候使用
class Choice(models.Model):
# ...
def __str__(self):
return self.choice_text
添加__str__()
方法很重要,不单单是为了你自己在交互式shell里面的方便,而且这个对象的表示会在所有Django自动生成的管理站点里面被使用。
注意到,这些都是普通的Python方法。让我们添加一个自定义的方法作为演示:
polls/models.py
import datetime
from django.db import models
from django.utils import timezone
class Question(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标准的datetime
模块和Django在django.utils.timezone
里的时区相关附件。如果你并不熟悉时区在Python里面的处理,你可以在《时区支持文档》里学习更多相关信息。
保存这些修改,然后再次通过python manage.py shell
新开一个Python的交互式shell。
>>> from polls.models import Question, Choice
# 确保我们的__str__()添加时工作的。
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
# Django通过关键字参数驱动的方式提供数据库的查询。
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>
# 获得在今年发布的问题
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>
# 请求一个不存在的ID,这里会抛出一个异常
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.
# 用主键查找是很寻常的事,所以Django提供了一个便捷方式
# 下面的代码和Question.objects.get(id=1)一样
>>> Qestion.objects.get(pk=1)
<Question: What's up?>
# 确保你的自定义方法是工作的。
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True
# 给问题一组选项(Choices)。这个create的调用将会新建一个Choice对象,调用INSERT语句,添加这个Choice对象到已有选项列表中,然后返回这个新的Choice对象。
# Django创建了一个集合用来访问外键关系的另外一边,可以使用API来访问。
>>> q = Question.objects.get(pk=1)
# 显示对应对象所有的Choice对象 -- 现在还没有
>>> q.choice_set.all()
<QuerySet []>
# 创建三个选项
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
# Choice对象有API可以访问它对应的Question对象。
>>> c.question
<Question: What's up?>
# 反过来, Question对象也可以访问对应的Choice对象
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3
# API还可以顺着关系走到你需要的任何对象。
# 使用双下划线来分隔那些关系。
# 任何深度都可以工作,这里没有限制。
# 查找发布日期是今年的问题的所有选项
# (我们重用上面创建的current_year变量)
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
# 让我们使用delete()方法删除一个选项
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
关于模型关系的更多信息,参见《访问相关对象》。对于如何使用双下划线来访问查询API,参见《字段查询》。需要数据库API的完整信息,参见我们的《数据库API参考》
介绍Django管理站点
原则
给你的员工或者客户创建用来添加,修改,删除内容的管理站点是一个非常沉闷的工作,而不需要许多创意。由于这里原因,Django完全自动化了模型管理接口的创建。
Django是在一个新闻室的环境下编写的,很清楚内容发布者和对外网站之间清晰的分离。站点管理者使用系统来添加新闻,时间,运动成绩等,内容就在对外的站点上显示出来。Django通过给站点创建同意的接口给管理员编辑内容来解决这个问题。
管理站点并不定位于网站访问者。这只用于站点管理者。
创建一个管理用户
首先你需要创建一个用户用来登陆管理站点。运行一下命令:
$ python manage.py createsuperuser
输入你想要的用户名然后回车。
Username: admin
然后你会被要求输出你的Email地址:
Email address: admin@example.com
最后一步是输入你的密码。你会被要求输入两次,第二次是用来确认第一次的输入。
Password: **********
Password (again): **********
Superuser created successfully.
启动开发服务器
Django管理站点默认就是激活的。让我们启动开发服务器试试它。
如果服务没有运行,你可以用下列命令开启它:
$ python manage.py runserver
现在,代开浏览器,然后访问”/admin/”使用你的本地域名 - 比如http://127.0.0.1:8000/admin/。然后你应该可以看到一下登陆界面:
由于翻译是默认打开的,如果Django支持这个语言的翻译,你的登陆界面可能会基于你浏览器的设置使用你的语言显示。
登陆管理站点
现在,尝试使用你刚才创建的超级用户账号来登陆这个站点。你应该看到Django管理站点的主页:
你应该能够看到一些类型的可编辑内容: 组和用户。他们是django.contrib.auth
模块提供的,这个认证框架是Django自带的。
让投票应用在管理站点里面可以修改
但是我们的投票应用呢?这个并没有在管理站点的主页上显示。
我们还有一件事需要做:我们需要告诉管理站点Question
对象需要一个管理接口。我们只需打开polls/admin.py
文件,然后按照下面编辑:
polls/admin.py
from django.contrib import admin
from .models import Question
admin.site.register(Question)
探索免费的管理功能
现在我们已经注册了Question
对象,Django知道它应该被显示在管理站点的首页:
点击“Questions”。你可以到了Questions对象的修改列表页。这个页面显示了数据库里面的所有问题,以及让你选择一个修改它。里面现在有一个你之前创建的“What’s up?”问题:
点击“What’s up?”问题就可以编辑它了:
这里需要注意的是:
- 这个表单是根据
Question
模型自动生成的。 - 不同模型的字段类型(
DateTimeField
,CharField
)相对于合适的HTMP输入组件。每个类型的字段知道如何在Django管理界面里面显示自己。 - 每个
DateTimeField
都有JavaScript的快捷输入。日期有一个“Today”(今天)的快捷输入以及弹出日历,时间有一个“Now”的快捷输入和一个方便的弹出列表,里面有通常需要的输入时间。
页面的底部给了你一组选项: - Save - 保存修改并且返回到这个类型对象的修改列表页面
- Save and continue editing - 保存修改并且重新加载这个对象的管理页面
- Save and add another - 保存修改,并且加载一个这个对象类型的新的,空白的表单。
- Delete - 显示一个删除确认页面。
如果“Date published”(发布日期)的值和你在教程1里面创建Question对象的时间不匹配的话,很可能是因为你忘记设置正确的TIME_ZONE
了。修改它,重新加载页面来检查是否能显示正确的值。
通过点击“Today”(今天)和“Now”(现在)快捷方式修改“Date published”(发布日期)。然后点击“Save and continue editing.”(保存然后继续修改)。然后点击右上角的“History”(历史记录)。你可以看到一个页面列出了所有通过Django管理界面对这个对象修改的记录,还包含修改的用户和修改时间:
当你觉得对模型API觉得适应并且对管理界面开始熟悉的时候,你就可以开始阅读教程的第三部分来学习如何添加更多的视图到我们的投票应用中去了。