import inspect
import sys
import yaml
# from reversion import revisions as reversion
import reversion
from crum import get_current_request
from django.conf import settings
from django.db import models
from django.db.models import Q
from apis_core.apis_entities.models import Person
from apis_core.apis_metainfo.models import TempEntityClass
#######################################################################
#
# Custom Managers
#
#######################################################################
def find_if_user_accepted():
request = get_current_request()
if request is not None:
print('running through request')
if request.user.is_authenticated:
print('authenticated')
return {}
else:
return {'published': True}
else:
return {}
[docs]class RelationPublishedQueryset(models.QuerySet):
def filter_for_user(self, *args, **kwargs):
if getattr(settings, "APIS_SHOW_ONLY_PUBLISHED", False):
request = get_current_request()
if request is not None:
if request.user.is_authenticated:
return self
else:
return self.filter(published=True)
else:
return self.filter(published=True)
else:
return self
[docs] def filter_ann_proj(self, request=None, ann_proj=1, include_all=True):
"""The filter function provided by the manager class.
:param request: `django.request` object
:return: queryset that contains only objects that are shown in the highlighted text or those not connected
to an annotation at all.
"""
qs = self
users_show = None
if request:
ann_proj = request.session.get('annotation_project', False)
if not ann_proj:
return qs
users_show = request.session.get('users_show_highlighter', None)
query = Q(annotation_set__annotation_project_id=ann_proj)
if users_show is not None:
query.add(Q(annotation_set__user_added_id__in=users_show), Q.AND)
if include_all:
query.add(Q(annotation_set__annotation_project__isnull=True), Q.OR)
return qs.filter(query)
class BaseRelationManager(models.Manager):
def get_queryset(self):
return RelationPublishedQueryset(self.model, using=self._db)
def filter_ann_proj(self, request=None, ann_proj=1, include_all=True):
return self.get_queryset().filter_ann_proj(request=request, ann_proj=ann_proj, include_all=include_all)
def filter_for_user(self):
if hasattr(settings, "APIS_SHOW_ONLY_PUBLISHED") or "apis_highlighter" in getattr(settings, "INSTALLED_APPS"):
return self.get_queryset().filter_for_user()
else:
return self.get_queryset()
#######################################################################
#
# AbstractRelation
#
#######################################################################
[docs]class AbstractRelation(TempEntityClass):
"""
Abstract super class which encapsulates common logic between the different relations and provides various methods
relating to either all or specific relations.
"""
objects = BaseRelationManager()
#annotation_links = AnnotationRelationLinkManager()
class Meta:
abstract = True
default_manager_name = 'objects'
# Methods dealing with individual data retrievals of instances
####################################################################################################################
def __str__(self):
return "{} ({}) {}".format(self.get_related_entity_instanceA(), self.relation_type, self.get_related_entity_instanceB())
def get_web_object(self):
nameA = self.get_related_entity_instanceA().name
nameB = self.get_related_entity_instanceB().name
if self.get_related_entity_classA() == Person:
nameA += ", "
if self.get_related_entity_instanceA().first_name is None:
nameA += "-"
else:
nameA += self.get_related_entity_instanceA().first_name
if self.get_related_entity_classB() == Person:
nameB += ", "
if self.get_related_entity_instanceB().first_name is None:
nameB += "-"
else:
nameB += self.get_related_entity_instanceB().first_name
result = {
'relation_pk': self.pk,
'relation_type': self.relation_type.name,
self.get_related_entity_field_nameA(): nameA,
self.get_related_entity_field_nameB(): nameB,
'start_date': self.start_date_written,
'end_date': self.end_date_written}
return result
[docs] def get_table_dict(self, entity):
"""Dict for the tabels in the html view
:param entity: Object of type :class:`entities.models.Place`; Used to determine which Place is the main antity
and which one the related.
:return:
"""
if self.get_related_entity_instanceA() == entity:
rel_other_key = self.get_related_entity_field_nameB()[:-1]
rel_other_value = self.get_related_entity_instanceB()
rel_type = self.relation_type.label
elif self.get_related_entity_instanceB() == entity:
rel_other_key = self.get_related_entity_field_nameA()[:-1]
rel_other_value = self.get_related_entity_instanceA()
rel_type = self.relation_type.label_reverse
else:
raise Exception("Did not find corresponding entity. Wiring of current relation to current entity is faulty.")
result = {
'relation_pk': self.pk,
'relation_type': rel_type,
rel_other_key: rel_other_value,
'start_date_written': self.start_date_written,
'end_date_written': self.end_date_written,
'start_date': self.start_date,
'end_date': self.end_date}
return result
# Various Methods enabling convenient shortcuts between entities, relations, fields, etc
####################################################################################################################
# Methods dealing with all relations
####################################################################################################################
_all_relation_classes = None
_all_relation_names = None
[docs] @classmethod
def get_all_relation_classes(cls):
"""
Instantiates both lists: '_all_relation_classes' and '_all_relation_names'
:return: list of all python classes of the relations defined within this models' module
"""
# check if not yet instantiated
if cls._all_relation_classes == None:
# if not, then instantiate the private lists
relation_classes = []
relation_names = []
# using python's reflective logic, the following loop iterates over all classes of this current module.
for relation_name, relation_class in inspect.getmembers(
sys.modules[__name__], inspect.isclass):
print('inspecting classes')
# check for python classes not to be used.
if \
relation_class.__module__ == "apis_core.apis_relations.models" and \
relation_class.__name__ != "AnnotationRelationLinkManager" and \
relation_class.__name__ != "BaseRelationManager" and \
relation_class.__name__ != "RelationPublishedQueryset" and \
relation_class.__name__ != "AbstractRelation" and \
relation_name != "ent_class":
relation_classes.append(relation_class)
relation_names.append(relation_name.lower())
cls._all_relation_classes = relation_classes
cls._all_relation_names = relation_names
return cls._all_relation_classes
[docs] @classmethod
def get_relation_class_of_name(cls, relation_name):
"""
:param entity_name: str : The name of an relation
:return: The model class of the relation respective to the given name
"""
for relation_class in cls.get_all_relation_classes():
if relation_class.__name__.lower() == relation_name.lower():
return relation_class
raise Exception("Could not find relation class of name:", relation_name)
[docs] @classmethod
def get_all_relation_names(cls):
"""
:return: list of all class names in lower case of the relations defined within this models' module
"""
if cls._all_relation_classes == None:
# The instantion logic of relation_names list is coupled to the instantiation logic of the relation_classes
# list done in the method 'get_all_relation_classes'; hence just calling that is sufficient.
cls.get_all_relation_classes()
return cls._all_relation_names
# Methods dealing with related relations and entities
####################################################################################################################
_relation_classes_of_entity_class = {}
_relation_classes_of_entity_name = {}
_relation_field_names_of_entity_class = {}
[docs] @classmethod
def get_relation_classes_of_entity_class(cls, entity_class):
"""
:param entity_class : class of an entity for which the related relations should be returned
:return: a list of relation classes that are related to the entity class
E.g. AbstractRelation.get_relation_classes_of_entity_class( Person )
-> [ PersonEvent, PersonInstitution, PersonPerson, PersonPlace, PersonWork ]
"""
return cls._relation_classes_of_entity_class[entity_class]
[docs] @classmethod
def get_relation_classes_of_entity_name(cls, entity_name):
"""
:param entity_name : class name of an entity for which the related relations should be returned
:return: a list of relation classes that are related to the entity class
E.g. AbstractRelation.get_relation_classes_of_entity_class( 'person' )
-> [ PersonEvent, PersonInstitution, PersonPerson, PersonPlace, PersonWork ]
"""
return cls._relation_classes_of_entity_name[entity_name.lower()]
[docs] @classmethod
def add_relation_class_of_entity_class(cls, entity_class):
"""
Adds the given entity class to a list which is later retrieved via a dictionary, where the entity class and entity class name
define the key and the list of related relation classes as their values.
:param entity_class: the class for which the related relation (the current cls) will be saved into a respective list.
:return: None
"""
# get the list of the class dictionary, create if not yet exists.
relation_class_list = cls._relation_classes_of_entity_class.get(entity_class, [])
# append the current relation class to the list.
relation_class_list.append(cls)
# save into the dictionary, which uses the entity class as key and the extended list above as value.
cls._relation_classes_of_entity_class[entity_class] = relation_class_list
cls._relation_classes_of_entity_name[entity_class.__name__.lower()] = relation_class_list
[docs] @classmethod
def get_relation_field_names_of_entity_class(cls, entity_class):
"""
:param entity_class : class of an entity for which the related relation class field names should be returned
:return: a list of relation class field names that are related to the entity class
E.g. AbstractRelation.get_relation_names_of_entity_class( Person )
-> [ personevent_set, personinstitution_set, related_personA, related_personB, personplace_set, personwork_set ]
"""
return cls._relation_field_names_of_entity_class[entity_class]
[docs] @classmethod
def add_relation_field_name_of_entity_class(cls, relation_name, entity_class):
"""
Adds the given entity class to a list which is later retrieved via a dictionary, where the entity class
defines the key and the list of related relation classes as its values.
:param entity_class: the class for which the related relation (the current cls) will be saved into a respective list.
:return: None
"""
# get the list of the class dictionary, create if not yet exists.
relation_names_list = cls._relation_field_names_of_entity_class.get(entity_class, [])
# append the current relation field name to the list.
if relation_name not in relation_names_list:
relation_names_list.append(relation_name) #TODO: this is a workaround, find out why it is called several times
# save into the dictionary, which uses the entity class as key and the extended list above as value.
cls._relation_field_names_of_entity_class[entity_class] = relation_names_list
# method stumps
####################################################################################################################
# These stumps merely serve as placeholders so that both IDE and developers know that these methods exist.
# They are implemented programmatically in the function 'generate_all_fields' in the class 'EntityRelationFieldGenerator'.
#######################################################################
#
# Person - ... - Relation
#
#######################################################################
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class PersonPerson(AbstractRelation):
pass
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class PersonPlace(AbstractRelation):
pass
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class PersonInstitution(AbstractRelation):
pass
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class PersonEvent(AbstractRelation):
pass
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class PersonWork(AbstractRelation):
pass
#######################################################################
#
# Institution - ... - Relation
#
#######################################################################
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class InstitutionInstitution(AbstractRelation):
pass
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class InstitutionPlace(AbstractRelation):
pass
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class InstitutionEvent(AbstractRelation):
pass
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class InstitutionWork(AbstractRelation):
pass
#######################################################################
#
# Place - ... - Relation
#
#######################################################################
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class PlacePlace(AbstractRelation):
pass
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class PlaceEvent(AbstractRelation):
pass
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class PlaceWork(AbstractRelation):
pass
#######################################################################
#
# Event - ... - Relation
#
#######################################################################
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class EventEvent(AbstractRelation):
pass
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class EventWork(AbstractRelation):
pass
#######################################################################
#
# Work - ... - Relation
#
#######################################################################
[docs]@reversion.register(follow=['tempentityclass_ptr'])
class WorkWork(AbstractRelation):
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']:
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()}"
if rel_class_name not in globals().keys():
print(rel_class_name)
ent_class = type(rel_class_name, (AbstractRelation,), attributes)
globals()[rel_class_name] = ent_class