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

出于多种原因 ^ ,我希望在我的一些 Django 模型中使用 UUID 作为主键。如果我这样做了,我是否仍然可以使用外部应用程序,比如通过 ContentType 使用通用关系的“投票贡献”、“ django 投票”或“ django 标记”?

以“ django 投票”为例,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")

对于使用 UUID 的第三方应用程序来说,这种“整数-PK 假定”的问题是否是一种常见的情况?或者,也许,我误解了这种情况?

有没有一种方法可以在 Django 中使用 UUID 作为主键而不会引起太多麻烦?


^ 一些原因: 隐藏对象计数,防止 url“ id 爬行”,使用多个服务器创建非冲突对象,..。

78953 次浏览

UUID 主键不仅会导致泛型关系方面的问题,而且还会导致效率方面的问题: 与机器词相比,每个外键的存储和加入成本都要高得多。

但是,没有什么需要 UUID 作为主键: 只要使它成为 次要的键,通过使用 unique=True作为 UUID 字段来补充您的模型。正常使用隐式主键(系统内部) ,并使用 UUID 作为外部标识符。

正如在文档 中看到的,从 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)

You can also 看看这个答案 for more information.

我遇到了类似的情况,并发现在 姜戈官方文件object_id不一定是相关模型的 主键相同的类型。例如,如果希望泛型关系对于 整数域查菲尔德 id 都有效,只需将 object_id设置为 查菲尔德。因为整数可以强制转换成字符串,所以没问题。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)

这个问题可以改写为“有没有办法让 Django 对所有表中的所有数据库 ID 使用 UUID,而不是使用自动递增的整数?”.

Sure, I can do:

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

在我所有的桌子,但我不能找到一个方法这样做:

  1. 第三方模块
  2. Django 生成了许多表

所以,这似乎是一个缺失的 Django 功能。

UUID 作为 PK 的真正问题是与非数字标识符相关的磁盘碎片和插入降级。因为 PK 是一个聚集索引(除了 PostgreSQL 之外,几乎在每个 RDBMS 中都是如此) ,当它不自动递增时,数据库引擎将不得不在插入一行具有较低序数的 id 时使用物理驱动器,这种情况在 UUID 中经常发生。当您在数据库中获得大量数据时,仅仅插入一条新记录可能就需要花费数秒甚至数分钟。您的磁盘最终将变得支离破碎,需要定期进行磁盘碎片整理。这一切都太糟糕了。

为了解决这些问题,我最近提出了以下我认为值得分享的架构。

伪主密钥

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

工作原理:

  1. 在 DB 模型上创建一个自动递增的主键 pkid
  2. 添加一个唯一索引的 UUID id字段,以允许您通过 UUID ID 而不是数字主键进行搜索。
  3. 将 ForeignKey 指向 UUID (使用 to_field='id') ,以允许外键正确地表示伪 PK 而不是数字 ID。

Essentially, you will do the following:

首先,创建一个抽象的 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

Make sure to extend the base model instead of 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

this can be done by using a custom base abstract model,using the following steps.

First create a folder in your project call it basemodel then add a abstractmodelbase.py with the following below:

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)

因此,上述模型事件固有的所有领域的基础抽象模型。