02_自定义字段与模型抽象类
思考
上一篇《开发环境与项目初始化》我们初始化了项目并简单运行了 django,同样在本文开始之前请大家先思考下面几个问题。
怎么实现一个数据库可为空且不空时唯一的 slug 自定义字段?
分类模型和标签模型所需要的字段几乎相同,能否用一个 model 类来实现?
本文目标
自定义一个 Django 模型字段
学会用 DRY 原则去写 model 代码
涉及知识点
自定义 django 模型字段
django 模型抽象类
Step1、自定义一个 slug 字段
实现一个在调用 model save() 方法时可空且在不为空的时候唯一的 slug 自定义字段
首先在 docspace 这个 app 目录下新建 mixins 包,并新建 docspace/mixins/fields.py 文件,写入下面代码:
# `docspace/mixins/fields.py`
from django.db import models
from django.forms import SlugField
class NullableSlugFormField(SlugField):
def to_python(self, value):
if value in self.empty_values:
return None
return super().to_python(value)
class NullableSlugFieldMixin(object):
"""
使用方法:
models.NullableSlugField(unique=True, null=True, blank=True)
"""
_formfield_class = NullableSlugFormField
def get_prep_value(self, value):
return super().get_prep_value(value) or None
def formfield(self, **kwargs):
defaults = {}
if self._formfield_class:
defaults['form_class'] = self._formfield_class
defaults.update(kwargs)
return super().formfield(**defaults)
class NullableSlugField(NullableSlugFieldMixin, models.SlugField):
pass
这样,便自定义了满足我们需求的 NullableSlguField 字段
to_python 方法将值转换为正确的 Python 对象,并且在 clean() 中也被调用。
get_prep_value 方法将值转换为数据库查询参数的格式数据。
formfield 方法重写了模型字段指定表单字段默认的 form_class
Step2、可重用的抽象 model 类
这一块主要是将一些可重用的 model 类抽象起来,然后复用到其他地方,DRY 设计原则来书写 django 模型代码。
比如创建时间、更新时间、父级Parent 等模型类
# `docspace/models.py`
class Created(models.Model):
created = models.DateTimeField(
editable=True,
verbose_name=_("Created datetime"),
default=datetime.now,
)
class Meta:
abstract = True
class Updated(models.Model):
updated = models.DateTimeField(
auto_now=True,
verbose_name=_("Updated datetime"),
)
class Meta:
abstract = True
class Parent(models.Model):
parent = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
blank=True, null=True,
verbose_name=_("Parent ID"),
related_name="%(app_label)s_%(class)s_parent",
)
class Meta:
abstract = True
abstract: True or False,模型类是否为抽象基类,这个类不会直接生成数据库表
Step3、将分类和标签类合并写的同一个模型
在 docspace/models.py 下添加写入如下代码:
class Taxonomy(Created, Update, Parent, models.Model):
class MarkItems(models.TextChoices):
TAG = "tag", _("Tag")
CATEGORY = "category", _("Category")
LINK = "link", _("Link")
FILE = "file", _("File")
PHOTO = "photo", _("Photo")
AUDIO = "audio", _("Audio")
VIDEO = "video", _("Video")
SPECIAL = "special", _("Special")
TUTORIAL = "tutorial", _("Tutorial")
COURSE = "course", _("Course")
mark = models.SlugField(
max_length=12,
choices=MarkItems.choices,
default=MarkItems.TAG,
verbose_name=_("Mark"),
)
slug = NullableSlugField(
max_length=255,
blank=True, null=True,
unique=True, verbose_name=_("Slug"),
)
name = models.CharField(
max_length=64, unique=True,
verbose_name=_("Name")
)
cover = models.ImageField(
null=True, blank=True,
upload_to=upload_to,
verbose_name=_("Cover"),
)
description = models.TextField(
null=True, blank=True, verbose_name=_("Description")
)
related_count = models.PositiveIntegerField(
default=0, null=True, blank=True, verbose_name=_("Related count"),
)
def __str__(self):
return self.name
class Meta:
verbose_name = _("Taxonomy")
verbose_name_plural = _("Taxonomys")
Taxonomy(Created, Update, Parent, models.Model) 会继承前面三个抽象类的字段
slug = NullableSlugField 是我们之前自定义的字段,它允许我们存空值,同时,在有值的时候,值不能重复
Step4、迁移与测试
...\> python .\manage.py makemigrations
...\> python .\manage.py migrate
...\> python .\manage.py shell # django python 交互式命令行
测试结果如下:
(env) PS C:\Users\shoutian\docspace> python .\manage.py shell
Python 3.8.6 (tags/v3.8.6:db45529, Sep 23 2020, 15:52:53) [MSC v.1927 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from docspace.models import Taxonomy
>>> t1 = Taxonomy.objects.create(name='django')
>>> c1 = Taxonomy.objects.create(name='python', mark=Taxonomy.MarkItems.CATEGORY)
>>> c1
>>> c1.mark
>>> t1.mark
>>> t2 = Taxonomy.objects.create(name='docs', slug='docspace')
>>> t3 = Taxonomy.objects.create(name='docspace', slug='docspace')
Traceback (most recent call last):
File "C:\Users\shoutian\docspace\env\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "C:\Users\shoutian\docspace\env\lib\site-packages\django\db\backends\sqlite3\base.py", line 423, in execute
return Database.Cursor.execute(self, query, params)
sqlite3.IntegrityError: UNIQUE constraint failed: docspace_taxonomy.slug
.
.
.
django.db.utils.IntegrityError: UNIQUE constraint failed: docspace_taxonomy.slug
>>>
t1,c1 分别用 mark 来区分标签和分类,并且 slug 字段都为空值
t2,t3 使用相同的非空 slug 值,t3 便不能继续创建,报 IntegrityError: UNIQUE constraint failed 异常错误
slug 字段一般都用于 URL 中,所以要唯一,在 wordpress 中通常指 Permalink
到此,博客的分类和标签的模型已经写完,下一篇文章将继续设计个人博客程序的模型(model) 《Django 个人博客教程-03:用户模型和博客链接》
阅读说明:
...\> 这个开头表示Windows powershell下执行的指令
...]$ 这个开头表示Linux bash下执行的指令
系列文章:
《Django 个人博客教程-00:开篇》
《Django 个人博客教程-01:开发环境与项目初始化》
《Django 个人博客教程-02:自定义字段与模型抽象类》当前文章
《Django 个人博客教程-03:用户模型和博客链接》下一篇
如果觉得文章对你有用,请分享给其他人或点击在看