import inspect
import re
import sys
import unicodedata
import yaml
from django.contrib.auth.models import User
from django.db import models
from django.utils.functional import cached_property
from reversion import revisions as reversion
from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
[docs]@reversion.register()
class VocabNames(models.Model):
"""List of Vocabulary names to allow the easy retrieval\
of Vovcabulary names and classes from the VocabsBaseClass"""
name = models.CharField(max_length=255)
def get_vocab_label(self):
return re.sub(r"([A-Z])", r" \1", self.name).strip()
[docs]@reversion.register()
class VocabsBaseClass(models.Model):
""" An abstract base class for other classes which contain so called
'controlled vocabulary' to describe subtypes of main temporalized
entites"""
choices_status = (
('rej', 'rejected'),
('ac', 'accepted'),
('can', 'candidate'),
('del', 'deleted')
)
name = models.CharField(max_length=255, verbose_name='Name')
description = models.TextField(
blank=True,
help_text="Brief description of the used term.")
parent_class = models.ForeignKey(
'self', blank=True, null=True,
on_delete=models.CASCADE
)
status = models.CharField(max_length=4, choices=choices_status, default='can')
userAdded = models.ForeignKey(
User, blank=True, null=True,
on_delete=models.SET_NULL
)
vocab_name = models.ForeignKey(
VocabNames, blank=True, null=True,
on_delete=models.SET_NULL
)
if 'apis_highlighter' in settings.INSTALLED_APPS:
from apis_highlighter.models import Annotation
annotation_set = GenericRelation(Annotation)
def __str__(self):
return self.label
[docs] def save(self, *args, **kwargs):
d, created = VocabNames.objects.get_or_create(name=type(self).__name__)
self.vocab_name = d
if self.name != unicodedata.normalize('NFC', self.name): # secure correct unicode encoding
self.name = unicodedata.normalize('NFC', self.name)
super(VocabsBaseClass, self).save(*args, **kwargs)
return self
@cached_property
def label(self):
d = self
res = self.name
while d.parent_class:
res = d.parent_class.name + ' >> ' + res
d = d.parent_class
return res
[docs]@reversion.register(follow=['vocabsbaseclass_ptr'])
class RelationBaseClass(VocabsBaseClass):
""" An abstract base class for other classes which contain so called
'controlled vocabulary' to describe the relations between main temporalized
entities ('db_')"""
name_reverse = models.CharField(
max_length=255,
verbose_name='Name reverse',
help_text='Inverse relation like: "is sub-class of" vs. "is super-class of".',
blank=True)
def __str__(self):
return self.name
@cached_property
def label_reverse(self):
d = self
if len(self.name_reverse) < 1:
res = '(' + self.name + ')'
else:
res = self.name_reverse
while d.parent_class:
try:
t = RelationBaseClass.objects.get(pk=d.parent_class.pk).name_reverse
if len(t) < 1:
t = '(' + d.parent_class.name + ')'
except Exception as e:
t = '(' + d.parent_class.name + ')'
res = t + ' >> ' + res
d = d.parent_class
return res
[docs] def save(self, *args, **kwargs):
if self.name_reverse != unicodedata.normalize('NFC', self.name_reverse):
self.name_reverse = unicodedata.normalize('NFC', self.name_reverse)
if self.name_reverse == "" or self.name_reverse == None:
self.name_reverse = self.name + " [REVERSE]"
super(RelationBaseClass, self).save(*args, **kwargs)
return self
[docs]@reversion.register()
class VocabsUri(models.Model):
"""Class to store URIs for imported types. URI class from metainfo is not
used in order to keep the vocabularies module/app seperated from the rest of the application.
"""
uri = models.URLField()
domain = models.CharField(max_length=255, blank=True)
rdf_link = models.URLField(blank=True)
vocab = models.ForeignKey(VocabsBaseClass, blank=True, null=True,
on_delete=models.CASCADE)
# loaded: set to True when RDF was loaded and parsed into the data model
loaded = models.BooleanField(default=False)
# loaded_time: Timestamp when file was loaded and parsed
loaded_time = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.uri
#######################################################################
#
# entity types
#
#######################################################################
[docs]@reversion.register(follow=['vocabsbaseclass_ptr'])
class WorkType(VocabsBaseClass):
"""Holds controlled vocabularies about work-types"""
pass
[docs]@reversion.register(follow=['vocabsbaseclass_ptr'])
class Title(VocabsBaseClass):
"""A person´s (academic) title"""
abbreviation = models.CharField(max_length=10, blank=True)
[docs]@reversion.register(follow=['vocabsbaseclass_ptr'])
class ProfessionType(VocabsBaseClass):
"""Holds controlled vocabularies about profession-types"""
pass
[docs]@reversion.register(follow=['vocabsbaseclass_ptr'])
class PlaceType(VocabsBaseClass):
"""Holds controlled vocabularies about place-types"""
pass
[docs]@reversion.register(follow=['vocabsbaseclass_ptr'])
class InstitutionType(VocabsBaseClass):
"""Holds controlled vocabularies about institution-types"""
pass
[docs]@reversion.register(follow=['vocabsbaseclass_ptr'])
class EventType(VocabsBaseClass):
"""Holds controlled vocabularies about event-types"""
pass
[docs]@reversion.register(follow=['vocabsbaseclass_ptr'])
class LabelType(VocabsBaseClass):
"""Holds controlled vocabularies about label-types"""
pass
[docs]@reversion.register(follow=['vocabsbaseclass_ptr'])
class CollectionType(VocabsBaseClass):
"""e.g. reseachCollection, importCollection """
pass
[docs]@reversion.register(follow=['vocabsbaseclass_ptr'])
class TextType(VocabsBaseClass):
"""used to store the Text types for the forms"""
entity = models.CharField(max_length=255)
collections = models.ManyToManyField('apis_metainfo.Collection', blank=True)
lang = models.CharField(
max_length=3, blank=True, null=True,
help_text="The ISO 639-3 (or 2) code for the label's language.",
verbose_name='ISO Code', default='deu')
#######################################################################
#
# relation types
#
#######################################################################
[docs]class AbstractRelationType(RelationBaseClass):
"""
Abstract super class which encapsulates common logic between the different relationtypes and provides various methods
relating to either all or a specific relationtypes.
"""
class Meta:
abstract = True
_all_relationtype_classes = None
_all_relationtype_names = None
_related_entity_field_names = None
# Methods dealing with all relationtypes
####################################################################################################################
[docs] @classmethod
def get_all_relationtype_classes(cls):
"""
:return: list of all python classes of the relationtypes defined within this models' module
"""
if cls._all_relationtype_classes == None:
relationtype_classes = []
relationtype_names = []
for relationtype_name, relationtype_class in inspect.getmembers(
sys.modules[__name__], inspect.isclass):
if relationtype_class.__module__ == "apis_core.apis_vocabularies.models" and \
relationtype_name != "ent_class" and \
relationtype_name.endswith("Relation"):
relationtype_classes.append(relationtype_class)
relationtype_names.append(relationtype_name.lower())
cls._all_relationtype_classes = relationtype_classes
cls._all_relationtype_names = relationtype_names
return cls._all_relationtype_classes
[docs] @classmethod
def get_relationtype_class_of_name(cls, relationtype_name):
"""
:param entity_name: str : The name of an relationtype
:return: The model class of the relationtype respective to the given name
"""
for relationtype_class in cls.get_all_relationtype_classes():
if relationtype_class.__name__.lower() == relationtype_name.lower():
return relationtype_class
raise Exception("Could not find relationtype class of name:", relationtype_name)
[docs] @classmethod
def get_all_relationtype_names(cls):
"""
:return: list of all class names in lower case of the relationtypes defined within this models' module
"""
if cls._all_relationtype_names == None:
cls.get_all_relationtype_classes()
return cls._all_relationtype_names
# Methods dealing with related entities
####################################################################################################################
#######################################################################
# Person-Relation-Types
#######################################################################
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class PersonPersonRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Persons and Persons"""
pass
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class PersonPlaceRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Persons and Places"""
pass
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class PersonInstitutionRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Persons and Persons"""
pass
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class PersonEventRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Persons and Events"""
pass
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class PersonWorkRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Persons and Works"""
pass
#######################################################################
# Institution-Relation-Types
#######################################################################
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class InstitutionEventRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Institutions and Events."""
pass
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class InstitutionPlaceRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Institutions and Places."""
pass
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class InstitutionInstitutionRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Institutions and Institutions."""
pass
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class InstitutionWorkRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Institutions and Works."""
pass
#######################################################################
# Place-Relation-Types
#######################################################################
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class PlacePlaceRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Places and Places"""
pass
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class PlaceEventRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Places and Events"""
pass
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class PlaceWorkRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Places and Works"""
pass
#######################################################################
# Event-Relation-Types
#######################################################################
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class EventEventRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Events and Events"""
pass
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class EventWorkRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Events and Works"""
pass
#######################################################################
# Work-Relation-Types
#######################################################################
[docs]@reversion.register(follow=['relationbaseclass_ptr'])
class WorkWorkRelation(AbstractRelationType):
"""Holds controlled vocabularies relation types of Works and Works"""
pass
a_ents = getattr(settings, 'APIS_ADDITIONAL_ENTITIES', False)
if a_ents:
with open(a_ents, 'r') as ents_file:
ents = yaml.load(ents_file, Loader=yaml.CLoader)
print(ents)
for ent in ents['entities']:
for voc in ent.get('vocabs', []):
attributes = {"__module__": __name__}
ent_class = type(voc, (VocabsBaseClass,), attributes)
globals()[ent['name']] = ent_class
rels = ent.get("relations", [])
base_ents = ['Person', 'Institution', 'Place', 'Work', 'Event']
if isinstance(rels, str):
if rels == 'all':
rels = base_ents + [x['name'].title() for x in ents['entities']]
else:
rels = base_ents + rels
for r2 in rels:
attributes = {"__module__":__name__}
if r2 in base_ents:
rel_class_name = f"{r2.title()}{ent['name'].title()}"
else:
rel_class_name = f"{ent['name'].title()}{r2.title()}"
attributes = {"__module__": __name__}
if f"{rel_class_name}Relation" not in globals().keys():
ent_class = type(f"{rel_class_name}Relation", (AbstractRelationType,), attributes)
globals()[f"{rel_class_name}Relation"] = ent_class