diff --git a/apis_ontology/serializers.py b/apis_ontology/serializers.py index cd46ef5..df58013 100644 --- a/apis_ontology/serializers.py +++ b/apis_ontology/serializers.py @@ -3,6 +3,27 @@ from rdflib.namespace import RDF, RDFS, XSD from apis_core.generic.serializers import GenericHyperlinkedModelSerializer from apis_core.relations.utils import relation_content_types +from apis_ontology.models import Person, Institution +from django.conf import settings + + +def normalize_empty_attributes(instance): + """ + Normalize empty string attributes of a Django model instance to None. + Only processes actual model fields, not methods or private attributes. + + Args: + instance: Django model instance + + Returns: + The modified instance with empty strings converted to None + """ + for field in instance._meta.fields: + value = getattr(instance, field.name, None) + if isinstance(value, str) and not value: + setattr(instance, field.name, None) + return instance + # Dynamically create and add serializer classes to this module for ct in relation_content_types(): @@ -24,21 +45,30 @@ class PersonCidocSerializer(serializers.BaseSerializer): def to_representation(self, instance): g = Graph() + instance = normalize_empty_attributes(instance) # Define namespaces crm = Namespace("http://www.cidoc-crm.org/cidoc-crm/") - apis = Namespace("http://apis.acdh.oeaw.ac.at/") + oebl_person = Namespace( + f"{getattr(settings, 'APIS_BASE_URI')}apis_ontology.person/" + ) + oebl_appellation = Namespace( + f"{getattr(settings, 'APIS_BASE_URI')}appellation/" + ) + oebl_attr = Namespace(f"{getattr(settings, 'APIS_BASE_URI')}attributes/") g.namespace_manager.bind("crm", crm, replace=True) - g.namespace_manager.bind("apis", apis, replace=True) + g.namespace_manager.bind("oebl-person", oebl_person, replace=True) + g.namespace_manager.bind("oebl-appellation", oebl_appellation, replace=True) + g.namespace_manager.bind("oebl-attr", oebl_attr, replace=True) # Create the Person instance - person_uri = URIRef(apis[str(instance.id)]) + person_uri = URIRef(oebl_person[str(instance.id)]) g.add((person_uri, RDF.type, crm.E21_Person)) # Add properties - appellation_uri = URIRef(apis[f"appellation_{instance.id}"]) - g.add((appellation_uri, RDF.type, crm.E41_Appellation)) + appellation_uri = URIRef(oebl_appellation[str(instance.id)]) + g.add((appellation_uri, RDF.type, crm.E33_E41_Linguistic_Appellation)) g.add((person_uri, crm.P1_is_identified_by, appellation_uri)) g.add( ( @@ -49,40 +79,183 @@ def to_representation(self, instance): ) if hasattr(instance, "forename"): - forename_uri = URIRef(apis[f"forename_{instance.id}"]) - g.add((forename_uri, RDF.type, crm.E41_Appellation)) + forename_uri = URIRef(oebl_appellation[f"forename_{instance.id}"]) + g.add((forename_uri, RDF.type, crm.E33_E41_Linguistic_Appellation)) g.add((appellation_uri, crm.P106_is_composed_of, forename_uri)) g.add((forename_uri, RDFS.label, Literal(instance.forename))) if hasattr(instance, "surname"): - surname_uri = URIRef(apis[f"surname_{instance.id}"]) - g.add((surname_uri, RDF.type, crm.E41_Appellation)) + surname_uri = URIRef(oebl_appellation[f"surname_{instance.id}"]) + g.add((surname_uri, RDF.type, crm.E33_E41_Linguistic_Appellation)) g.add((appellation_uri, crm.P106_is_composed_of, surname_uri)) g.add((surname_uri, RDFS.label, Literal(instance.surname))) - if hasattr(instance, "birth_date"): - birth_event = URIRef(apis[f"birth_{instance.id}"]) + if instance.start_date_written is not None: + birth_event = URIRef(oebl_attr[f"birth_{instance.id}"]) + birth_time_span = URIRef(oebl_attr[f"birth_time-span_{instance.id}"]) g.add((birth_event, RDF.type, crm.E67_Birth)) g.add((birth_event, crm.P98_brought_into_life, person_uri)) + g.add((birth_event, crm["P4_has_time-span"], birth_time_span)) + g.add((birth_time_span, RDF.type, crm["E52_Time-Span"])) g.add( ( - birth_event, - crm.P4_has_time_span, - Literal(instance.birth_date, datatype=XSD.date), + birth_time_span, + crm.P82a_begin_of_the_begin, + Literal(instance.start_date, datatype=XSD.date) + if instance.start_date is not None + else Literal(instance.start_date_written), + ) + ) + g.add( + ( + birth_time_span, + crm.P82b_end_of_the_end, + Literal(instance.start_date, datatype=XSD.date) + if instance.start_date is not None + else Literal(instance.start_date_written), ) ) - if hasattr(instance, "death_date"): - death_event = URIRef(apis[f"death_{instance.id}"]) + if instance.end_date_written is not None: + death_event = URIRef(oebl_attr[f"death_{instance.id}"]) + death_time_span = URIRef(oebl_attr[f"death_time-span_{instance.id}"]) g.add((death_event, RDF.type, crm.E69_Death)) g.add((death_event, crm.P100_was_death_of, person_uri)) + g.add((death_event, crm["P4_has_time-span"], death_time_span)) + g.add((death_time_span, RDF.type, crm["E52_Time-Span"])) + g.add( + ( + death_time_span, + crm.P82a_begin_of_the_begin, + Literal(instance.end_date, datatype=XSD.date) + if instance.end_date is not None + else Literal(instance.end_date_written), + ) + ) g.add( ( - death_event, - crm.P4_has_time_span, - Literal(instance.death_date, datatype=XSD.date), + death_time_span, + crm.P82b_end_of_the_end, + Literal(instance.end_date, datatype=XSD.date) + if instance.end_date is not None + else Literal(instance.end_date_written), ) ) # Serialize the graph to RDF/XML return g + + +class PersonInstitutionCidocBaseSerializer(serializers.BaseSerializer): + def to_representation(self, instance): + instance = normalize_empty_attributes(instance) + g = Graph() + + # Define namespaces + crm = Namespace("http://www.cidoc-crm.org/cidoc-crm/") + oebl_person = Namespace( + f"{getattr(settings, 'APIS_BASE_URI')}apis_ontology.person/" + ) + oebl_inst = Namespace( + f"{getattr(settings, 'APIS_BASE_URI')}apis_ontology.institution/" + ) + oebl_appellation = Namespace( + f"{getattr(settings, 'APIS_BASE_URI')}appellation/" + ) + oebl_attr = Namespace(f"{getattr(settings, 'APIS_BASE_URI')}attributes/") + + g.namespace_manager.bind("crm", crm, replace=True) + g.namespace_manager.bind("oebl-person", oebl_person, replace=True) + g.namespace_manager.bind("oebl-inst", oebl_inst, replace=True) + g.namespace_manager.bind("oebl-appellation", oebl_appellation, replace=True) + g.namespace_manager.bind("oebl-attr", oebl_attr, replace=True) + person_id = ( + instance.obj_oject_id + if instance.obj_content_type.model == "person" + else instance.subj_object_id + ) + # Create the Person instance + person_uri = URIRef(oebl_person[str(person_id)]) + g.add( + (person_uri, RDF.type, crm.E21_Person) + ) # maybe remove that as also in detail view of person + inst_id = ( + instance.subj_object_id + if instance.subj_object_id != person_id + else instance.obj_object_id + ) + inst_uri = URIRef(oebl_inst[str(inst_id)]) + g.add((inst_uri, RDF.type, crm.E74_Group)) + joining_uri = URIRef(oebl_attr[f"joining_ev_{instance.id}"]) + pc_joining_uri = URIRef(oebl_attr[f"pc_joining_ev_{instance.id}"]) + memb_type_uri = URIRef(oebl_attr[f"kind_member_{instance.id}"]) + g.add((person_uri, crm.P143i_was_joined_by, joining_uri)) + g.add((joining_uri, crm.P01i_is_domain_of, pc_joining_uri)) + g.add((pc_joining_uri, crm.P02_has_range, inst_uri)) + g.add((pc_joining_uri, crm.P144_1_kind_of_member, memb_type_uri)) + g.add((memb_type_uri, RDFS.label, Literal(instance.name()))) + if instance.start_date_written is not None: + joining_time_span_uri = URIRef( + oebl_attr[f"joining_ev_time_span_{instance.id}"] + ) + + g.add((joining_uri, crm["P4_has_time-span"], joining_time_span_uri)) + g.add((joining_time_span_uri, RDF.type, crm["E52_Time-Span"])) + g.add( + ( + joining_time_span_uri, + crm.P82a_begin_of_the_begin, + Literal(instance.start_date, datatype=XSD.date) + if instance.start_date is not None + else Literal(instance.start_date_written), + ) + ) + g.add( + ( + joining_time_span_uri, + crm.P82b_end_of_the_end, + Literal(instance.start_date, datatype=XSD.date) + if instance.start_date is not None + else Literal(instance.start_date_written), + ) + ) + if instance.end_date_written is not None: + leaving_time_span_uri = URIRef( + oebl_attr[f"leaving_ev_time_span_{instance.id}"] + ) + leaving_uri = URIRef(oebl_attr[f"leaving_ev_{instance.id}"]) + g.add((leaving_uri, RDF.type, crm.E86_Leaving)) + g.add((leaving_uri, crm["P4_has_time-span"], leaving_time_span_uri)) + g.add((leaving_time_span_uri, RDF.type, crm["E52_Time-Span"])) + g.add( + ( + leaving_time_span_uri, + crm.P82a_begin_of_the_begin, + Literal(instance.end_date, datatype=XSD.date) + if instance.end_date is not None + else Literal(instance.end_date_written), + ) + ) + g.add( + ( + leaving_time_span_uri, + crm.P82b_end_of_the_end, + Literal(instance.end_date, datatype=XSD.date) + if instance.end_date is not None + else Literal(instance.end_date_written), + ) + ) + return g + + +for ct in relation_content_types(combination=(Person, Institution)): + cls = ct.model_class() + serializer_class = type( + f"{cls.__name__}CidocSerializer", + (PersonInstitutionCidocBaseSerializer,), + { + "__module__": __name__, + }, + ) + # Add the new serializer class to the module globals + globals()[f"{cls.__name__}CidocSerializer"] = serializer_class