Django 的 model 模型的一些操作技巧

20 篇文章 0 订阅
8 篇文章 0 订阅

Django 中的 model 扮演了什么样的一种角色呢? 有点像我们在 SQL 中初始化一个数据表的格式时需要做的工作,即定义这个数据表的名字,各个字段,各个字段的类型,还有各个字段的一些限制,以及表与表之间的关联。

这里我们主要是根据 Django 官网上给出的 models 的一些解释,对其中的一些需要注意的技巧进行讲解,可能会起到事半功倍的效果。

1. 字段

如果我们把每一个 model 看成一个表,那么 model 里面的每个定义的变量,相当于数据表的一个字段,那么我们就需要对字段的格式做一些定义,包括SQL里面常用的 int, float, char 类型等等,Django 在整个的接口里面给我们提供了相对于SQL更加丰富的数据格式。这边我们不做详细概述,只是对其中特别有意思的点:字段选项 拿出来解析

字段选项:

blank : 这个是个布尔值,将这个值放在null 选项前面的原因是因为这个字段对 null 取到一个过滤的作用。blank 的意思是能否让该字段接受空值,即没有值的情况,如果可以,再去检查如果对空值进行处理,即查看null 选项。

null : 这个是个布尔值,即只能选择 True 或者 False,如果是 True,那么当 blank 为 True,即该字段可以接受空值的时候,将空值设置为数据库中的 NULL,否则保留原来的空值进入数据库。

choices: 这个字段给该字段的值划定了一个范围,即该字段的可以选择的值的范围。如果写入该字段的值不在范围之内,则被拒绝。

default: 该字段的默认对象,即默认该字段有这个值,所以永远不会为空,除非认为设置为 NULL 值。

help_text:这个是额外的帮助文本,在将model当作表单输出到前端的HTML页面的时候,非常有用,主要是用来对该字段进行解释,而且可以进行国际化操作,即可以设置多种语言,针对不同的人群进行说明 model 的字段。

2. Meta 选项

使用内部 Meta类 来给模型赋予元数据,就像:

from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

这里的 Meta 元数据里面包含了排序的字段,model 对应的复数名字,即 Ox 的复数英文为 oxen

3. 模型方法

在模型中添加自定义方法会给你的对象提供自定义的“行级”操作能力。与之对应的是类 Manager 的方法意在提供“表级”的操作,模型方法应该在某个对象实例上生效。

这是一个将相关逻辑代码放在一个地方的技巧——模型。

比如,该模型有一些自定义方法:

3.1 模型内部自定义方法

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    @property
    def full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)

这里的 baby_boomer_status 就是模型的一个方法,而加了 property 修饰符的 full_name 函数就是模型的属性函数之一,可以直接通过 实例.full_name 来达到 实例.full_name() 的同样的效果。

3.2 执行自定义 SQL

最好用例子来解释。假设你有以下模型:

class Person(models.Model):
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    birth_date = models.DateField(...)

然后你可以像这样执行自定义 SQL:

>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
...     print(p)
John Smith
Jane Jones

这里的 model.manager.raw('自己的SQL语句') 的方法,能够实现一些在 Django 自己的 QuerySet 查询语句中实现起来比较困难的SQL语句。

或者如以下的方法

生成新的字段

>>> Person.objects.raw('''SELECT first AS first_name,
...                              last AS last_name,
...                              bd AS birth_date,
...                              pk AS id,
...                       FROM some_other_table''')

将参数传给 raw

>>> lname = 'Doe'
>>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])

直接执行自定义 SQL

from django.db import connection

def my_custom_sql(self):
    with connection.cursor() as cursor:
        cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
        cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
        row = cursor.fetchone()

    return row

要避免 SQL 注入,你绝对不能在 SQL 字符串中用引号包裹 %s 占位符。

注意,若要在查询中包含文本的百分号,你需要在传入参数使用两个百分号:

cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")
cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])

默认情况下,Python DB API 返回的结果不会包含字段名,这意味着你最终会收到一个 list,而不是一个 dict。要追求较少的运算和内存消耗,你可以以 dict 返回结果,通过使用如下的语法:

def dictfetchall(cursor):
    "Return all rows from a cursor as a dict"
    columns = [col[0] for col in cursor.description]
    return [
        dict(zip(columns, row))
        for row in cursor.fetchall()
    ]

另一个选项是使用来自 Python 标准库的 collections.namedtuple()。 namedtuple 是一个类元组对象,可以通过属性查找来访问其包含的字段;也能通过索引和迭代。结果都是不可变的,但能通过字段名或索引访问,这很实用:

from collections import namedtuple

def namedtuplefetchall(cursor):
    "Return all rows from a cursor as a namedtuple"
    desc = cursor.description
    nt_result = namedtuple('Result', [col[0] for col in desc])
    return [nt_result(*row) for row in cursor.fetchall()]

这有个例子,介绍了三者之间的不同:

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> cursor.fetchall()
((54360982, None), (54360880, None))

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> dictfetchall(cursor)
[{'parent_id': None, 'id': 54360982}, {'parent_id': None, 'id': 54360880}]

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> results = namedtuplefetchall(cursor)
>>> results
[Result(id=54360982, parent_id=None), Result(id=54360880, parent_id=None)]
>>> results[0].id
54360982
>>> results[0][0]
54360982

3.3 重写之前定义的模型方法

还有一个 模型方法 的集合,包含了一些你可能自定义的数据库行为。尤其是这两个你最有可能定制的方法 save()和 delete()

你可以随意地重写这些方法(或其它模型方法)来更改方法的行为。

一个典型的重写内置方法的场景是你想在保存对象时额外做些事。比如(查看文档 save() 了解其接受的参数):

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        do_something()
        super().save(*args, **kwargs)  # Call the "real" save() method.
        do_something_else()

4. 模型继承

Django 中的模型继承方法,和一般的 Python 模型继承类似,同时,也有自己的一些特征。

Django 有三种可用的继承风格。

  1. 常见情况下,你仅将父类用于子类公共信息的载体,因为你不会想在每个子类中把这些代码都敲一遍。这样的父类永远都不会单独使用,所以 抽象基类 是你需要的。
  2. 若你继承了一个模型(可能来源于其它应用),且想要每个模型都有对应的数据表,那么应该尝试 多表继承
  3. 最后,若你只想修改模型的 Python 级行为,而不是以任何形式修改模型字段, 代理模型 会是比较适合的选择。

4.1 抽象基类

抽象基类在你要将公共信息放入很多模型时会很有用。编写你的基类,并在 Meta 类中填入 abstract=True。该模型将不会创建任何数据表。当其用作其它模型类的基类时,它的字段会自动添加至子类。

一个例子:

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

Meta 继承

当一个抽象基类被建立,Django 将所有你在基类中申明的 Meta 内部类以属性的形式提供。若子类未定义自己的 Meta 类,它会继承父类的 Meta。当然,子类也可继承父类的 Meta,比如:

from django.db import models

class CommonInfo(models.Model):
    # ...
    class Meta:
        abstract = True
        ordering = ['name']

class Student(CommonInfo):
    # ...
    class Meta(CommonInfo.Meta):
        db_table = 'student_info'

由于Python继承的工作方式,如果子类从多个抽象基类继承,则默认情况下仅继承第一个列出的类的 Meta 选项。为了从多个抽象类中继承 Meta 选项,必须显式地声明 Meta 继承。例如:

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True
        ordering = ['name']

class Unmanaged(models.Model):
    class Meta:
        abstract = True
        managed = False

class Student(CommonInfo, Unmanaged):
    home_group = models.CharField(max_length=5)

    class Meta(CommonInfo.Meta, Unmanaged.Meta):
        pass

对 related_name 和 related_query_name 要格外小心

若你在 外键 或 多对多字段 使用了 related_name 或 related_query_name,你必须为该字段提供一个 独一无二的反向名字和查询名字。这在抽象基类中一般会引发问题,因为基类中的字段都被子类继承,且保持了同样的值(包括 related_name 和 related_query_name)。

为了解决此问题,当你在抽象基类中(也只能是在抽象基类中)使用 related_name 和 related_query_name,部分值需要包含 '%(app_label)s' 和 '%(class)s'。

  • '%(class)s' 用使用了该字段的子类的小写类名替换。
  • '%(app_label)s' 用小写的包含子类的应用名替换。每个安装的应用名必须是唯一的,应用内的每个模型类名也必须是唯一的。因此,替换后的名字也是唯一的。

举个例子,有个应用 common/models.py:

from django.db import models

class Base(models.Model):
    m2m = models.ManyToManyField(
        OtherModel,
        related_name="%(app_label)s_%(class)s_related",
        related_query_name="%(app_label)s_%(class)ss",
    )

    class Meta:
        abstract = True

class ChildA(Base):
    pass

class ChildB(Base):
    pass

附带另一个应用 rare/models.py:

from common.models import Base

class ChildB(Base):
    pass

common.ChildA.m2m 字段的反转名是 common_childa_related,反转查询名是 common_childas。 common.ChildB.m2m 字段的反转名是 common_childb_related, 反转查询名是 common_childbs。 rare.ChildB.m2m 字段的反转名是 rare_childb_related,反转查询名是 rare_childbs。这决定于你如何使用 '%(class)s' 和 '%(app_label)s' 构建关联名字和关联查询名。但是,若你忘了使用它们,Django 会在你执行系统检查(或运行 migrate)时抛出错误。

如果你未指定抽象基类中的 related_name 属性,默认的反转名会是子类名,后接 '_set' 。这名字看起来就像你在子类中定义的一样。比如,在上述代码中,若省略了 related_name 属性, ChildA 的 m2m 字段的反转名会是 childa_set , ChildB 的是 childb_set。如果你未指定抽象基类中的 related_name 属性,默认的反转名会是子类名,后接 '_set' 。这名字看起来就像你在子类中定义的一样。比如,在上述代码中,若省略了 related_name 属性, ChildA 的 m2m 字段的反转名会是 childa_set , ChildB 的是 childb_set。

5. 在一个包中管理模型

manage.py startapp 命令创建了一个应用结构,包含一个 models.py 文件。若你有很多 models.py 文件,用独立的文件管理它们会很实用。

为了达到此目的,创建一个 models 包。删除 models.py,创建一个 myapp/models 目录,包含一个 __init__.py 文件和存储模型的文件。你必须在 __init__.py 文件中导入这些模块。

比如,若你在 models 目录下有 organic.py 和 synthetic.py

from .organic import Person
from .synthetic import Robot

6. 在model作为其他 model 的 foreign key 的情况下更新主键

由于在这种情况下我们需要更改已经完成的外键关系,所以我们

1. 首先删除所有的数据,尤其是外键所在的model 中的全部数据

2. 删除APP下的所有的 migrations 文件夹数据
3.新建对应的SQL代码

python manage.py makemigrations

4. 同步数据库

python manage.py migrate APP_NAME --run-syncdb

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值