[翻译]Django tutorial, part 1: Models

编写你的第一个Django app, part I

让我们通过一个例子来学习。

贯穿这个教程,我们将带你了解一个基本的poll应用的创建。

它将由两部分组成:

  • 一个让人们可以查看投票结果并在其中投票的公共站点
  • 一个admin站点,让你可以添加,改变和删除polls。

我们将假设你已经安装了Django 。你可以通过如下的命令来判断Django是否安装,及安装的版本:

$ python -c "import django; print(django.get_version())"

如果Django已经安装,你应该能够看到你安装的版本。如果没有,你将得到一条错误消息“No module named django”

这个教程是为Django 1.7和Python 3.2或更高的版本而写的。如果Django版本不匹配,你可以通过使用本页右下角的版本切换器,来参考你的Django版本的教程,或者将你的Django更新到最新版。如果你依然在使用Python 2.7,你将需要对示例代码做微小的调整,如注释里面描述的那样。

请参考 如何安装Django部分来了解如何移除老的Django版本并安装一个新的。

Where to get help:

如果在读这份教程时有任何问题,请post一个message给django-users,或顺便拜访#django on irc.freenode.net来与其他可能能够帮到你的Django用户交流。

创建一个工程

如果这是你第一次使用Django,你将不得不留意一些初始的设置。即,你将需要自动生成一些代码来建立一个Django 工程——一个Django实体的一系列设置,包括数据库配置,Django特定的选项和特定于应用的设置。

由命令行,cd进入一个你想要在其中保存你的代码的目录,然后执行如下的命令:

$ django-admin.py startproject mysite

这将在你的当前目录下创建一个名为 mysite的目录。如果这个命令没有起作用,则请参考 Problems running django-admin.py

注意

你需要避免将你的工程命名为Python或Django内建的组件名。特别地,这意味着你应该避免使用django之类的名字(这样的名字会与Django本身起冲突)或者test(这会与一个内建的Python package起冲突)。

你应该把这些代码放在哪儿?

如果你的技术背景主要在普通的老式的PHP(没有使用现代的框架),你可能习惯于把代码放在Web server的document root(在一个类似于/var/www的位置)。在Django里,你不要那样做。把这些Python 代码中的任何部分放在你Web server的document root都不是个好主意,因为这存在一定的风险,即人们有可能会通过web看到你的代码。那样对安全性不好。

把你的代码放在document root之外的一些目录,比如/home/mycode

让我们看一下startproject创建了些什么东西:

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

与你看到的不符?

最近默认的工程布局变了。如果你看到的是一个“flat”布局(没有内部的mysite/目录),你可能正在使用一个与这一版教程不匹配的Django版本。你将需要切换到老的教程或更新的Django版本。

这些文件是:
  • mysite/外面的根目录仅仅是你的工程的一个容器。它的名字与Django无关,你可以将它命名为任何你喜欢的名字。
  • manage.py:一个命令行的实用工具,使得你可以与这个Django工程以多种方式进行交互。你可以在django-admin.py 和 manage.py的部分读到关于manage.py的所有详细信息。
  • 内部的mysite/目录是你的工程实际的Python package。它的名字是Python package的名字,你将需要用它来导入它内部的任何东西(比如,mysite.urls)。
  • mysite/__init__.py一个空文件,它告诉Python这个目录应当被看作一个Python package。(如果你是一个Python新手,则请参考官方Pytho docs中的more about packages)。
  • mysite/settings.py这个是该Django工程的设置/配置项。Django settings将告诉你所有关于settings如何工作的东西。
  • mysite/urls.py这个是该Django工程的URL声明;一个你的Django站点的“目录”。在URL dispatcher中,你可以读到更多关于URLs的东西。
  • mysite/wsgi.pyWSGI兼容的web servers to server你的工程的一个入口点。参考How to deploy with WSGI来获取更多详细信息。

数据库设置

现在,编辑mysite/settings.py。它是一个普通的Python模块,拥有表示Django设置项的模块级变量。

默认情况下,配置使用SQLite。如果你是数据库新手,或者你只对使用Django有兴趣,则这是最简单的选择。Python中包含了SQLite,因而你不需要安装任何其它的东西来支持你的数据库。

如果你想要使用其它的数据库,则需要安装适当的database bindings,并在DATABASES 'default'项中修改如下的key来匹配你的数据库连接设置:

  • ENGINE – 是  'django.db.backends.sqlite3',  'django.db.backends.postgresql_psycopg2', 'django.db.backends.mysql', 或 'django.db.backends.oracle'中的一个。 也可以使用其它的后端。
  • NAME – 你的数据库的名字。如果你使用SQLite,数据库将是你计算机上的一个文件;在那种情况下,NAME应该是完整路径,包括那个文件的文件名。默认的值是os.path.join(BASE_DIR, 'db.sqlite3'),将会把文件保存在你的工程目录下。

如果你不使用SQLIte作为你的数据库,则必须添加额外的设置,比如USERPASSWORDHOST。更多详情,请参考DATABASES文档。

Note注意

如果你使用PostgreSQL或MySQL,则要确保此时你已经创建了一个数据库。可以通过在你的数据库的交互提示符下输入CREATE DATABASE database_name;这样的命令来完成。

如果你使用SQLite,则事先不需要创建任何东西 - 数据库文件将在需要的时候自动创建。

当你编辑mysite/settings.py时,请将TIME_ZONE设置为你的时区。

也要注意文件顶部的INSTALLED_APPS设置。它包含了这个Django实例中已经激活的所有Django应用的名字。Apps可被用于多个工程,并且你可以打包并分发给其他的用户以应用于他们的工程。

默认情况下,INSTALLED_APPS包含了如下的apps,它们都来自于Django:

  • django.contrib.admin – admin站点。你将在这份教程的part 2中使用到它。
  • django.contrib.auth – 一个身份认证系统。
  • django.contrib.contenttypes – 一个内容类型的框架。
  • django.contrib.sessions – 一个session框架。
  • django.contrib.messages – 一个消息框架。
  • django.contrib.staticfiles – 一个管理静态文件的框架。

为了大多数情况的方便,这些应用默认都不被包含了进来。

这些应用中的一些至少使用了一个数据库表,因而我们需要在使用它们之前在数据库中创建表。要做到这些,则执行如下的命令:

$ python manage.py migrate

migrate命令查看INSTALLED_APPS设置,并根据你的mysite/settings.py文件的设置及伴随你app的数据库迁移(我们将在后面讨论)创建任何需要的数据库表。对于它应用的每一项迁移你都将看到有一条消息输出。如果感兴趣的话,则可以运行你的数据库的命令行客户端,并键入\dt (PostgreSQL),SHOW  TABLES; (MySQL), 或 .schema (SQLite)来显示Django创建的表。

开发版服务器

让我们来验证一下你的Django工程是否工作。如果还没有的话则切换目录到mysite的外层,然后执行下面的命令:

$ python manage.py runserver
你将在命令行上看到如下的输出:

Performing system checks...

0 errors found
January 07, 2015 - 15:50:53
Django version 1.7, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

你已经启动的Django开发版服务器,一个完全用Python写的轻量级Web服务器。我们在Django中包含了它,以便于你可以快速地开发出东西来,而不是不得不处理一个产品级服务器——如Apache——的配置,直到你准备好了要正式上线。

现在是时候要注意一点了:不要在任何类似于一个生产环境的环境中使用这个服务器。它只是在开发时使用的。(我们的主业是创建Web框架,而不是web服务器。)

现在服务器运行起来了,通过你的Web浏览器来访问http://127.0.0.1:8000/。你将看到一个“Welcome to Django”页面,愉快的,淡蓝色的。它工作了!

改变端口

默认情况下,runserver命令在内部IP的8000端口启动开发版服务器。

如果你想要改变服务器的端口,则通过一个命令行参数来传递它。比如,这个命令在8080端口启动服务器:

$ python manage.py runserver 8080

如果你想要改变服务器的IP,则将它与端口号一起通过命令行参数传递。要在所有公共IPs上监听(如果你想要在别的计算机上炫耀你的成果时很有用),使用命令:

$ python manage.py runserver 0.0.0.0:8000

开发版服务器的完整文档可以在runserver参考中找到。

runserver的自动重新加载

如果有需要的话,开发版服务器自动地为每个请求重新加载Python代码。你不需要为了使修改的代码起作用而重新启动服务器。然而,某些行为,比如添加文件不触发一个重启,因而你将不得不在那些情况下重新启动服务器。

创建models

现在你的环境——一个“project”——建立起来了,可以开始工作了。

Django中你所写的每个应用由一个Python package及其某一convention组成。Django中带有一个实用工具来自动地产生一个app基本的目录结构,以便于你可以专注于写代码而不是创建目录。

Projects vs. apps

一个project和一个app的不同之处在哪里呢?一个app是一个Web应用,它做的事情是——比如,一个Weblog系统,一个公共记录的数据库或一个简单的poll app。一个project是一个配置及特定的Web站点的apps的集合。一个project可以包含多个apps。一个app可被用于多个projects中。

你的app可以放在你的Python path的任何位置。在这个教程中,我们将在你的manage.py文件所在的目录下创建我们的poll app,以便于它可以以它自己的顶级模块的形式被引入,而不是mysite的子模块。

要创建你的app,请确保你所在的目录与manage.py所在的目录相同,并键入下面的命令:

$ python manage.py startapp polls
这将创建一个名为polls的目录,它的目录结构大体如下:
polls/
    __init__.py
    admin.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

这个目录结构将放置poll应用。

用Django编写一个数据库Web app的第一个步骤是定义你的models——必不可少的,你的数据库的布局,及一些额外的元数据。

哲学

一个model是关于你的数据单独的,明确的数据源。它包含了必须的字段及你保存数据的行为。Django遵从DRY原则。目标是在一个地方定义你的数据模型,并自动地从它派生东西。

这包含migrations-不像在Ruby On Rails中,比如,migrations完全派生自你的models文件,它所必须的只是一个历史,Django可以跟踪来更新你的数据库schema来匹配你当前的models。

在我们简单的poll app中,我们将创建两个models:QuestionChoice。一个Question具有一个问题及一个发布日期。一个Choice具有两个字段: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)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

这个代码很简单。每个model由一个继承自django.db.models.Model类表示。每个model具有一定数量的类变量,其中的每一个表示model中的一个数据库字段。

每个成员由一个Field类的实例表示 – 比如,CharField表示字符字段,DateTimeField表示时间日期字段。这告诉Django每个字段的数据类型是什么。

每个Field实例的名字(比如question_textpub_date)是字段的名字,以机器-友好的格式。你将在你的Python代码中使用这个值,并且你的数据库将把它用作列名。

你可以使用可选的第一个位置参数给一个Field来指定一个易读的名字。它被用于Django内部的两个部分,并且它还扮演文档的角色。如果这个字段没有提供,Django将使用机器-可读的名字。在这个例子中,我们只定义了一个易读的名字Question.pub_date。对于这个model中所有其他的字段,机器-可读的名字将是足够易读的名字。

有些Field类需要提供参数。比如CharField,需要你给出一个max_length。它不仅被用于数据库schema,而且用于校验,稍后我们将会看到。

一个Field也可以有各种各样的可选参数;在这个例子中,我们设置了votes默认值为0。

最后,注意一个关系的定义,使用了ForeignKey。它告诉Django,每个Choice都与一个单独的Question关联。Django支持所有常见的数据库关系:多对一,多对多,和一对一。

激活models

那一小段 model的代码,却给了Django大量的信息。通过它,Django能够:

  • 为这个app创建一个数据库 schema(CREATE TABLE语句)。
  • 创建一个Python 数据库访问 API来访问Question和Choice对象。

但是首先,我们要告诉我们的project,polls app安装了。

哲学

Django apps是“可插入的”:你可以在多个projects中使用一个app,你也可以分发app,因为它们不是一定要绑定到一个给定的Django安装的。

再次编辑mysite/settings.py文件,并修改INSTALLED_APPS设置,以包含字符串'polls'。它看起来就像这样:

mysite/settings.py

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 makemigrations polls
你应该看到像下面这样的一些东西:

Migrations for 'polls':
  0001_initial.py:
    - Create model Question
    - Create model Choice
    - Add field question to choice

通过运行makemigrations,你告诉Django,你已经对你的models做了一些改变(在这个例子中,你创建了一些新的models),并且你希望这些改变被存储为一个migration。

Migrations是Django如何为你的models存储改动(及你的数据库schema)- 它们仅仅是磁盘上的文件。如果你喜欢的话,你可以为你的新model读取migration;它是文件polls/migrations/0001_initial.py。不用担心,你不会被期望去读Django创建的每一个migration,但它们被设计为是人类可编辑的,以防你想要手动的微调Django要如何做改动。

有一个命令将为你执行migrations,并自动地管理你的数据库schema - 那被称为migrate,我们将稍后再回到这个上面 - 但首先,让我们看一下migrations将执行什么SQL。sqlmigrate命令接收migration名字作为参数,并返回它们的SQL:

$ python manage.py sqlmigrate polls 0001

你应该能够看到一些类似于下面这些的东西(我们已经为了可读性而重新格式化它了):

BEGIN;
CREATE TABLE polls_question (
    "id" serial NOT NULL PRIMARY KEY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);

CREATE TABLE polls_choice (
    "id" serial NOT NULL PRIMARY KEY,
    "question_id" integer NOT NULL,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL
);

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产生的。
  • 表的名字是通过把app的名字(polls)和model名字的小写-questionchoice结合在一起自动生成的。(你可以覆写这样的行为。)
  • 主键(IDs)是自动添加的。(你也可以覆写这样的行为。)
  • 按惯例,Django会把"_id"附接到外键字段的名字上。(是的,你也可以覆写它。)
  • 外键关系是通过一个FOREIGN KEY约束显式创建的。不用担心关于DEFERRABLE的部分;那仅仅告诉PostgreSQL,不要实施外键,直到事务结束。
  • 它是为你所使用的数据库而裁剪了的,因而特定数据库的字段类型,比如auto_increment (MySQL),serial(PostgreSQL),或integer primary key autoincrement (SQLite)是为你自动处理了的。字段名字的引号也一样 - 比如,使用双引号或单引号。
  • sqlmigrate命令不会实际在你的数据库上运行migration - 它仅仅把它打印出来,以便于你能够看到Django认为所需要的SQL。想要检查Django将做什么,或如果你有数据库管理员,而他需要改动所需的SQL脚本时很有用。

如果你感兴趣,你也可以运行python manage.py check;这将在不进行migrations或操作数据库的情况下检查你的工程中的任何问题。

现在,来再次执行migrate,以在你的数据库中创建那些model表:

$ python manage.py migrate

Operations to perform:
  Synchronize unmigrated apps: sessions, admin, messages, auth, staticfiles, contenttypes
  Apply all migrations: polls
Synchronizing apps without migrations:
  Creating tables...
  Installing custom SQL...
  Installing indexes...
Installed 0 object(s) from 0 fixture(s)
Running migrations:
  Applying polls.0001_initial... OK

migrate命令提取所有的还没有应用的migrations(Django会使用你的数据库中一个称为django_migrations的特别的表来追踪哪些已经应用了)并在你的数据库上执行它们 - 本质上,同步你对你的models所做的改动与你的数据库中的schema。

Migrations非常强大,它是你能够随着时间逐渐开发你的工程而改变你的models,而不需要删除你的数据库或表并创建新的 - 它专门更新你的数据库,而不会丢失数据。在这份教程的后面我们将更深入的描述它们,但现在,记住三步指南来修改models:

  • 修改你的models(在models.py中)。
  • 执行python manage.py makemigrations来为那些改动创建migrations。
  • 执行python manage.py migrate来将那些改动应用到数据库。

使用分开的命令来创建和应用migrations的原因是因为,你将提交migrations到你的版本控制系统,并伴随着你的app一起分发;它们不止使你的开发更简单,他们也可以被其它开发者或产品使用。

参考django-admin.py documentation来了解manage.py实用工具所能做的事情的完整信息。

玩转API

现在,让我们进入交互式Python shell,并来自由地玩一下Django给你提供的API。要调用Python shell,使用这个命令:

$ python manage.py shell

我们使用这个命令,而不是简单地键入 “python”,因为 manage.py设置了 DJANGO_SETTINGS_MODULE环境变量,其为Django提供指向了你的 mysite/settings.py文件的Python import path。

绕过 manage.py

如果你不愿意使用manage.py,没问题。仅仅设置DJANGO_SETTINGS_MODULE环境变量为mysite.settings,启动一个plain Python shell,然后设置Django:

>>> import django
>>> django.setup()

如果这个地方产生了一个AttributeError,那么你可能使用了一个与这份教程不匹配的Django版本。你可能想要切换到更老版本的教程或更新版本的Django。

你必须在manage.py所在的目录运行python,或者确保那个目录在Python path中,以便于导入mysite能够成功。

关于这些的更多信息,请参考django-admin.py documentation。

一旦你进入了shell,则探索database API:

>>> from polls.models import Question, Choice   # Import the model classes we just wrote.

# No questions are in the system yet.
>>> Question.objects.all()
[]

# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# Save the object into the database. You have to call save() explicitly.
>>> q.save()

# Now it has an ID. Note that this might say "1L" instead of "1", depending
# on which database you're using. That's no biggie; it just means your
# database backend prefers to return integers as Python long integer
# objects.
>>> q.id
1

# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)

# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all() displays all the questions in the database.
>>> Question.objects.all()
[<Question: Question object>]

等一下。<Question: Question object>是,完全的,一个对于这个对象没有任何帮助的描述。让我们通过编辑Question model (在polls/models.py文件中)并给QuestionChoice都添加__str__()方法来解决它:

polls/models.py
from django.db import models

class Question(models.Model):
    # ...
    def __str__(self):              # __unicode__ on Python 2
        return self.question_text

class Choice(models.Model):
    # ...
    def __str__(self):              # __unicode__ on Python 2
        return self.choice_text
为你的models添加__str__()方法很重要,不仅仅是在处理交互提示时为了你自己而显得明智,而且对象的描述Djang也会被用于Django自动产生的admin。

__str__还是__unicode__?

在Python 3中,它很简单,只是使用__str__()就行。

在Python 2上,你应该定义__unicode__()方法来返回unicode值。Django models有一个默认的 __str__()方法,它调用__unicode__()并将结果转换为一个UTF-8字节串。这意味着unicode(p)将返回一个Unicode字符串,而str(p)将返回一个字节串,其字符都以UTF-8编码了的。Python执行相反的:object有一个__unicode__,它调用__str__,并将结果解释为一个ASCII字节串。这点不同可能会造成一些困扰。

如果这些有点乱,则只使用Python 3。

注意,这些是普通的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 datetimefrom django.utils import timezone,分别引用了Python的标准datetime模块,及django.utils.timezone中的Django的时区相关实用程序。如果你对Python中的时区处理不熟悉,你可以在time zone support docs中学到更多东西。

保存这些改动,并通过再次运行python manage.py shell启动一个新的Python 交互式 shell:

>>> from polls.models import Question, Choice

# Make sure our __str__() addition worked.
>>> Question.objects.all()
[<Question: What's up?>]

# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Question.objects.filter(id=1)
[<Question: What's up?>]
>>> Question.objects.filter(question_text__startswith='What')
[<Question: What's up?>]

# Get the question that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# Request an ID that doesn't exist, this will raise an exception.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question 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 Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>

# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# Give the Question 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 question's choice) which can be accessed via the API.
>>> q = Question.objects.get(pk=1)

# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
[]

# Create three choices.
>>> 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 objects have API access to their related Question objects.
>>> c.question
<Question: What's up?>

# And vice versa: Question objects get access to Choice objects.
>>> q.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> q.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 question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(question__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 = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

关于model relations的更多信息,请参考Accessing related objects。关于如何通过API使用双下划线执行字段查找,请参考Field lookups。关于数据库API的完整详细信息,请参考我们的Database API reference。

When you’re comfortable with the API, read part 2 of this tutorial to get Django’s automatic admin working.

原文地址:https://docs.djangoproject.com/en/1.7/intro/tutorial01/

你可能感兴趣的:([翻译]Django tutorial, part 1: Models)