Django-1.7.3-Tutorial-Part-1-模型
Tim Chen(motion$) Lv5

创建你的第一个Django app-Part 1

  • 让我们从简单的例子开始吧。
  • 通过这个教程,我们将快速学习投票(poll)应用的创建。
  • 环境:windows 8 32 bits + Python 2.7.5 + Django 1.7.3
  • 它包含两个部分:
    • 一个供人们查看和进行投票的公开的站点。
    • 一个管理员站点,可以让你进行添加,修改和查询投票。
  • 我们假设你已经装好了Django.你可以通过以下命令查看你安装的版本。
    1
    $ python -c "import django; print(django.get_version())"
  • 如果你成功安装了Django,你就会看到版本号,否则,你就会得到一个错误信息:”No module named django”.
  • 我的Django信息如下:

创建项目

  • 如果你是第一次使用Django,那么你应该注意一些初始化的设置。也就是说,你需要一些自生成的代码来构建一个Django项目,包括Django实例的设置文件,譬如数据库配置文件,Django指定选项和应用指定的设置。
  • 从命令行中切换到你想把项目存储的目录,然后输入以下命令:
    1
    $ django-admin.py startproject mysite
  • 在当前目录下就会生成一个mysite的目录。如果没有,请查看问题反馈

笔记
请注意命名,不能和Python或者Django组件中的关键字重名,特别的,你不能使用django或者test。

  • 通过startproject命令创建的目录如下:

和你建的目录不同?
默认项目布局最近有所改变。如果你看到一个’flat’布局(里面没有mysite/目录),你可能使用的Django和本教程的版本不同。请移步。

  • 目录路径解释:
    • 外层mysite/:你的项目的根目录。它的名字可以随意更改。
    • manage.py文件:它是一个和Django进行多样化交互的命令行工具。你可以从以下链接查看详细内容django-admin.py and manage.py
    • 内层mysite/:它是项目的真正的python包。名字不可随意更改,因为它是在其他地方可供导入的(例如:mysite.urls)
    • mysite/init.py:一个空文件,它的作用是标注自己是一个python包。
    • mysite/settings.py:Django项目的设置或配置。详细可查看Django settings
    • mysite/urls.py:对Django项目的URL声明,就像是你的Django网站的目录列表。详细可查看URL dispatcher
    • mysite/wsgi.py:它是配置WSGI服务器的入口点。详细可见:How to deploy with WSGI

数据库配置

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

  • 默认情况下,配置是用SQLite。如果你不熟悉数据库,或者你只想玩玩Django,这是最快捷的选择。SQLite包含在Python中,所以不需要你安装。

  • 如果你想用其他数据库,安装相应数据库绑定,然后修改DATABASES中的默认选项来匹配你的数据库连接设置:

    • ENGINE-数据库引擎,SQLite(默认):**’django.db.backends.sqlite3’;postgresql:‘django.db.backends.postgresql_psycopg2’;mysql:‘django.db.backends.mysql’**;Oracle: **’django.db.backends.oracle’**,或其他。
    • 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可以被多个项目使用,你可以把Apps打包和发布给其他项目使用。

  • 默认情况下,INSTALLED_APPS配置包含Django中的apps,如下:

    • django.contrib.admin-管理员站点,你在Part2中会用到。
    • django.contrib.auth-认证系统
    • django.contrib.contenttypes-一个内容类型的框架
    • django.contrib.sessions-session框架
    • django.contrib.messages-messaging框架
    • django.contrib.staticfiles-管理静态文件的框架
  • 这些apps默认添加的,你可以根据自己需求修改。

  • 这些apps中可能要用到数据库表,所以,我们应该先要在数据库中创建表,然后再使用。命令如下:

    1
    $ python manage.py migrate
  • migrate命令会查看INSTALLED_APPS配置,然后根据mysite/settings.py文件创建一些需要的数据库表,并把数据库迁移到相应的应用中。n你可以查看信息:运行数据库客户端,在命令行中输入\dt (PostgreSQL), SHOW TABLES; (MySQL), or .schema (SQLite)。

发布到服务器

  • 验证你的项目是否可运行,在外层mysite目录下,运行一下命令:
    1
    $ python manage.py runserver
  • 你可以看到以下输出(我的版本如下):
    1
    2
    3
    4
    January 16, 2015 - 14:18:47
    Django version 1.7.3, using settings 'mysite.settings'
    Starting development server at http://127.0.0.1:8000/
    Quit the server with CTRL-BREAK.
  • 你已经把项目成功发布到服务器上了,它是一个用纯python实现的轻量级的Web server。它已经被包含到Django当中,方便我们进行快速的开发。
  • 它不可以用于商用,因为能力有限。我们是开发框架而不是服务器。
  • 现在打开浏览器,输入”127.0.0.1:8000/“,你就可以访问到你的项目页面了。效果如下:

修改端口
默认端口是8000,如果你想修改,可以按如下格式发布项目

1
$ python mange.py runserver 8080

如果你想修改服务器的IP,如下格式:

1
$ python manage.py runserver 0.0.0.0:8000

详细文档参考runserver

服务器自动加载,不用重新发布项目。

创建模型(models)

  • 现在你项目的基本环境已经搭建好,你可以开始做事了。

  • 在Django中创建的app都有相同的目录结构,但是Django已经用过工具自动生成了,而不必你操心,你只要专注于写代码就好了。

    Projects VS apps
    项目和app之间有什么不同呢?一个app表示网站程序中的一个功能,譬如,一个网路博客系统,一个公开记录或公共投票的数据库应用。而一个项目(project)往往是app的集合,几个app组成一个project,另外,一个app可以被多个项目包含。

  • 如果你的python环境已经配置好,那么你的app可以在任何地方创建。这里我们把polls应用创建在manage.py的旁边,所以,它可以被当成自己的顶级模块导入,而不是mysite的子模块。

  • 创建app,请到manage.py所在目录下,执行命令:

    1
    $ python manage.py startapp polls
  • 创建了一个polls目录,结构如下:

  • 这个目录包含polls应用。

  • 通过Django写数据库网页应用的第一步是定义自己的模块(models)-主要的是,你的数据库布局,加上额外的元数据。

哲学
一个模型是一个独立的,明确的数据资源。它包含主要的字段和你存储的数据的行为。Django遵循DRY原则。目的是从一个地方定义和导出数据。
还包括迁移,不像Ruby On Rails,例如,模型中的数据可以全部迁移,但它只不过是Django中的一个历史记录,可以随时回滚到当前适应的模型。

  • 在简单的投票(polls)应用中,我们创建2个模型:QuestionChoiceQuestion有一个问题和发表日期。Choice有两个域:选择文本和投票计数器。每一个Choice关联一个Question
  • 这些概念都能通过简单的Python类实现。编辑polls/models.py文件,如下:
  • polls/models.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    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)

  • 代码非常简单明了。每个模型都是继承类**django.db.models.Model。每个模型都有一些类变量,其中每个变量都代表数据库中的一个字段**。
  • 每个字段都是一个字段(Field)的实例–譬如,CharField代表字符域,DateTimeField代表数据库实时间。这样Django就知道每个字段是什么类型的。
  • 每个域实例的名字(譬如question_text或者pub_date)就是字段名称,它是机器能够识别的形式。字段的值会用python代码表示,数据库就会把字段名称当成一个列名。
  • 你还可以为每个字段起一个易于让人读懂的名字。在Django中是支持的,而且也可以记录到文档中。如果你美誉定义这个名字,那么Django就会用机器识别的名字。例如,我们只为Question.pub_date定义一个易于让人都懂的名字。对于模型中的其他字段,机读和人读的名字是一样的。
  • 一些字段类中有写必需的参数,例如CharField,我们必须给它指定一个最大长度**(max_length)**。它不仅仅需要数据库模式,还需要验证的,后面我们就会将到。
  • 一个字段还可以有多个可选的参数,譬如,我们把vote的默认值设为0.
  • 最后,要定义一个关系,用ForeignKey,即是外键。它向Django表明每个Choice关联一个Question。Django支持多种数据库映射关系:多对一,多对多,一对一。

激活模型(Activating models)

  • 上面一小段代码给了Django很多的信息。通过代码,Django可以做到:

    • 为这个app创建一个数据库模式(CREATE TABLE 语句)
    • 创建一个Python数据库可用API,用于使用QuestionChoice对象。
  • 但是我们先要告知项目polls已经安装了。

    哲学
    Django的app是可插的(像USB一样,适配多台电脑):你的app可以插入到多个项目当中,你也可以发布app,这样它(app)就不用绑定到Django中用于安装了。

  • 再一次编辑mysite/settings.py文件,然后修改INSTALLED_APPS的内容,让它包含’polls‘,修改后的文件是:

  • mysite/settings.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'polls',
    )
  • 现在Django知道应该包含polls这个app了。让我们运行另外一个命令:

    1
    $ python manage.py makemigrations polls
  • 你应该看到以下的输出:

    1
    2
    3
    4
    5
    6
    Migrations for 'polls':
    0001_initial.py:
    - Create model Choice
    - Create model Question
    - Add field question to choice

  • 通过makemigrations口令,你在告诉Django你的模型有了修改(这样,你就新建了一个模型),你希望修改转存的过程当成迁移。

  • Migrations记录着你对模型做了什么修改(也即是数据库模式)-它们只是硬盘上的文件。你可以读取你新模型的migration如果你喜欢,它保存在polls/migrations/0001_initial.py文件当中。不要担心,你不需要每当Django创建一个的时候就读取一次,但它还是可编辑的,当你想对它进行修改的时候。

  • 有一个口令可以帮你运行migration并且自动管理你的数据库模式,它是migrate,我们就要将到它了,但首先,让我们看一下migration会运行什么样的SQL代码。sqlmigrate口令得到migration的名字并且返回它们的SQL。

    1
    $ python manage.py sqlmigrate polls 0001
  • 你应该会看到以下相似的代码:(我们已经把代码格式化了,为了让我们更易读懂它)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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_question和polls_choice。(你可以自己更改)
    • 主见(IDs)是自增长的,你也可以自行修改。
    • 按照惯例,Django会在外键字段名字后面加上**_id**,(当然这个你也可以修改)。
    • 外键关系是通过FOREIGN KEY这个约束来清晰定义的。不要担心不同的部分,它仅仅是告知PostgreSQL在结束事务之前不要强行实施外键。
    • 它是根据你用的数据库来适配的,所以数据库指定的域类型,好像,自动适配auto_increment(MySQL),serial(PostgreSQL),或者integer primary key autoincrement(SQLite),给字段名加引号是一样的,加单引号或双引号。
    • sqlmigrate口令并不会真正在你的数据库上运行migtaration-只是把它打印到屏幕上,让你知道Django需要什么SQL。知道Django正在做什么或者你拥有可以修改SQL资格的数据库管理员是非常有用的。
  • 如果你有兴趣,你也可以运行python manage.py check口令,它会检测你的项目是否有问题或者创建数据库是否顺利。

  • 现在,再次运行migrate来创建你数据库上的模型:

    1
    2
    3
    4
    Operations to perform:
    Apply all migrations: admin, contenttypes, polls, auth, sessions
    Running migrations:
    Applying polls.0001_initial... OK
  • migrate口令处理所有那些没被处理的migrations。(Django通过你数据库当中的特殊表django_migrations来跟踪那些被处理了或者那些没有处理)

  • migrations非常强大,而且当你在开发你的项目时,它可以让你随着时间变化修改你的模型,而不需要删除当前的数据库去创建一个新的。我们稍后再对这部分做深入研究,现在,请记住以下三步来对你的模型进行修改:

    • 修改模型(在models.py文件中)
    • 运行python manage.py makemigrations来为这些修改创建migrations
    • 运行python manage.py migrate把这些修改应用(apply)到数据库
  • 为什么make和apply这两步要分开执行呢?原因是你要首先提交migrations到你的版本控制系统,然后在把它们和你的app一起执行。这样会不仅会令你的开发更容易,还便于其他的开发者使用和再次开发。

  • 阅读django-admin.py documentation得到manage.py工具类的更详细的信息。

玩转API

  • 现在,让我们投进可交互的Python shell,玩一下Django提供给我们的免费的API。进入Python Shell,用这个命令:
    1
    $ python manage.py shell
  • 我们用python manage.py而不用python的原因是manage.py设置了DJANGO_SETTINGS_MODULE的环境变量,也就是说,Django和python的路径都加入了你的mysite/settings.py文件中。

跳过manage.py(Bypassing manage.py)
如果你不想使用manage.py,没问题,只需要把DJANGO_SETTINGS_MODULE的环境变量设置到mysite.settings中,新开一个纯净的Python shell,然后设置Django:

1
2
>>> import django
>>> django.setup()

如果这样报了一个AttributeError错误,那么你现在使用的是一个不匹配的Django版本。请更换不同的Django版本。
你必须在manage.py的目录下运行python,或者确保你的目录加入了Python路径,这样import mysite才起作用。
更多详情,请看django-admin.py documentation

  • 当你进入了shell,可以探索[database API](database API:)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    In [1]: from polls.models import Question, Choice # Import the model classes we just wrote
    # No questions are in the system yet.
    In [2]: Question.objects.all()
    Out[2]: []

    # Create a new Question.
    # Support for time zones is enabled in the default settings file, so Django experts a datatime with tzinfo for
    # pub_date.Use timezone.now()
    # Instead of datatime.datetime.now() and it will do the right thing.
    In [3]: from django.utils import timezone

    In [4]: q = Question(question_text="What's new?", pub_date = timezone.now())

    # Save the object into the database. You have to call save() explicitly.
    In [5]: 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 returns to return integers as Python long integer objects.
    In [6]: q.id
    Out[6]: 1

    # Access model field values via Python attributes.
    In [7]: q.question_text
    Out[7]: "What's new?"

    In [8]: q.pub_date
    Out[8]: datetime.datetime(2015, 1, 18, 2, 22, 53, 431000, tzinfo=<UTC>)

    # Change values by changing the attributes, then calling save().
    In [9]: q.question_text = "What's up?"

    In [10]: q.save()

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

  • 等一下,**<Question: Question object>似乎对这个对象来说表现很差。我们可以这样来修正它,修改Question模型(在polls/models.py文件中),为QuestionChoice分别添加一个unicode**方法(就像java中重写toString()方法一样)。

  • polls/models.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from django.db import models

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

    class Choice(models.Model):
    # ...
    def __unicode__(self): # __str__ on Python 3
    return self.choice_text
  • 为你的模型添加__unicode__()方法非常重要,不仅仅方便于你和Python进行交互,还方便通过Django自动生成的管理员对对象的展现。

    str()方法还是__unicode__()方法
    在Python 3,就简单的用**str()方法
    在Python 2中,你应该定义
    unicode()方法返回的是unicode值。Django模型有一个默认的str()方法,它调用了unicode()方法并且把结果转化成了一个UTF-8字节型字符串。也就是说unicode(p)方法返回的是一个Unicode字符串,而str(p)返回的是一个字节型的字符串,编码格式是UTF-8。Python刚好相反:object有一个unicode()方法,它调用了str()**方法,并把结果解释成一个ASCII的字节型的字符串。这些不同很容易混淆。
    如果你觉得以上内容很混乱,那请直接用Python 3吧。

  • 看完了一般的Python方法,让我们添加一个自定义的方法,说明一下:

  • polls/models.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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交互:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    >>> 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()
  • 关于更多信息,请看

  • [Accessing related objects](Accessing related objects),

  • Field lookups,

  • Database API reference.

  • 如果你熟悉了API,那么请读下一篇Part2-Django’s automatic admin working.

 评论