from __future__ import unicode_literals import re import uuid from django.db import models from django.contrib.postgres.fields import ArrayField, JSONField from django.core.validators import RegexValidator from ... import common class OCDIDField(models.CharField): def __init__(self, *args, **kwargs): self.ocd_type = kwargs.pop("ocd_type") if self.ocd_type != "jurisdiction": kwargs["default"] = lambda: "ocd-{}/{}".format(self.ocd_type, uuid.uuid4()) # len('ocd-') + len(ocd_type) + len('/') + len(uuid) # = 4 + len(ocd_type) + 1 + 36 # = len(ocd_type) + 41 kwargs["max_length"] = 41 + len(self.ocd_type) regex = ( "^ocd-" + self.ocd_type + "/[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$" ) else: kwargs["max_length"] = 300 regex = common.JURISDICTION_ID_REGEX kwargs["primary_key"] = True # get pattern property if it exists, otherwise just return the object (hopefully a string) msg = "ID must match " + getattr(regex, "pattern", regex) kwargs["validators"] = [RegexValidator(regex=regex, message=msg, flags=re.U)] super(OCDIDField, self).__init__(*args, **kwargs) def deconstruct(self): name, path, args, kwargs = super(OCDIDField, self).deconstruct() if self.ocd_type != "jurisdiction": kwargs.pop("default") kwargs.pop("max_length") kwargs.pop("primary_key") kwargs["ocd_type"] = self.ocd_type return (name, path, args, kwargs) class OCDBase(models.Model): """ common base fields across all top-level models """ created_at = models.DateTimeField( auto_now_add=True, help_text="The date and time of creation." ) updated_at = models.DateTimeField( auto_now=True, help_text="The date and time of the last update." ) extras = JSONField( default=dict, blank=True, help_text="A key-value store for storing arbitrary information not covered elsewhere.", ) locked_fields = ArrayField(base_field=models.TextField(), blank=True, default=list) class Meta: abstract = True class RelatedBase(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) class Meta: abstract = True class LinkBase(RelatedBase): note = models.CharField( max_length=300, blank=True, help_text="A short, optional note related to an object.", ) url = models.URLField( max_length=2000, help_text="A hyperlink related to an object." ) class Meta: abstract = True def __str__(self): return self.url class MimetypeLinkBase(RelatedBase): media_type = models.CharField(max_length=100) url = models.URLField(max_length=2000) text = models.TextField(default="", blank=True) class Meta: abstract = True class IdentifierBase(RelatedBase): identifier = models.CharField( max_length=300, help_text="A unique identifier developed by an upstream or third party source.", ) scheme = models.CharField( max_length=300, help_text="The name of the service that created the identifier." ) class Meta: abstract = True def __str__(self): return self.identifier class RelatedEntityBase(RelatedBase): name = models.CharField(max_length=2000) entity_type = models.CharField(max_length=20, blank=True) # optionally tied to an organization or person if it was linkable # for these two on_delete is SET_NULL so that deletion of a linked entity doesn't # delete this object- it should instead just become unresolved (NULL) organization = models.ForeignKey( "core.Organization", null=True, on_delete=models.SET_NULL ) person = models.ForeignKey("core.Person", null=True, on_delete=models.SET_NULL) @property def entity_name(self): if self.entity_type == "organization" and self.organization_id: return self.organization.name elif self.entity_type == "person" and self.person_id: return self.person.name else: return self.name @property def entity_id(self): if self.entity_type == "organization": return self.organization_id if self.entity_type == "person": return self.person_id return None class Meta: abstract = True