Source code for morango.models.fields.uuids

import hashlib
import uuid

from django.db import models

from morango.utils import _assert


def sha2_uuid(*args):
    return hashlib.sha256("::".join(args).encode("utf-8")).hexdigest()[:32]


[docs] class UUIDField(models.CharField): """ Adaptation of Django's UUIDField, but with 32-char hex representation as Python representation rather than a UUID instance. """ def __init__(self, *args, **kwargs): kwargs["max_length"] = 32 super(UUIDField, self).__init__(*args, **kwargs)
[docs] def prepare_value(self, value): if isinstance(value, uuid.UUID): return value.hex return value
[docs] def deconstruct(self): name, path, args, kwargs = super(UUIDField, self).deconstruct() del kwargs["max_length"] return name, path, args, kwargs
[docs] def get_internal_type(self): return "UUIDField"
[docs] def get_db_prep_value(self, value, connection, prepared=False): if value is None: return None if not isinstance(value, uuid.UUID): try: value = uuid.UUID(value) except AttributeError: raise TypeError(self.error_messages["invalid"] % {"value": value}) return value.hex
[docs] def from_db_value(self, value, expression, connection): return self.to_python(value)
[docs] def to_python(self, value): if isinstance(value, uuid.UUID): return value.hex return value
[docs] def get_default(self): """ Returns the default value for this field. """ if self.has_default(): if callable(self.default): default = self.default() if isinstance(default, uuid.UUID): return default.hex return default if isinstance(self.default, uuid.UUID): return self.default.hex return self.default return None
[docs] class UUIDModelMixin(models.Model): """ Mixin for Django models that makes the primary key "id" into a UUID, which is calculated as a function of jointly unique parameters on the model, to ensure consistency across instances. """ # a tuple of the names of model fields from which to calculate the UUID, or the string "RANDOM" for a random UUID uuid_input_fields = None # field to hold the model's UUID primary key id = UUIDField(max_length=32, primary_key=True, editable=False)
[docs] class Meta: abstract = True
[docs] def calculate_uuid(self): """Should return a 32-digit hex string for a UUID that is calculated as a function of a set of fields from the model.""" # raise an error if no inputs to the UUID calculation were specified if self.uuid_input_fields is None: raise NotImplementedError( """You must define either a 'uuid_input_fields' attribute (with a tuple of field names) or override the 'calculate_uuid' method, on models that inherit from UUIDModelMixin. If you want a fully random UUID, you can set 'uuid_input_fields' to the string 'RANDOM'.""" ) # if the UUID has been set to be random, return a random UUID if self.uuid_input_fields == "RANDOM": return uuid.uuid4().hex # if we got this far, uuid_input_fields should be a tuple _assert( isinstance(self.uuid_input_fields, tuple), "'uuid_input_fields' must either be a tuple or the string 'RANDOM'", ) # calculate the input to the UUID function hashable_input_vals = [] for field in self.uuid_input_fields: new_value = getattr(self, field) if new_value: hashable_input_vals.append(str(new_value)) hashable_input = ":".join(hashable_input_vals) # if all the values were falsey, just return a random UUID, to avoid collisions if not hashable_input: return uuid.uuid4().hex # compute the UUID as a function of the input values return sha2_uuid(hashable_input)
[docs] def save(self, *args, **kwargs): if not self.id: self.id = self.calculate_uuid() super(UUIDModelMixin, self).save(*args, **kwargs)