在Django模型中使用UUID作为主键(泛型关系影响)

muk1a3rh  于 2023-02-14  发布在  Go
关注(0)|答案(6)|浏览(162)

出于多种原因^,我希望在我的一些Django模型中使用UUID作为主键。如果我这样做了,我还能使用外部应用程序吗,比如“contrib.comments”,“django-voting”或“django-tagging”,这些应用程序通过ContentType使用泛型关系?
以“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作为主键而不引起太多麻烦?
^部分原因:隐藏对象计数、防止URL“ID爬行”、使用多个服务器来创建不冲突的对象...

iszxjhcz

iszxjhcz1#

如文档所示,Django1.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获取更多信息。

m2xkgtsf

m2xkgtsf2#

UUID主键不仅会导致泛型关系的问题,而且通常会导致效率的问题:每个外键的存储和连接都比机器字昂贵得多。
但是,没有什么要求UUID是主键:只需将其作为一个 secondary 键,方法是在模型中添加一个带有unique=True的uuid字段,使用隐式主键(系统内部),并使用UUID作为外部标识符。

4nkexdtk

4nkexdtk3#

UUID作为PK的真实的问题是与非数字标识符相关的磁盘碎片和插入降级。(在除PostgreSQL之外的几乎所有RDBMS中),当它不是自动递增时,您的DB引擎将不得不在插入具有较低序号的ID的行时重新选择您的物理驱动器,这种情况在使用UUID时经常发生。当你的数据库中有很多数据时,插入一条新记录可能要花很多秒甚至几分钟。而且你的磁盘最终会变成碎片,需要定期的磁盘碎片整理。这真的很糟糕。
为了解决这些问题,我最近提出了以下架构,我认为值得与大家分享。

UUID伪主键

此方法允许您利用UUID作为主键(使用唯一索引UUID)的优势,同时维护自动递增的PK以解决使用非数字PK时的碎片和插入性能下降问题。

工作原理:

1.在DB模型上创建一个名为pkid的自动递增主键。
1.添加一个唯一索引的UUID id字段,以允许您按UUID id而不是数字主键进行搜索。
1.将外键指向UUID(使用to_field='id'),以允许外键正确表示伪PK而不是数字ID。
基本上,您将执行以下操作:
首先,创建一个抽象的Django基础模型

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

确保扩展基础模型而不是模型。模型

class Site(UUIDModel):
    name = models.CharField(max_length=255)

还要确保外键指向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

pobjuy32

pobjuy324#

我遇到过类似的情况,在Django的官方文档中发现,object_id不必与相关模型的primary_key为同一类型。例如,如果您希望泛型关系对IntegerFieldCharFieldid都有效,只要把你的object_id设置成一个CharField。2因为整数可以强制转换成字符串,所以就可以了。3同样的方法也适用于UIDField

    • 示例:**
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)
kcrjzv8t

kcrjzv8t5#

这可以通过使用定制基础抽象模型、使用以下步骤来完成。
首先在您的项目中创建一个文件夹,将其命名为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)

因此上述模型与基本抽象模型中的所有领域相关联。

o4tp2gmn

o4tp2gmn6#

这个问题可以改写为"有没有办法让Django对所有表中的所有数据库ID使用UUID,而不是自动递增的整数?"
当然,我能做到:

id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

在我所有的表中,但我找不到一种方法来执行此操作:
1.第三方模块

  1. Django生成的ManyToMany表
    所以,这似乎是Django缺少的一个特性。

相关问题