出于多种原因^,我想在我的一些 Django 模型中使用 UUID 作为主键。如果我这样做,我是否仍然可以使用通过 ContentType 使用通用关系的外部应用程序,例如“contrib.comments”、“django-voting”或“django-tagging”?
以“django-voting”为例,Vote 模型如下所示:
class Vote(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = generic.GenericForeignKey('content_type', 'object_id')
vote = models.SmallIntegerField(choices=SCORES)
这个应用程序似乎假设正在投票的模型的主键是一个整数。
不过,内置的评论应用似乎能够处理非整数 PK:
class BaseCommentAbstractModel(models.Model):
content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s")
object_pk = models.TextField(_('object ID'))
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
对于第三方应用程序来说,这种“假定整数 PK”问题是否是一种常见情况,这会使使用 UUID 变得很痛苦?或者,可能,我误读了这种情况?
有没有办法在 Django 中使用 UUID 作为主键而不会造成太多麻烦?
As seen in the documentation,从 Django 1.8 开始,有一个内置的 UUID 字段。使用 UUID 与整数时的性能差异可以忽略不计。
import uuid
from django.db import models
class MyUUIDModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
您还可以check this answer了解更多信息。
UUID 主键不仅会导致泛型关系问题,还会导致效率问题:每个外键都将比机器字更昂贵——无论是存储还是连接。
但是,没有什么要求 UUID 是主键:只需将其设置为 辅助 键,通过使用带有 unique=True
的 uuid 字段补充您的模型。正常使用隐式主键(系统内部),并使用 UUID 作为外部标识符。
default
。
django_extensions.db.fields.UUIDField
时,Django-South 迁移不会有任何问题 - 他提到的字段内置了对 South 迁移的支持。
UUID 作为 PK 的真正问题是与非数字标识符相关的磁盘碎片和插入降级。因为 PK 是一个聚集索引(几乎在除 PostgreSQL 之外的所有 RDBMS 中),当它不是自动递增时,您的数据库引擎将不得不在插入具有较低序数的行时使用您的物理驱动器,这将一直发生使用 UUID。当您在数据库中获得大量数据时,插入一条新记录可能需要几秒钟甚至几分钟。而且您的磁盘最终会变得碎片化,需要定期进行磁盘碎片整理。这一切都非常糟糕。
为了解决这些问题,我最近提出了以下我认为值得分享的架构。
UUID 伪主键
此方法允许您利用 UUID 作为主键(使用唯一索引 UUID)的优势,同时保持自动递增的 PK 以解决碎片和插入性能退化问题的非数字 PK。
这个怎么运作:
在您的数据库模型上创建一个名为 pkid 的自动递增主键。添加唯一索引的 UUID id 字段以允许您按 UUID id 搜索,而不是数字主键。将 ForeignKey 指向 UUID(使用 to_field='id')以允许您的外键正确表示 Pseudo-PK 而不是数字 ID。
本质上,您将执行以下操作:
首先,创建一个抽象的 Django Base Model
class UUIDModel(models.Model):
pkid = models.BigAutoField(primary_key=True, editable=False)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
class Meta:
abstract = True
确保扩展基本模型而不是 models.Model
class Site(UUIDModel):
name = models.CharField(max_length=255)
还要确保您的 ForeignKeys 指向 UUID id
字段而不是自动递增的 pkid
字段:
class Page(UUIDModel):
site = models.ForeignKey(Site, to_field='id', on_delete=models.CASCADE)
如果您使用的是 Django Rest Framework (DRF),请确保还创建一个 Base ViewSet 类来设置默认搜索字段:
class UUIDModelViewSet(viewsets.ModelViewSet):
lookup_field = 'id'
并为您的 API 视图扩展它而不是基础 ModelViewSet:
class SiteViewSet(UUIDModelViewSet):
model = Site
class PageViewSet(UUIDModelViewSet):
model = Page
本文中有关原因和方法的更多说明:https://www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps
我遇到了类似的情况,在 official Django documentation 中发现,object_id
不必与相关模型的 primary_key 属于同一类型。例如,如果您希望通用关系对 IntegerField 和 CharField id 都有效,只需将 object_id
设置为 CharField .由于整数可以强制转换为字符串,所以没问题。 UUIDField 也是如此。
例子:
class Vote(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.CharField(max_length=50) # <<-- This line was modified
object = generic.GenericForeignKey('content_type', 'object_id')
vote = models.SmallIntegerField(choices=SCORES)
这可以通过使用自定义基础抽象模型来完成,使用以下步骤。
首先在您的项目中创建一个文件夹,将其命名为 basemodel,然后添加一个 abstractmodelbase.py,其中包含以下内容:
from django.db import models
import uuid
class BaseAbstractModel(models.Model):
"""
This model defines base models that implements common fields like:
created_at
updated_at
is_deleted
"""
id = models.UUIDField(primary_key=True, unique=True, default=uuid.uuid4, editable=False)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True, editable=False)
is_deleted = models.BooleanField(default=False)
def soft_delete(self):
"""soft delete a model instance"""
self.is_deleted=True
self.save()
class Meta:
abstract = True
ordering = ['-created_at']
第二:在每个应用程序的所有模型文件中执行此操作
from django.db import models
from basemodel import BaseAbstractModel
import uuid
# Create your models here.
class Incident(BaseAbstractModel):
""" Incident model """
place = models.CharField(max_length=50, blank=False, null=False)
personal_number = models.CharField(max_length=12, blank=False, null=False)
description = models.TextField(max_length=500, blank=False, null=False)
action = models.TextField(max_length=500, blank=True, null=True)
image = models.ImageField(upload_to='images/', blank=True, null=True)
incident_date = models.DateTimeField(blank=False, null=False)
所以上述模型事件包含了baseabstract模型中的所有领域。
这个问题可以改写为“有没有办法让 Django 为所有表中的所有数据库 id 使用 UUID 而不是自动递增的整数?”。
当然,我可以这样做:
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
在我所有的桌子上,但我找不到这样做的方法:
3rd 方模块 Django 生成 ManyToMany 表
因此,这似乎是缺少的 Django 功能。
不定期副业成功案例分享