Django 个人博客教程-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 202015: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
<Taxonomy: python>
>>> c1.mark
<MarkItems.CATEGORY: 'category'>
>>> t1.mark
<MarkItems.TAG: 'tag'>
>>> 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 84in _execute
    return self.cursor.execute(sql, params)
  File "C:\Users\shoutian\docspace\env\lib\site-packages\django\db\backends\sqlite3\base.py", line 423in 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
>>>
  • t1c1 分别用 mark 来区分标签和分类,并且 slug 字段都为空值
  • t2t3 使用相同的非空 slug 值,t3 便不能继续创建,报 IntegrityError: UNIQUE constraint failed 异常错误
  • slug 字段一般都用于 URL 中,所以要唯一,在 wordpress 中通常指 Permalink

到此,博客的分类和标签的模型已经写完,下一篇文章将继续设计个人博客程序的模型《Django 个人博客教程-03:用户模型和博客链接》


阅读说明:

...\> 这个开头表示Windows powershell下执行的指令

...]$ 这个开头表示Linux bash下执行的指令


系列文章:

  1. Django 个人博客教程-00:开篇
  2. Django 个人博客教程-01:开发环境与项目初始化
  3. 《Django 个人博客教程-02:自定义字段与模型抽象类》当前文章
  4. 《Django 个人博客教程-03:用户模型和博客链接》下一篇

如果觉得文章对你有用,请分享给其他人或点击在看



相关阅读RelatedRead

Django 个人博客教程-03:用户模型和博客链接

Django利用xlrd将excel表格数据导入到model数据库中

django-idcops 部署线上生成环境

Django个人博客教程-1:开发环境之编译安装python3.7

Django个人博客教程-1:开发环境

Django个人博客教程:开篇

本博启用灰色调 2020年4月4日举行全国性哀悼活动

暂无评论添加评论