Django model 使用

环境信息

  • Python 3.10
  • Django 4.1

在 Project/App 的 models.py 文件中创建 model,当 model 定义完成,Django 会自动生产一个后台管理接口,允许认证用户添加、更改和删除对象,只需在管理站点上注册模型即可 [1]

创建 model

在 Project/App 的 models.py 文件中创建 model

models.py
from django.db import models

# 公司部门
class Department(models.Model):
name = models.CharField(max_length=24, unique=True, help_text="部门名称",verbose_name='名称')
shortName = models.CharField(max_length=8, unique=True, help_text="部门名称简称",verbose_name='简称')
manager = models.ForeignKey('Emplyee', on_delete=models.CASCADE, help_text="部门老大")
comment = models.CharField(max_length=256, blank=True, help_text="备注信息",verbose_name='备注')

def __str__(self):
return self.shortName

class Meta:
# 管理后台显示的 model 名,最后面没有 's'
verbose_name_plural = "部门"

# 管理后台显示的 model 名,最后面有 's',显示为 '部门s'
verbose_name = "部门"

# 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
db_table = "table_name"

Django 模型字段参考

对修改后的 model 进行 migrate,以使在数据库中变更更改。

python manage.py makemigrations
python manage.py migrate

model 注册到后台

在 Project/App 的 admin.py 文件中注册 model

admin.py
from django.contrib import admin

# 假如 app 为 servers,导入 models
from servers import models

admin.site.site_header = "My Admin"
admin.site.site_title = "My Admin"

@admin.register(models.Department)
class DepartmentAdmin(admin.ModelAdmin):
list_display = ('id','name','shortName','manager','comment')
list_display_links = ('id','name','shortName','manager','comment')

更多有关 admin 配置方法,请参考 Django admin 配置

model 基本操作

假设 model 为 Publish,新增数据 [2]

create 方式新增数据

Publish.objects.create("name"="人民出版社",city="北京")
Publish.objects.create(**{"name":"文艺出版社","city":"上海"})

save 方式新增数据

book1=Book(title="python",price="88",publish_id="1",publication_date="2017-06-18")
book1.save()

为了避免重复创建数据表中已存在的条目,Django 还提供了 get_or_create 方法。它会返回查询到的或新建的模型对象实例,还会返回这个对象实例是否是刚刚创建的。

obj, created = Article.objects.get_or_create(title="My first article", body="My first article body")

update_or_create 方式更新或者添加数据

update_or_create 是使用给定的 kwargs 更新对象的一种方便方法,必要时创建一个新对象。defaults 是用来更新对象的 (field, value) 对的字典。defaults 中的值可以是可调用对象。

返回 (object, created) 的元组,其中 object 是创建或更新的对象,created 是一个布尔值,指定是否创建了一个新对象。 [3]

obj, created = models.RawDomains.objects.update_or_create(id=id, domain=domain, defaults=d)

以上示例中,先使用 id=id, domain=domain 的条件查询(筛选)数据,如果筛选出 1 条数据,则对此数据使用 defaults 中定义的对象进行更新,如果没有筛选出数据,则创建数据。

存在 Foreignkey 的表新增数据

通过绑定对象的方式新增

#获取出版社对象
publish_obj=Publish.objects.get(id=4)

#将出版社的对象绑定到书籍的记录中
Book.objects.create(
title="python",
price=48.00,
publication_date="2017-07-12",
publish=publish_obj,
)

直接通过 Foreignkey 对应记录的 id 号新增数据

#直接把出版社的id号插入到书籍的记录中
Book.objects.create(
title="python",
price=48.00,
publish_id=2,
publication_date="2017-06-18",
)

ManyToManyField 的表新增数据

为一本书添加多个作者

author1=Author.objects.get(id=1)              # 获取id号为1的作者对象
author2=Author.objects.filter(name="a") # 获取名字为"tom"的作者对象
book1=Book.objects.get(id=2) # 获取id号为2的书籍对象
book1.authors.add(author1,author2) # 为书籍对象添加多个作者对象

也可以使用以下方法

book1.authors.add(*[author1,author2])             # 为书籍对象添加作者对象的列表
book1.authors.remove(*[author1,author2]) # 删除指定书籍的所有作者

为一个作者添加多本书

author_obj = Author.objects.filter(name="jerry")        # 获取名字为"jerry"的作者对象
book_obj=Book.objects.filter(id__gt=3) # 获取id大于3的书籍对象集合
author_obj.book_set.add(*book_obj) # 为作者对象添加书籍对象集合
author_obj.book_set.remove(*book_obj) # 删除指定作者对象所有的书籍

Book.objects.filter(id=1).delete()

使用 save 方法将所有属性重新设定一遍,效率较低

author1=Author.objects.get(id=3)           # 获取id为3的作者对象

author1.name="jobs" # 修改作者对象的名字

author1.save() # 把更改写入数据库

使用 update 方法直接设置对应的属性

Publish.objects.filter(id=2).update(name="北京出版社")

update()QuerySet 对象的一个方法,get 返回的是一个 model 对象,其没有 update 方法

查询数据使用 QuerySet API。 QuerySet 是惰性执行的,创建 Query Set 不会访问数据库,只有在访问具体查询结果的时候才会访问数据库。

查询方法

方法 方法说明 举例
filter(**kwargs) 包含了与所给筛选条件相匹配的对象,返回 QuerySet ,相当于 SQL 中的 WHERE
all() 查询所有结果 ,等同于 SQL 语句 SELECT * FROM
get(**kwargs) 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都是报错
values(*field) 只取指定列 ,返回 QuerySet,此列表由字典 {'列名': '列值'} 组成 models.AwsZoneInfo.objects.all().values('code')
exclude(**kwargs) 包含了与所给的筛选条件不匹配的对象
order by(*field) 对查询结果排序
reverse() 对查询结果反向排序
distinct() 从返回结果中剔除重复记录
values_list(*field) values() 非常相似,返回一个元组序列,values 返回一个字典序列
count() 返回数据库中匹配的记录的数量
first() 返回数据库中匹配的对象的第一个对象
last() 返回数据库中匹配的对象的最后一个对象
exists() 判断一个对象集合中是否包含指定对象,包含返回 True,不包含返回 False
exclude() 排除满足条件的对象
annotate() 使用聚合函数
dates() 根据日期获取查询集
datetimes() 根据时间获取查询集
none() 创建空的查询集
union() 并集
intersection() 交集
difference() 差集
select_related() 附带查询关联对象
prefetch_related() 预先查询
extra() 附加 SQL 查询
defer() 不加载指定字段
only() 只加载指定的字段
using() 选择数据库
select_for_update() 锁住选择的对象,直到事务结束。
raw() 接收一个原始的 SQL 查询

values(*field) 和 values_list(*field) 使用示例

values(*field) 获取 QuerySet 中指定列的值,会返回一个由字典 {'列名1': '列值1', '列名2': '列值2', ...} 组成的 QuerySet

>>> models.AwsZoneInfo.objects.all().values('code')
<QuerySet [{'code': 'af-south-1'}, {'code': 'ap-east-1'}, {'code': 'ap-northeast-1'}, {'code': 'ap-south-1'}, {'code': 'ap-southeast-3'}]>

models.AwsZoneInfo.objects.all().values('code','name')
<QuerySet [{'code': 'ap-east-1', 'name': 'Asia Pacific (Hong Kong)'}, {'code': 'us-east-1', 'name': '美国东部(弗吉尼亚北部)']>

values_list(*field) 获取 QuerySet 中指定列的值,会返回一个由元组 ('列值1', '列值2', ...) 组成的 QuerySet

>>> models.AwsZoneInfo.objects.all().values_list('code','name')
<QuerySet [('us-east-2', '美国东部(俄亥俄)'), ('ap-east-1', 'Asia Pacific (Hong Kong)'), ('us-east-1', '美国东部(弗吉尼亚北部)')]>

model 类型使用说明

OneToOneField

一对一表中,子表从母表中选出一条数据一一对应,母表中选出来一条就少一条,子表不可以再选择母表中已被选择的那条数据 [4]

常见用法

示例如下:

models.py
from django.db import models

class User(models.Model):
username = models.CharField(max_length=100)
# ...

class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField()
# ...

User 实例中使用 Profile 实例的属性

假如需要在 User 对象中,调用和 User 实例关联的 Profile 对象的属性,可以通过以下方法实现

user = User.objects.get(id=1)
bio = user.profile.bio

需要注意的是,如果 user 没有与之关联的 Profile 实例,那么 user 就没有 profile 属性,上述代码会报错:models.User.profile.RelatedObjectDoesNotExist: User has no profile

可以通过以下方式处理异常 [5]

user = User.objects.get(id=1)
try:
bio = user.profile.bio
except models.User.profile.RelatedObjectDoesNotExist:
bio = ""

父表插入记录后,子表自动插入相关联的记录

实例代码如下:

# 域名信息
class RawDomains(models.Model):
provider = models.ForeignKey('Provider', on_delete=models.CASCADE, help_text="账号供应商", verbose_name="账号供应商")
account = models.ForeignKey('AccountAuthInfo', on_delete=models.CASCADE, help_text="账号名", verbose_name='账号名')
domain = models.CharField(max_length=64, unique=True, blank=False, help_text="域名", verbose_name='域名')
expire = models.DateTimeField(help_text="域名过期时间", verbose_name='域名过期时间')

# 域名及项目关联信息
class DomainProjectInfo(models.Model):
domain = models.OneToOneField(RawDomains, on_delete=models.CASCADE, help_text="域名", verbose_name="域名")
project = models.ForeignKey('Project', on_delete=models.CASCADE, help_text="产品", verbose_name="产品", default=default_project)
type = models.ForeignKey('DomainType', on_delete=models.CASCADE, help_text="域名类型", verbose_name="域名类型", default=1)
status = models.ForeignKey('DomainStatus', on_delete=models.CASCADE, help_text="域名状态", verbose_name="域名状态", default=1)

为了在 Django 中实现 RawDomains 新增记录后,DomainProjectInfo 自动关联对应条目的需求,可以使用 Django 的信号机制。具体来说,可以监听 RawDomains 模型的 post_save 信号,每当有新的 RawDomains 记录被保存后,自动创建一个对应的 DomainProjectInfo 记录并建立关联。

以下是具体实现步骤:

  1. 定义信号接收器

    首先,在你的应用中创建一个新的文件 signals.py(如果还没有的话),在这个文件中定义一个函数来作为信号的接收器。这个函数将在 RawDomains 记录被成功创建或保存后被自动调用。

    from django.db.models.signals import post_save
    from django.dispatch import receiver
    from .models import RawDomains, DomainProjectInfo, Project

    @receiver(post_save, sender=RawDomains)
    def create_domain_project_info(sender, instance, created, **kwargs):
    if created: # 检查是不是新创建的记录
    # 这里你可以根据需要来设定默认值,比如默认的项目ID
    default_project_id = 234 # 假设已存在的默认项目ID
    DomainProjectInfo.objects.create(
    domain=instance,
    project_id=default_project_id,
    type_id=3, # 示例默认值,根据实际情况调整
    status_id=5,
    )

    这个信号接收器会在每次 RawDomains 模型的 post_save 事件发生时被调用。如果是新创建的 RawDomains 记录(created=True),它会自动创建一个对应的 DomainProjectInfo 实例,并设置一些默认值。

  2. 确保信号被正确连接

    为了确保你的信号接收器被正确地挂接,你需要在应用的配置类中导入这些信号。修改你应用的 apps.py 文件,确保 ready 方法如下所示:

    from django.apps import AppConfig

    class YourAppNameConfig(AppConfig):
    name = 'your_app_name' # 请确保这里使用你的实际应用名称
    verbose_name = '你的应用的可读名称'

    def ready(self):
    import your_app_name.signals # 替换your_app_name为你的应用的实际名称

完成以上步骤后,每当 RawDomains 模型添加新记录时,Django 会自动创建一个相关联的 DomainProjectInfo 记录。根据你的具体模型字段和逻辑需要调整示例代码中的默认值设置。

可选择的字段

要定义在给定选项中选择值的字段,可以通过在模型类中使用 choices 参数来配置可选字段。choices 参数需要传递一个元组的列表,其中每个元组表示一个可选择的选项。每个元组由 2 个值组成,第一个值是数据库中存储的值,第二个是在表单中显示的标签。

models.py
class Mymodel(models.Model):
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female')
)
name = models.CharField(max_length=100)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)

查询给定的 QuerySet 是否属于某个 model

在 Django 中,可以使用以下方法来检查一个 QuerySet 是否是特定 model 的实例

>>> queryset.model == models.Mymodel
True

如果对象 queryset 中的所有对象都是 Mymodel 的实例,返回 True。如果 queryset 中有任何一个对象不是 Mymodel 的实例,返回 False

字符串格式的 QuerySet 转换为 QuerySet 对象

假如有以下字符串,内容和 QuerySet 内容一致,只是类型为字符串

>>> queryset_string
'<QuerySet [<Domain: a11.com>, <Domain: a12.com>, <Domain: a13.com>, <Domain: a14.com>]>'

>>> type(queryset_string)
<class 'str'>

要将示例中的 queryset_string 类型从 str 转换为 QuerySet 对象,主要步骤如下

>>> import re
>>> from django.apps import apps

>>> model_name = "Domain"

# 获取 model 类,app_label="your_app_label" 换成自己的 Django 项目中的 app 名称
>>> model = apps.get_model(app_label="your_app_label", model_name=model_name)

# 将 queryset_string 转换为只包含主键的列表
>>> obj_str_list = queryset_string.replace('<QuerySet', '').replace('<DomainProjectInfo: ', '').replace('>', '').replace('[','').replace(']','').replace(' ','').split(',')

>>> obj_str_list
['a11.com', 'a12.com', 'a13.com', 'a14.com']

# 根据 obj_str_list 中的主键转换为 model 的对象组成的列表
>>> queryset_list = []
>>> for obj_str in obj_str_list:
... obj = model.objects.get(domain=obj_str)
... queryset_list.append(obj)

>>> queryset_list
[<DomainProjectInfo: a11.com>, <DomainProjectInfo: a12.com>, <DomainProjectInfo: 613.com>, <DomainProjectInfo: a14.com>]
>>>

>>> type(queryset_list)
<class 'list'>

# 将 list 类型的 queryset_list 转换为 QuerySet 对象
>>> queryset = model.objects.filter(pk__in=[obj.pk for obj in queryset_list])
>>> queryset
<QuerySet [<DomainProjectInfo: a11.com>, <DomainProjectInfo: a12.com>, <DomainProjectInfo: a13.com>, <DomainProjectInfo: a14.com>]>

>>> type(queryset)
<class 'django.db.models.query.QuerySet'>

手动执行 migrate

有时候使用 python manage.py migrate 执行数据库迁移会出错,可能需要重建数据库重新执行 migrate 操作,在不方便如此做的情况下,可以选择手动执行迁移操作。

  1. 使用以下命令,根据想要迁移的文件生成实际在数据库中执行的 sql 语句。命令中 0008_table_alter 为要迁移的文件名(不用带完整路径(myapp_name/migrations/) 和文件名后缀(.py)),否则会报错:CommandError: Cannot find a migration matching 'myapp_name/migrations/0008_table_alter.py' from app 'myapp_name'. Is it in INSTALLED_APPS?

    $ python manage.py sqlmigrate myapp_name 0008_table_alter
    --
    -- Create model Domain
    --
    CREATE TABLE `myapp_name_domain` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(10) NOT NULL UNIQUE);
    --
    -- Alter field owner on domainproject
    --
    --
    -- Add field usage to domainproject
    --
    ALTER TABLE `myapp_name_domainproject` ADD COLUMN `usage_id` bigint DEFAULT 1 NOT NULL , ADD CONSTRAINT `myapp_name_doma_usage_id_43803979_fk_domains_c` FOREIGN KEY (`usage_id`) REFERENCES `myapp_name_domainusage`(`id`);
    ALTER TABLE `myapp_name_domainproject` ALTER COLUMN `usage_id` DROP DEFAULT;
  2. 在数据库中执行打印出的 sql

常见错误

str returned non-string

后台添加对象失败,原因说明

remaining elements truncated

django.db.models.query.QuerySet 类型的对象,长度超过 20 时,20 个之后的内容会显示为 remaining elements truncated,如果值变为了字符串类型,最后一项默认变成了 remaining elements truncated。因此在 django.db.models.query.QuerySet 类型或类似类型的数据会转为 str 的场景下下,要将其处理成 list 类型

脚注