Por varias razones ^, me gustaría usar un UUID como clave principal en algunos de mis modelos de Django. Si lo hago, ¿podré seguir usando aplicaciones externas como "contrib.comments", "django-vote" o "django-tagging" que usan relaciones genéricas a través de ContentType?
Usando "django-vote" como ejemplo, el modelo Vote se ve así:
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)
Esta aplicación parece asumir que la clave principal del modelo que se vota es un número entero.
Sin embargo, la aplicación de comentarios incorporada parece ser capaz de manejar PK que no son enteros:
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")
¿Es este problema de "PK entero asumido" una situación común para aplicaciones de terceros que haría que el uso de UUID sea un problema? O, posiblemente, ¿estoy malinterpretando esta situación?
¿Hay alguna forma de usar UUID como claves primarias en Django sin causar demasiados problemas?
^ Algunas de las razones: ocultar recuentos de objetos, evitar el "rastreo de id" de la URL, utilizar varios servidores para crear objetos no conflictivos, ...
default
.django_extensions.db.fields.UUIDField
como lo menciona mitchf, no tendrá problemas con las migraciones Django-South; el campo mencionado por él tiene soporte incorporado para las migraciones South.Como se ve en la documentación , desde Django 1.8 hay un campo UUID integrado. Las diferencias de rendimiento cuando se usa un UUID frente a un número entero son insignificantes.
import uuid from django.db import models class MyUUIDModel(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
También puede consultar esta respuesta para obtener más información.
fuente
Me encontré con una situación similar y descubrí en la documentación oficial de Django , que
object_id
no tiene que ser del mismo tipo que la clave primaria del modelo relacionado. Por ejemplo, si desea que su relación genérica sea válida para los identificadores de IntegerField y CharField , simplemente configúreloobject_id
para que sea CharField . Dado que los números enteros pueden coaccionarse en cadenas, estará bien. Lo mismo ocurre con UUIDField .Ejemplo:
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)
fuente
El problema real con UUID como PK es la fragmentación del disco y la degradación de inserción asociada con identificadores no numéricos. Debido a que el PK es un índice agrupado, cuando no se incrementa automáticamente, su motor de base de datos tendrá que recurrir a su unidad física al insertar una fila con un ID de menor ordinalidad, lo que sucederá todo el tiempo con UUID. Cuando obtiene muchos datos en su base de datos, puede llevar muchos segundos o incluso minutos insertar un nuevo registro. Y su disco eventualmente se fragmentará, lo que requerirá una desfragmentación periódica del disco. Todo esto es realmente malo.
Para solucionar estos problemas, recientemente se me ocurrió la siguiente arquitectura que pensé que valdría la pena compartir.
La clave pseudoprimaria de UUID
Este método le permite aprovechar los beneficios de un UUID como clave principal (utilizando un UUID de índice único), al tiempo que mantiene un PK autoincrementado para abordar la fragmentación e insertar problemas de degradación del rendimiento de tener un PK no numérico.
Cómo funciona:
pkid
en sus modelos de base de datos.id
campo UUID indexado único para permitirle buscar por un ID de UUID, en lugar de una clave primaria numérica.to_field='id'
) para permitir que sus claves externas representen correctamente el Pseudo-PK en lugar del ID numérico.Básicamente, harás lo siguiente:
Primero, cree un modelo base de Django abstracto
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
Asegúrese de extender el modelo base en lugar de los modelos.
class Site(UUIDModel): name = models.CharField(max_length=255)
También asegúrese de que sus ForeignKeys apunten al
id
campo UUID en lugar delpkid
campo incrementado automáticamente :class Page(UUIDModel): site = models.ForeignKey(Site, to_field='id', on_delete=models.CASCADE)
Si está utilizando Django Rest Framework (DRF), asegúrese de crear también una clase Base ViewSet para establecer el campo de búsqueda predeterminado:
class UUIDModelViewSet(viewsets.ModelViewSet): lookup_field = 'id'
Y amplíe eso en lugar del ModelViewSet base para sus vistas de API:
class SiteViewSet(UUIDModelViewSet): model = Site class PageViewSet(UUIDModelViewSet): model = Page
Más notas sobre el por qué y el cómo en este artículo: https://www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps
fuente
esto se puede hacer usando un modelo abstracto base personalizado, siguiendo los siguientes pasos.
Primero cree una carpeta en su proyecto, llámelo modelo base y luego agregue un modelo abstracto base.py con lo siguiente a continuación:
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']
segundo: en todo su archivo de modelo para cada aplicación, haga esto
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)
Entonces, el incidente del modelo anterior es inherente a todo el campo en el modelo baseabstract.
fuente
La pregunta se puede reformular como "¿hay alguna manera de hacer que Django use un UUID para todos los ID de base de datos en todas las tablas en lugar de un entero autoincrementado?".
Claro que puedo hacer:
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
en todas mis tablas, pero no puedo encontrar una manera de hacer esto para:
Entonces, esto parece ser una característica faltante de Django.
fuente