Source code for apis_core.apis_metainfo.models

import logging

from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db import models
from django.db.models.fields.related import ForeignKey, ManyToManyField
from django.db.models.fields.related_descriptors import ForwardManyToOneDescriptor
from django.forms import model_to_dict
from model_utils.managers import InheritanceManager

from apis_core.apis_metainfo import signals
from apis_core.generic.abc import GenericModel
from apis_core.utils import rdf
from apis_core.utils.normalize import clean_uri

logger = logging.getLogger(__name__)


NEXT_PREV = getattr(settings, "APIS_NEXT_PREV", True)


[docs] class RootObject(GenericModel, models.Model): """ The very root thing that can exist in a given ontology. Several classes inherit from it. By having one overarching super class we gain the advantage of unique identifiers. """ # self_contenttype: a foreign key to the respective contenttype comes in handy when querying for # triples where the subject's or object's contenttype must be respected (e.g. get all triples # where the subject is a Person) self_contenttype = models.ForeignKey( ContentType, on_delete=models.deletion.CASCADE, null=True, blank=True, editable=False, ) objects = models.Manager() objects_inheritance = InheritanceManager()
[docs] def save(self, *args, **kwargs): self.self_contenttype = ContentType.objects.get_for_model(self) super().save(*args, **kwargs)
[docs] def duplicate(self): origin = self.__class__ signals.pre_duplicate.send(sender=origin, instance=self) # usually, copying instances would work like # https://docs.djangoproject.com/en/4.2/topics/db/queries/#copying-model-instances # but we are working with abstract classes, # so we have to do it by hand using model_to_dict:( objdict = model_to_dict(self) # remove unique fields from dict representation unique_fields = [field for field in self._meta.fields if field.unique] for field in unique_fields: logger.info(f"Duplicating {self}: ignoring unique field {field.name}") objdict.pop(field.name, None) # remove related fields from dict representation related_fields = [ field for field in self._meta.get_fields() if field.is_relation ] for field in related_fields: objdict.pop(field.name, None) newobj = type(self).objects.create(**objdict) for field in related_fields: # we are not using `isinstance` because we want to # differentiate between different levels of inheritance if type(field) is ForeignKey: setattr(newobj, field.name, getattr(self, field.name)) if type(field) is ManyToManyField: objfield = getattr(newobj, field.name) values = getattr(self, field.name).all() objfield.set(values) newobj.save() signals.post_duplicate.send(sender=origin, instance=self, duplicate=newobj) return newobj
duplicate.alters_data = True
[docs] class InheritanceForwardManyToOneDescriptor(ForwardManyToOneDescriptor):
[docs] def get_queryset(self, **hints): return self.field.remote_field.model.objects_inheritance.db_manager( hints=hints ).select_subclasses()
[docs] class InheritanceForeignKey(models.ForeignKey): forward_related_accessor_class = InheritanceForwardManyToOneDescriptor
# Uri model # We use a custom UriManager, so we can override the queryset `get` # method. This way we can normalize the uri field.
[docs] class UriQuerySet(models.query.QuerySet):
[docs] def get(self, *args, **kwargs): if "uri" in kwargs: kwargs["uri"] = clean_uri(kwargs["uri"]) return super().get(*args, **kwargs)
[docs] class UriManager(models.Manager):
[docs] def get_queryset(self): return UriQuerySet(self.model)
[docs] class Uri(GenericModel, models.Model): uri = models.URLField(blank=True, null=True, unique=True, max_length=255) root_object = InheritanceForeignKey( RootObject, blank=True, null=True, on_delete=models.CASCADE ) objects = UriManager() def __str__(self): return str(self.uri)
[docs] def get_web_object(self): result = { "relation_pk": self.pk, "relation_type": "uri", "related_root_object": self.root_object.name, "related_root_object_url": self.root_object.get_absolute_url(), "related_root_object_class_name": self.root_object.__class__.__name__.lower(), "uri": self.uri, } return result
[docs] def save(self, *args, **kwargs): self.clean() return super().save(*args, **kwargs)
[docs] def clean(self): self.uri = clean_uri(self.uri) if self.uri and not hasattr(self, "root_object"): try: definition, attributes = rdf.get_definition_and_attributes_from_uri( self.uri ) if definition.getattr("model", False) and attributes: app_label, model = definition.getattr("model").split(".", 1) ct = ContentType.objects.get_by_natural_key(app_label, model) obj = ct.model_class()(**attributes) obj.save() self.root_object = obj else: raise ImproperlyConfigured( f"{self.uri}: did not find matching rdf defintion" ) except Exception as e: raise ValidationError(f"{e}: {self.uri}")