Ethan's Blog

记录和思考

Django 基础入门教程

这里记录一下 Django 的基础入门教程,是根据官网提供的文档整理记录。

安装 Django 很简单,一条命令就可以了:pip install Django

新建一个项目

在终端中执行:

django-admin startproject mysite

这将会在终端当前的目录中新建一个 mysite 的文件夹。在实际应用中,最好不要在类似 /var/www 目录下新建这个项目文件夹,而是在 /home/mycode 中比较稳妥。

新建的 startproject 中的文件结构如下:

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py
  • 最外围的 mysite/ 目录是当前这个项目的容器,其名称对 Django 并不敏感,可以修改为自己需要的值。
  • manage.py 是一个命令行工具,可以用来和 Django 交互,执行相应的操作。文档见 django-admin and manage.py
  • 内部的 mysite 文件夹是项目真正的 Python 包,它的名称也是 Python 包名称,在 import 的时候使用,例如 mysite.urls。
  • mysite/__init__.py 是一个空文件,告诉 Python 这个目录是一个 Python 包。
  • mysite/settings.py 是 Django 项目的配置文件,文档见 Django settings
  • mysite/urls.py 是这个 Django 项目的 URL 声明,一个由 Django 驱动的站点的“目录”。文档见 URL dispatcher
  • mysite/asgi.py 是 ASGI 兼容的 Web 服务器的入口点。详见 How to deploy with ASGI
  • mysite/wsgi.py 是 WSGI 兼容的 Web 服务器的入口点。详见 How to deploy with WSGI

使用开发服务器

使用以下命令打开开发服务器:

python manage.py runserver

# 或者以下命令以特定 ip 和端口启动
django-admin runserver 1.2.3.4:8000

访问 http://127.0.0.1:8000/ 即可看到自己的 Django 项目网站。注意 runserver 打开的这个开发服务器不要在生产环境中使用。runserver 文档见 runserver

在开发过程中,runserver 打开的开发服务器会自动重新加载修改过的 Python 代码,因此无需在修改代码后重启服务器。特别地,添加新文件之后需要手动重启生效。

新建 Polls 应用

项目 project 和应用程序 apps 有什么区别?

应用程序是一个 Web 应用程序,它可以做一些事情,例如 Weblog 系统、公共记录数据库或小型投票应用程序。项目是特定网站的配置和应用程序的集合。一个项目可以包含多个应用程序。一个应用程序可以在多个项目中。

应用可以放在任意的 Python path 可以访问到的位置。本教程中放在与 manage.py 同级,使这个 app 可以作为自己的顶级模块导入,而不是作为 mysite 的子模块导入。

以下命令生成 app 文件及其相关的目录结构:

python manage.py startapp polls

生成的 polls 文件夹及其结构如下:

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

这个目录结构容纳了 polls 应用程序的相关代码。

编写第一个视图

打开 polls/views.py 文件,添加以下代码:

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

为了调用展示这个视图,需要将它映射到一个 URL,因此我们需要一个 URLconf,在 polls 文件夹下新建一个 urls.py 文件,新的 polls 目录结构如下:

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    urls.py
    views.py

在 polls/urls.py 文件中加入以下代码:

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

下一步是将 root URLconf 指向 polls.urls 模块。在 mysite/urls.py 文件中,import django.urls.include 并在其 urlpatterns 添加一个 include(),引入 polls.urls:

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('polls/', include('polls.urls')),
    path('admin/', admin.site.urls),
]

include() 函数允许引入其他 URLconfs。每当 Django 遇到 include() 时,它就会删除 URL 中与该项匹配的部分,并将剩余的字符串发送到引入的 URLconf 以进行进一步处理。

include() 背后的思想是简化 URLs 的即插即用。例如 polls 是在它自己的 URLconf (polls/urls.py)中,它可以放在 /polls/ 下,或者在 /fun_polls/ 下,或者在 /content/polls/ 下,或者任何其他根目录下,应用程序仍然可以工作。

需要注意的是,除了 admin.site.urls,其他任何时候都应当使用 include() 来引入包含其他的 URL 匹配。

经过以上配置,已经向 URLconf 中配置了 index 视图,访问 http://localhost:8000/polls/ 即可看到浏览器返回的 Hello, world. You’re at the polls index. 字符。

URLs 配置中的 path() 函数接受四个参数,其中 route 和 view 是必需参数,kwargs 和 name 是可选参数。

  • route 是一个包含 URL 模式匹配的字符串。在处理请求时,Django从 urlpatterns 中的第一个匹配模式开始,然后在列表中向下移动,将请求的 URL 与每个匹配模式进行比较,直到找到匹配的模式。匹配模式不会搜索域名以及 GET 和 POST 的参数,例如 https://www.example.com/myapp/https://www.example.com/myapp/?page=3 都将匹配到 myapp/。
  • view 是当 Django 发现匹配的模式时,调用的指定的视图函数。调用时以 HttpRequest 对象作为第一个参数,以路由中“捕获”的任何值作为关键字参数。
  • kwargs 实现在字典中将任意关键字参数传递给目标视图。
  • name 命名 URL,可以实现在 Django 的其他地方明确地引用这个 URL,尤其是在模板中。这个强大的特性允许全局修改 URL 时只需要在一个地方修改即可。

设置数据库

打开 mysite/settings.py,这是包含了 Django 项目设置的 Python 模块。通常,这个配置文件使用 SQLite 作为默认数据库。如果需要更改想要使用的数据库引擎,首先需要安装合适的 database bindings,然后在配置文件中 DATABASES ‘default’ 进行相应更改:

  • ENGINE – 可选值有 ‘django.db.backends.sqlite3’,’django.db.backends.postgresql’,’django.db.backends.mysql’,或 ‘django.db.backends.oracle’ 等等其他后端
  • NAME - 数据库的名称。如果使用的是 SQLite,数据库将是你电脑上的一个文件,在这种情况下, NAME 应该是此文件的绝对路径,包括文件名。默认值 os.path.join(BASE_DIR, ‘db.sqlite3’) 将会把数据库文件储存在项目的根目录。
  • 如果不使用 SQLite,则必须添加一些额外设置,比如 USER 、 PASSWORD 、 HOST 等等。想了解更多数据库设置方面的内容,请看文档:DATABASES

同时,在 mysite/settings.py 配置文件中,可以修改设置 TIME_ZONE 为自己的时区。

此外,在 mysite/settings.py 配置文件头部的 INSTALLED_APPS 设置项包括了在项目中启用的所有 Django 应用。应用能在多个项目中使用,也可以打包并且发布应用,让别人使用它们。

通常,INSTALLED_APPS 默认包括了以下 Django 的自带应用:

  • django.contrib.admin – 管理员站点。
  • django.contrib.auth – 认证授权系统。
  • django.contrib.contenttypes – 内容类型框架。
  • django.contrib.sessions – 会话框架。
  • django.contrib.messages – 消息框架。
  • django.contrib.staticfiles – 管理静态文件的框架。

这些应用默认启用,可以为常规项目提供方便。

这些默认应用有的使用了至少一张数据表,因此通过以下命令新建表:

python manage.py migrate

migrate 命令检查 INSTALLED_APPS 设置,根据 mysite/settings.py 中的数据库设置和每个应用的数据库迁移文件,为其中的应用创建需要的数据表。命令所执行的每个迁移操作都会在终端中显示出来。

需要注意的是,INSTALLED_APPS 设置里的应用并不是每个人都需要,因此可以根据自己项目需求,在运行 migrate 前从 INSTALLED_APPS 里注释或者删除掉它们。

创建模型

在 Django 里写一个数据库驱动的 Web 应用的第一步是定义模型 - 也就是数据库结构设计和附加的其它元数据。

模型是真实数据的简单明确的描述。它包含了储存的数据所必要的字段和行为。Django 遵循 DRY Principle 。它的目标是你只需要定义数据模型,然后其它的杂七杂八代码你都不用关心,它们会自动从模型生成。

投票应用中,需要创建两个模型:Question 和 Choice。Question 模型包括问题描述和发布时间。Choice 模型有两个字段,选项描述和当前得票数。每个 Choice 都与一个 Question 关联。

这些概念通过一个 Python 类来描述。向 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 每个字段要处理的数据类型。

每个 Field 类实例变量的名字(例如 question_text 或 pub_date )也是字段名,将会在 Python 代码里使用它们,而数据库会将它们作为列名。

定义某些 Field 类实例需要参数。例如 CharField 需要一个 max_length 参数。这个参数的用处不止于用来定义数据库结构,也用于验证数据。Field 也能够接收多个可选参数;在上面的例子中:将 votes 的 default 也就是默认值,设为 0。还可以使用 ForeignKey 定义一个关系。这将告诉 Django,每个 Choice 对象都关联到一个 Question 对象。Django 支持所有常用的数据库关系:多对一、多对多和一对一。

激活模型

上面的一小段用于创建模型的代码给了 Django 很多信息,通过这些信息,Django 可以:

  • 为这个应用创建数据库 schema(生成 CREATE TABLE 语句)。
  • 创建可以与 Question 和 Choice 对象进行交互的 Python 数据库 API。

但是首先得把 polls 应用安装到我们的项目里。

为了在项目中包含这个应用,需要在配置类 INSTALLED_APPS 中添加设置。因为 PollsConfig 类写在文件 polls/apps.py 中,所以它的点式路径是 ‘polls.apps.PollsConfig’。在文件 mysite/settings.py 中 INSTALLED_APPS 子项添加点式路径后,它看起来像这样:

INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

此时,polls 应用已经安装到我们的项目里了,接着运行以下命令:

python manage.py makemigrations polls

通过运行 makemigrations 命令,告诉 Django 你对模型文件进行了修改或新建,希望 Django 把修改的部分储存为一次迁移。

迁移是 Django 对于模型定义(也就是数据库结构)变化的储存形式 - 本质上是磁盘上的文件。它们被储存在 polls/migrations/0001_initial.py 里,并被设计成人类可读的形式,可以方便手动调整 Django 的修改方式。

Django 有一个自动执行数据库迁移并同步管理数据库结构的命令 - migrate。对于迁移命令会执行哪些 SQL 语句,可以通过执行 sqlmigrate 命令接收一个迁移的名称,然后返回对应的 SQL:

python manage.py sqlmigrate polls 0001

执行此命令,即可以看到输出的 SQL 命令,需要注意的是:

  • 输出的内容和使用的数据库引擎有关。
  • 数据库的表名是由应用名 polls 和模型名 question 和 choice 的小写形式连接而来。
  • 主键 id 会被自动创建。
  • 默认的,Django 会在外键字段名后追加字符串 “_id”。
  • 外键关系由 FOREIGN KEY 生成。
  • 生成的 SQL 语句是为所用的数据库定制的,所以那些和数据库有关的字段类型 Django 会自动处理。
  • 这个 sqlmigrate 命令并没有真正在数据库中执行迁移,只是把命令输出到屏幕上,让你检查 Django 认为需要执行哪些 SQL 语句。

现在,再次运行 migrate 命令,在数据库里创建新定义的模型的数据表:

python manage.py migrate

这个 migrate 命令选中所有还没有执行过的迁移(Django 通过在数据库中创建一个特殊的表 django_migrations 来记录执行过哪些迁移)并应用在数据库上 - 也就是将对模型的更改同步到数据库结构上。

迁移是非常强大的功能,它能让你在开发过程中持续的改变数据库结构而不需要重新删除和创建表 - 它专注于使数据库平滑升级而不会丢失数据。

至此,记住改变模型需要这三步:

  • 编辑 models.py 文件,改变模型。
  • 运行 python manage.py makemigrations 为模型的改变生成迁移文件。
  • 运行 python manage.py migrate 来应用数据库迁移。

使用 API

在 Python 命令行中可以使用 Django 创建的各种 API。通过以下命令打开 Python 命令行:

python manage.py shell

在这里不简单使用 python 打开命令行的目的是 manage.py 会设置 DJANGO_SETTINGS_MODULE 环境变量,使得 Django 根据 mysite/settings.py 文件来设置 Python 包的导入路径。

进入命令行后,尝试使用 database API:

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

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

# 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.
>>> 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()
<QuerySet [<Question: Question object (1)>]>

然而,<QuerySet [<Question: Question object (1)>]> 并不是一个友好的可以了解对象细节的结果。编辑 polls/models.py 中的模型的代码,给 Question 和 Choice 增加 __str__() 方法:

from django.db import models

class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text

class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text

为模型 __str__() 方法可以为命令行里使用带来方便,同时在 Django 自动生成的 admin 里也使用这个方法来表示对象。

继续为 Question 模型添加一个自定义方法:

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)

保存文件后,通过 python manage.py shell 命令再次打开 Python 交互式命令行:

>>> from polls.models import Choice, Question

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

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

# 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()
<QuerySet [<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)
<QuerySet [<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,关于使用双下划线通过 API 进行字段查找可以查阅 Field lookups。完整的数据库操作 API 见 Database API reference

使用 Django 管理界面

相关文章: