Skip to content

Commit

Permalink
feat: improve cidoc serializers
Browse files Browse the repository at this point in the history
adds a serializer for Personinstitution Relations;
adds proper birth and death events
  • Loading branch information
sennierer committed Nov 28, 2024
1 parent afbba4a commit 44db13c
Showing 1 changed file with 192 additions and 19 deletions.
211 changes: 192 additions & 19 deletions apis_ontology/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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(
(
Expand All @@ -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

0 comments on commit 44db13c

Please sign in to comment.