用Docker挑战Django的新手工程师系列之二:创建模型
简而言之
一位素人基础工程师使用Docker来开发Django系列①:继续使用Docker创建Django。
接下来,在之前创建的Django应用中添加模型。
有一个名为Django Girls的网站上发布了一个关于Django的教程,所以这次我们将按照该教程中“Django模型”之后的章节创建一个博客示例,并且与Django一起玩耍。
(虽然这个网站非常易懂,老实说可能比看这篇文章更好,但我仍然希望通过公开我作为素人学习过程中的经验和所查阅的内容来促进同样水平的人们的理解。)
Django的设计策略
模型 – 模板 – 视图
Django似乎是根据MTV(模型-模板-视图)的思想进行设计的。虽然听说过MVC(模型-视图-控制器),但还是一个一个地确认一下。
模型
这个模型与数据库进行交互。
在DjangoGirls的博客示例中,提及了面向对象编程,然后列举了作为博客所需的以下数据。
Post
--------
title
text
author
created_date
published_date
定义这样的数据格式作为模型,并在请求到来时从数据库中获取所需数据并返回。这在MVC中与模型的作用几乎相同。(可能)
模板
在Django中,模板是指界面的部分。
HTML以及用于完善它的CSS和JavaScript是对应的。
在MVC中,相当于View的角色。有点复杂。
在MVC中,Controller起着连接Model和View的桥梁作用,类似地,在MTV中,模板(Template)不会直接从Model获取数据,而是通过View来处理数据。
观看
虽然模板中稍作涉及,但在视图中需要描述如何展示使用模型获取的信息。然后,对其进行功能化并与模板进行关联。
简而言之,角色如下:
1. 对模型发出数据获取指令
2. 确定要返回给用户请求的模板
3. 将从模型获取的数据函数化并传递给模板
当用户访问使用Django创建的网站时,首先根据urls.py的描述确定要使用哪个视图函数来处理,所选的视图函数使用模型获取数据,然后将数据传递给选择的模板以显示网页。
以下是我的一种观点,关于你提到的大致的想法是这样的:
U(网址)⇒ urls.py(网址配置文件)⇒ view.py(视图文件)⇔ Model(模型层)⇔ DB(数据库)
S(静态文件) ⇓
E(错误处理) ⇓
R(模板文件)⇐ Templates(模板层)
在继续建设之前,要轻轻地意识到这三个角色。
建立
①创建应用程序的模板
运行 manage.py startapp
docker-compose run web python manage.py startapp blog
利用之前创建的docker-compose.yaml来在本地环境中创建应用程序的模板。上次使用runserver来启动web服务器,但如果使用startapp,则会创建必要的文件组以创建模型。
执行此命令后,目录结构应该如下所示,包括上次执行的内容。
.
∟ blog
∟ admin.py
∟ apps.py
∟ __init__.py
∟ migrations
∟ models.py
∟ tests.py
∟ views.py
∟ composeexample
∟ __init__.py
∟ __pycache__
∟ settings.py
∟ urls.py
∟ wsgi.py
∟ docker-compose.yaml
∟ Dockerfile
∟ manage.py
∟ migrations
∟ requirements.txt
如同上一次一样,在Linux机器上运行时,文件夹权限将变为root,因此执行以下操作。
sudo chown -R $USER:$USER .
使用settings.py进行关联
设置.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
]
将本次创建的应用程序添加到INSTALLED_APPS中。
创建模型
models.py 模型.py
from django.conf import settings
from django.db import models
from django.utils import timezone
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
创建模型定义。在由startapp创建的blog文件夹中,有一个models.py文件,对其进行编辑。一开始只有第二行的from django.db import models,其余的是借用了Django Girls的示例。
簡明明確的解釋
from django.conf import settings
使用的模块来引用项目/设置.py文件中的变量。
from django.db import models
提供了关于模型的各种方法的模块,例如外键和数据类型。
from django.utils import timezone
涉及时区的模块。它与许多其他语言提供的实用程序模組基本相同。此次使用它是为了获取发布时间。
时区设置在settings.py文件中进行,当前未进行任何设置时应该是UTC。
class Post(models.Model):
以下是关于定义名为Post的模型的部分。
要定义模型,需要继承自`django.db.models.Model`类。
此外,还可以创建子类。例如,如果创建日期(create_date)可能在其他模型中也会被使用,可以预先创建一个名为Timestamp的类来定义create_date数据,并通过继承`Post(Timestamp)`的形式来使用。
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
ForeignKey()是用于定义外键模型的。在这个例子中,我们使用settings模块来获取MODEL定义,以指定django默认拥有的User模型作为外键。但是,如果要使用自己创建的模型作为外键,则需要指定模型名称。on_delete选项在django2.0之后是必需的,用于指定在删除外键对象时的行为。CASCADE是指删除外键对象时同时删除所有关联对象。
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
指定构成博客的每个数据类型和约束。created_date用于存储对象生成的时间,即文章的撰写时间。相反,published_date目前为空,它将在稍后的publish方法中赋值。
def publish(self):
self.published_date = timezone.now()
self.save()
发布博客文章的方法。将当前时间赋值给发布时间published_date,并调用save()方法。由于我没有编写save()方法等,所以当然是在继承的models.Model内的方法中,如果表中存在主键,则进行更新(UPDATE),如果不存在,则进行创建(CREATE)。
def __str__(self):
return self.title
魔法方法(关于魔法方法,请自行查询。)被用于在实例化时返回博客文章的标题。
数据库的迁移
据说,在Django中,使用models.py文件中的内容连接数据库并创建表被称为迁移。
当执行manage.py makemigrations命令时,会创建用于迁移的文件,然后通过执行manage.py migrate命令来实际在数据库中创建表。
我立刻通过docker-compose exec进入web服务器并执行命令,然后进入db服务器检查表是否已创建。(这些操作将在之后的docker-compose命令中追加,以便在执行docker-compose up时执行迁移,但现在我只是进入容器执行命令进行确认)
docker-compose exec web bash
(在以下的web容器中)
manage.py makemigrations blog
在应用程序目录下的migrations文件夹中会创建连续编号的迁移文件,第一个文件是blog/migrations/0001_initial.py,其内容是创建了一个名为Post的模型。据说之后的文件会添加变化部分。
manaeg.py migrate blog
应用contenttypes.0001_initial…完成
应用auth.0001_initial…完成
应用blog.0001_initial…完成
如果以上的输出出现了,迁移就完成了。退出并进入数据库服务器进行确认。
docker-compose exec db bash
(在以下的数据库容器内)
psql -U postgres
\d
List of relations
Schema | Name | Type | Owner
--------+-----------------------------------+----------+----------
public | auth_group | table | postgres
public | auth_group_id_seq | sequence | postgres
public | auth_group_permissions | table | postgres
public | auth_group_permissions_id_seq | sequence | postgres
public | auth_permission | table | postgres
public | auth_permission_id_seq | sequence | postgres
public | auth_user | table | postgres
public | auth_user_groups | table | postgres
public | auth_user_groups_id_seq | sequence | postgres
public | auth_user_id_seq | sequence | postgres
public | auth_user_user_permissions | table | postgres
public | auth_user_user_permissions_id_seq | sequence | postgres
public | blog_post | table | postgres
public | blog_post_id_seq | sequence | postgres
public | django_content_type | table | postgres
public | django_content_type_id_seq | sequence | postgres
public | django_migrations | table | postgres
public | django_migrations_id_seq | sequence | postgres
桌子有很多。
auth什么的和django什么的可能是默认创建的(大概)。
因为我在models.py中写的是blog_post,所以要确认一下blog_post表的定义。
\d blog_post
Table "public.blog_post"
Column | Type | Collation | Nullable | Default
----------------+--------------------------+-----------+----------+---------------------------------------
id | integer | | not null | nextval('blog_post_id_seq'::regclass)
title | character varying(200) | | not null |
text | text | | not null |
created_date | timestamp with time zone | | not null |
published_date | timestamp with time zone | | |
author_id | integer | | not null |
Indexes:
"blog_post_pkey" PRIMARY KEY, btree (id)
"blog_post_author_id_dd7a8485" btree (author_id)
Foreign-key constraints:
"blog_post_author_id_dd7a8485_fk_auth_user_id" FOREIGN KEY (author_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
在Django定义模型时,默认情况下,ID是一个连续编号,作为主键。其他字段则按照在models.py中定义的方式进行。这样,模型的准备工作就完成了。
终于迈出了第二步的感觉,但不要气馁,坚持下去吧。。
故事将继续。