【Django入门·基础知识】编写第一个Django应用——《Python Web开发框架Django》
编写第一个Django应用
请求和响应
我们可以通过示例来学习如何创建一个基本的投票应用程序。
在本教程中,我们将引导你逐步构建一个投票系统,它包含以下两个主要部分:
- 一个公共站点,供用户查看和投票;
- 一个管理站点,允许你添加、修改和删除投票选项。
在开始之前,我们假设你已经安装了Django框架。要确认Django是否已安装以及安装的版本,请在命令提示符(Windows)或终端(Mac/Linux)中输入以下命令(命令前通常有$符号作为提示符):
Windows:
py -m django --version
Linux/MacOS:
python -m django --version
如果这行命令输出了一个版本号,证明你已经安装了此版本的 Django;如果你得到的是一个“No module named django”的错误提示,则表明你还未安装。
创建项目
如果你初次使用Django,那么你需要进行一些初始化设置。具体来说,你需要借助一些自动生成的代码来配置一个Django项目实例,这涵盖了必要的设置项集合,如数据库配置、Django本身的配置以及应用程序的配置。
接下来,请打开命令行界面,使用cd
命令切换到你想存放代码的目录,然后执行以下命令:
Windows:
django-admin startproject mysite
Linux/MacOS:
django-admin startproject mysite
这行代码的功能是在当前目录下创建一个名为 “mysite” 的目录。
请注意:
在命名你的项目时,务必避免使用Python或Django的内部保留字。例如,你不应使用像 “django”(这会与Django框架本身产生冲突)或 “test”(这可能会与Python的内置模块发生冲突)这样的名字。
关于代码存放位置:
如果你没有使用过现代框架,你可能习惯于将代码放置在网络服务器的文档根目录下(例如,/var/www)。然而,在Django中,这样的做法并不推荐。将任何Python代码直接放在网络服务器的文档根目录下都是不明智的,因为这可能允许他人通过网络访问你的代码,这对安全极为不利。
因此,建议将你的代码存放在文档根目录之外的其他位置,比如 /home/mycode。
现在,我们来看看 startproject
命令到底创建了哪些内容:
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
这些目录和文件的用途如下:
- 最外层的
mysite/
根目录主要作为项目的容器。根目录的名称对 Django 没有实际影响,你可以根据自己的喜好将其重命名。 manage.py
:这是一个命令行工具,用于以多种方式管理 Django 项目。- 内层的
mysite/
目录包含了你的项目内容,它是一个纯 Python 包。当你需要引用其内部任何内容时,这个目录的名称将作为 Python 包名使用(例如mysite.urls
)。 mysite/__init__.py
:这是一个空文件,用于告知 Python 该目录应被视为一个 Python 包。mysite/settings.py
:这是 Django 项目的配置文件,用于设置项目的各种参数和选项。mysite/urls.py
:这是 Django 项目的 URL 声明文件,相当于你网站的“目录”,用于定义 URL 模式及其对应的视图函数。mysite/asgi.py
:这个文件作为你的项目在 ASGI 兼容的 Web 服务器上的入口点。mysite/wsgi.py
:这个文件作为你的项目在 WSGI 兼容的 Web 服务器上的入口点。
用于开发的简易服务器
为了验证你的Django项目是否已成功创建,请确保你当前所在的目录是外层的mysite
目录。如果不在该目录,请切换到此目录,并执行以下命令:
Windows:
py manage.py runserver
Linux/MacOS:
python manage.py runserver
你应该会看到如下输出:
Performing system checks...
System check identified no issues (0 silenced).
You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.
May 07, 2024 - 15:50:53
Django version 4.0, 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)的问题,直至你准备好将应用部署到生产环境。此刻,需要特别提醒你的是:切勿将此开发服务器用于任何与生产环境相关的场景。它仅适用于开发目的。目前,服务器已正常运行。你可以通过浏览器访问http://127.0.0.1:8000/
来查看效果。你将看到一个包含发射火箭图案的“祝贺”页面,这表示你已经成功迈出了第一步!
更换端口
默认情况下,runserver
命令会将服务器设置为监听本机内部 IP 的 8000 端口。如果你希望更换服务器的监听端口,可以使用命令行参数来实现。例如,下面的命令将会使服务器监听 8080 端口:
Windows:
py manage.py runserver 8080
Linux/MacOS:
python manage.py runserver 8080
如果你想要修改服务器监听的IP地址,请在端口之前输入新的IP地址。例如,如果你想要让服务器监听所有公开的IP地址(这在运行Vagrant或想要向网络上的其他电脑展示你的成果时非常有用),你可以使用以下设置:
Windows:
py manage.py runserver 0.0.0.0:8000
Linux/MacOS:
python manage.py runserver 0.0.0.0:8000
自动重新加载的服务器 runserver
用于开发的服务器在需要时会对每次访问请求重新载入Python代码,这意味着你无需频繁重新启动服务器即可使修改的代码生效。然而,需要注意的是,某些操作如添加新文件等并不会触发自动重新加载机制,此时你需要手动重启服务器。
创建投票应用
现在,你的开发环境已经针对这个“项目”完成了配置,你可以开始着手工作了。
在Django框架中,每个应用都被构建为一个Python包,并且它们都遵循统一的约定。Django提供了一个便捷的工具,用于自动生成应用的基础目录结构,从而让你能够专注于编写代码,而不必花费时间手动创建目录。
项目和应用有什么区别?
应用是一种专门用于执行特定任务的网络程序,比如博客系统、公共记录数据库或小型投票程序等。而项目则是由一系列配置和应用组成的集合,用于构建完整的网站。一个项目中可以包含多个应用,而一个应用也可以被多个项目所使用。
你的应用可以存放在Python路径中定义的任何位置。在本教程中,我们将在与manage.py文件同一级的目录下创建投票应用,这样它就可以作为顶级模块被导入,而不是作为mysite的子模块。
请确保你现在正位于包含manage.py文件的目录下,然后运行以下命令来创建一个新的应用:
Windows:
py manage.py startapp polls
Linux/MacOS:
python manage.py startapp polls
这将会创建一个 polls目录,它的目录结构大致如下:
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
这个目录结构包括了投票应用的全部内容。
编写第一个视图
让我们开始编写第一个视图吧。打开 polls/views.py,把下面这些 Python 代码输入进去:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
这是 Django 中最简单的视图。如果想看见效果,我们需要将一个 URL 映射到它——这就是我们需要 URLconf 的原因了。
为了创建 URLconf,请在 polls 目录里新建一个 urls.py文件。你的应用目录现在看起来应该是这样:
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'),
]
下一步是要在根 URLconf 文件中指定我们创建的 polls.urls 模块。在 mysite/urls.py 文件的 urlpatterns列表里插入一个 include(), 如下:
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() 的理念是使其可以即插即用。因为投票应用有它自己的 URLconf( polls/urls.py ),他们能够被放在 “/polls/” , “/fun_polls/” ,“/content/polls/”,或者其他任何路径下,这个应用都能够正常工作。
何时使用 include()
当包括其它 URL 模式时你应该总是使用 include(), admin.site.urls是唯一例外。
你现在把 index视图添加进了 URLconf。通过以下命令验证是否正常工作:
Windows:
py manage.py runserver
Linux/MacOS:
python manage.py runserver
用你的浏览器访问 http://localhost:8000/polls/,你应该能够看见 “Hello, world. You’re at the polls index.” ,这是你在 index 视图中定义的。
没有找到页面?
如果你在这里得到了一个错误页面,检查一下你是不是正访问着http://localhost:8000/polls/ 而不应该是 http://localhost:8000/。
函数 path() 具有四个参数,两个必须参数:route 和 view,两个可选参数:kwargs 和 name。现在,是时候来研究这些参数的含义了。
path() 参数: route
route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。
这些准则不会匹配 GET 和 POST 参数或域名。例如,URLconf 在处理请求 https://www.example.com/myapp/ 时,它会尝试匹配 myapp/ 。处理请求 https://www.example.com/myapp/?page=3 时,也只会尝试匹配 myapp/。
path() 参数: view
当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。稍后,我们会给出一个例子。
path() 参数: kwargs
任意个关键字参数可以作为一个字典传递给目标视图函数。本教程中不会使用这一特性。
path() 参数: name
为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式。
模型和管理站点
数据库配置
现在,打开 mysite/settings.py 。这是个包含了 Django 项目设置的 Python 模块。
通常,这个配置文件使用 SQLite 作为默认数据库。如果你不熟悉数据库,或者只是想尝试下 Django,这是最简单的选择。Python 内置 SQLite,所以你无需安装额外东西来使用它。当你开始一个真正的项目时,你可能更倾向使用一个更具扩展性的数据库,例如 PostgreSQL,避免中途切换数据库这个令人头疼的问题。
如果你想使用其他数据库,你需要安装合适的 database bindings ,然后改变设置文件中 DATABASES 'default’ 项目中的一些键值:
- ENGINE -- 可选值有 ’django.db.backends.sqlite3’,’django.db.backends.postgresql’,’django.db.backends.mysql’,或 ’django.db.backends.oracle’。
- NAME -- 数据库的名称。如果你使用 SQLite,数据库将是你电脑上的一个文件,在这种情况下,NAME应该是此文件完整的绝对路径,包括文件名。默认值 BASE_DIR / 'db.sqlite3’ 将把数据库文件储存在项目的根目录。
如果你不使用 SQLite,则必须添加一些额外设置,比如 USER 、 PASSWORD 、 HOST 等等。
编辑 mysite/settings.py 文件前,先设置 TIME_ZONE为你自己时区。
此外,关注一下文件头部的 INSTALLED_APPS设置项。这里包括了会在你项目中启用的所有 Django 应用。应用能在多个项目中使用,你也可以打包并且发布应用,让别人使用它们。
通常, INSTALLED_APPS默认包括了以下 Django 的自带应用:
- django.contrib.admin – 管理员站点, 你很快就会使用它。
- django.contrib.auth – 认证授权系统。
- django.contrib.contenttypes – 内容类型框架。
- django.contrib.sessions – 会话框架。
- django.contrib.messages – 消息框架。
- django.contrib.staticfiles – 管理静态文件的框架。
这些应用被默认启用是为了给常规项目提供方便。
默认开启的某些应用需要至少一个数据表,所以,在使用他们之前需要在数据库中创建一些表。请执行以下命令:
Windows:
py manage.py migrate
Linux/MacOS:
python manage.py migrate
这个 migrate命令查看 INSTALLED_APPS 配置,并根据 mysite/settings.py 文件中的数据库配置和随应用提供的数据库迁移文件(我们将在后面介绍这些),创建任何必要的数据库表。你会看到它应用的每一个迁移都有一个消息。如果你有兴趣,运行你的数据库的命令行客户端,输入 \dt (PostgreSQL), SHOW TABLES;(MariaDB,MySQL), .tables (SQLite)或 SELECT TABLE_NAME FROM USER_TABLES; (Oracle)来显示 Django 创建的表。
创建模型
在 Django 里写一个数据库驱动的 Web 应用的第一步是定义模型 - 也就是数据库结构设计和附加的其它元数据。
在这个投票应用中,需要创建两个模型:问题 Question 和选项 Choice。Question 模型包括问题描述和发布时间。Choice 模型有两个字段,选项描述和当前得票数。每个选项属于一个问题。
这些概念可以通过一个 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 定义一个人类可读的名字。这个功能在很多 Django 内部组成部分中都被使用了,而且作为文档的一部分。如果某个字段没有提供此名称,Django 将会使用对机器友好的名称,也就是变量名。在上面的例子中,我们只为 Question.pub_date 定义了对人类友好的名字。对于模型内的其它字段,它们的机器友好名也会被作为人类友好名使用。
定义某些 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',
]
现在你的 Django 项目会包含 polls 应用。接着运行下面的命令:
Windows:
py manage.py makemigrations polls
Linux/MacOS:
python manage.py makemigrations polls
你将会看到类似于下面这样的输出:
Migrations for 'polls':
polls/migrations/0001_initial.py
- Create model Question
- Create model Choice
通过运行 makemigrations 命令,Django 会检测你对模型文件的修改(在这种情况下,你已经取得了新的),并且把修改的部分储存为一次 迁移。
迁移是 Django 对于模型定义(也就是你的数据库结构)的变化的储存形式 - 它们其实也只是一些你磁盘上的文件。如果你想的话,你可以阅读一下你模型的迁移数据,它被储存在 polls/migrations/0001_initial.py 里。别担心,你不需要每次都阅读迁移文件,但是它们被设计成人类可读的形式,这是为了便于你手动调整 Django 的修改方式。
Django 有一个自动执行数据库迁移并同步管理你的数据库结构的命令 - 这个命令是 migrate,我们马上就会接触它 - 但是首先,让我们看看迁移命令会执行哪些 SQL 语句。sqlmigrate 命令接收一个迁移的名称,然后返回对应的 SQL:
Windows:
py manage.py sqlmigrate polls 0001
Linux/MacOS:
python manage.py sqlmigrate polls 0001
你将会看到类似下面这样的输出(我把输出重组成了人类可读的格式):
BEGIN;
--
-- 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
);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
"id" serial NOT NULL PRIMARY KEY,
"choice_text" varchar(200) NOT NULL,
"votes" integer NOT NULL,
"question_id" integer NOT NULL
);
ALTER TABLE "polls_choice"
ADD CONSTRAINT "polls_choice_question_id_c5b4b260_fk_polls_question_id"
FOREIGN KEY ("question_id")
REFERENCES "polls_question" ("id")
DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;
请注意以下几点:
- 输出的内容和你使用的数据库有关,上面的输出示例使用的是 PostgreSQL。
- 数据库的表名是由应用名(polls)和模型名的小写形式( question 和 choice)连接而来。(如果需要,你可以自定义此行为。)主键(IDs)会被自动创建。(当然,你也可以自定义。)
- 默认的,Django 会在外键字段名后追加字符串 "_id" 。(同样,这也可以自定义。)
- 外键关系由 FOREIGN KEY 生成。你不用关心 DEFERRABLE部分,它只是告诉 PostgreSQL,请在事务全都执行完之后再创建外键关系。
- 生成的 SQL 语句是为你所用的数据库定制的,所以那些和数据库有关的字段类型,比如 auto_increment(MySQL)、 serial (PostgreSQL)和 integer primary key autoincrement (SQLite),Django 会帮你自动处理。那些和引号相关的事情 - 例如,是使用单引号还是双引号 - 也一样会被自动处理。
- 这个 sqlmigrate命令并没有真正在你的数据库中的执行迁移 - 相反,它只是把命令输出到屏幕上,让你看看 Django 认为需要执行哪些 SQL 语句。这在你想看看 Django 到底准备做什么,或者当你是数据库管理员,需要写脚本来批量处理数据库时会很有用。
如果你感兴趣,你也可以试试运行 python manage.py check ;这个命令帮助你检查项目中的问题,并且在检查过程中不会对数据库进行任何操作。
现在,再次运行 migrate命令,在数据库里创建新定义的模型的数据表:
Windows:
py 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
Linux/MacOS:
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来跟踪执行过哪些迁移)并应用在数据库上 - 也就是将你对模型的更改同步到数据库结构上。
迁移是非常强大的功能,它能让你在开发过程中持续的改变数据库结构而不需要重新删除和创建表 - 它专注于使数据库平滑升级而不会丢失数据。我们会在后面的教程中更加深入的学习这部分内容,现在,你只需要记住,改变模型需要这三步:
- 编辑 models.py 文件,改变模型。
- 运行 python manage.py makemigrations 为模型的改变生成迁移文件。
- 运行 python manage.py migrate 来应用数据库迁移。
数据库迁移被分解成生成和应用两个命令是为了让你能够在代码控制系统上提交迁移数据并使其能在多个应用里使用;这不仅仅会让开发更加简单,也给别的开发者和生产环境中的使用带来方便。
初试API
现在让我们进入交互式 Python 命令行,尝试一下 Django 为你创建的各种 API。通过以下命令打开 Python 命令行:
Windows:
py manage.py shell
Linux/MacOS:
python manage.py shell
我们使用这个命令而不是简单的使用“python”是因为 manage.py 会设置 DJANGO_SETTINGS_MODULE环境变量,这个变量会让 Django 根据 mysite/settings.py 文件来设置 Python 包的导入路径。
当你成功进入命令行后,来试试 数据库 API 吧:
>>> from polls.models import Choice, Question # Import the model classes we just wrote.
# 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.
>>