Skip to content

Commit

Permalink
refactor!: get rid of additional_serializer
Browse files Browse the repository at this point in the history
This was a clumsy workaround for ontologies not being standalone Django
apps. Its easier now to create serializers and hook them into Django, so
we don't need the additional_serializer logic anymore.

Closes: #338
  • Loading branch information
b1rger committed Nov 9, 2023
1 parent 83f10c5 commit 190e5f3
Show file tree
Hide file tree
Showing 2 changed files with 0 additions and 206 deletions.
198 changes: 0 additions & 198 deletions apis_core/api_routers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from dataclasses import dataclass
from functools import reduce
from ast import literal_eval

try:
from apis_ontology.models import *
Expand Down Expand Up @@ -469,199 +467,3 @@ def dispatch(self, request, *args, **kwargs):
# filter_classes = dict()
# lst_filter_classes_check = []
generic_serializer_creation_factory()


@dataclass
class AdditionalSerializerConfig:
"""
use this class in a list named 'additional_serializers_list' to load custom
serializers
"""

url: str # For now, it is recommended to prefix the url with 'additional'
name: str # unique name for django
path_structure: dict # see example structure


def load_additional_serializers():
try:
additional_serializers_list = getattr(
importlib.import_module("apis_ontology.additional_serializers"),
"additional_serializers_list",
)
except:
return []
# imports had to be done here, because if imported at top, python would mistake class 'InheritanceForwardManyToOneDescriptor'
# from different module apis_metainfo. No idea how this is even possible or could be properly fixed.
from django.db.models.query_utils import DeferredAttribute
from django.db.models.fields.related_descriptors import (
ForwardManyToOneDescriptor,
ReverseManyToOneDescriptor,
ManyToManyDescriptor,
)
from django.db.models.base import ModelBase
from apis_core.apis_relations.models import InheritanceForwardManyToOneDescriptor

def create_additional_viewset(path_structure):
def create_additional_serializer(path_structure):
if len(path_structure.keys()) != 1:
raise Exception()
target = list(path_structure.keys())[0]
model_fields = list(path_structure.values())[0]
if type(target) is ModelBase:
model_class = target
elif (
type(target) is ForwardManyToOneDescriptor
or type(target) is InheritanceForwardManyToOneDescriptor
or type(target) is ManyToManyDescriptor
):
model_class = target.field.related_model
elif type(target) is ReverseManyToOneDescriptor:
model_class = target.field.model
else:
raise Exception(
f"Unhandled case. Report to Stefan. type of field is: {type(target)}"
)
meta_fields = []
sub_serializers = {}
for field in model_fields:
if (
type(field) is DeferredAttribute
or type(field) is ForwardManyToOneDescriptor
or type(field) is InheritanceForwardManyToOneDescriptor
):
meta_fields.append(field.field.name)
# In case the referenced model class by the foreign key is parent class
# of designated target class, use the target class:
if field.field.model is not model_class and issubclass(
field.field.model, model_class
):
model_class = field.field.model
elif type(field) is ReverseManyToOneDescriptor:
meta_fields.append(field.rel.related_name)
elif type(field) is dict:
target = list(field.keys())[0]
if type(target) is ManyToManyDescriptor:
target_name = target.field.name
is_many = True
elif (
type(target) is ForwardManyToOneDescriptor
or type(target) is InheritanceForwardManyToOneDescriptor
):
target_name = target.field.name
is_many = False
elif type(target) is ReverseManyToOneDescriptor:
target_name = target.rel.name
is_many = True
else:
raise Exception(
f"Unhandled case. Report to Stefan. type of field is: {type(field)}"
)
sub_serializers[target_name] = create_additional_serializer(field)(
read_only=True, many=is_many
)
field["path_self"] = target_name
meta_fields.append(target_name)
else:
raise Exception(
f"Unhandled case. Report to Stefan. type of field is: {type(field)}"
)

class AdditionalSerializer(serializers.ModelSerializer):
for item in sub_serializers.items():
# Don't use temporary veriables here for the sub-serializer. Otherwise django would mistake
# the temporary variable as belonging to the parent serializer. Hence 'item[1]'
vars()[item[0]] = item[1]

class Meta:
model = model_class
fields = meta_fields

AdditionalSerializer.__name__ = (
AdditionalSerializer.__qualname__
) = f"Additional{model_class.__name__.title().replace(' ', '')}Serializer"

return AdditionalSerializer

def construct_prefetch_path_set(path_structure):
path_set = set()
if type(path_structure) is dict:
path_current = path_structure.get("path_self")
for path_structure_sub in list(path_structure.values())[0]:
for path_sub in construct_prefetch_path_set(path_structure_sub):
if path_current is not None:
path_set.add(path_current + "__" + path_sub)
else:
path_set.add(path_sub)
if len(path_set) == 0:
if path_current is not None:
path_set.add(path_current)
else:
return set()

return path_set

def main():
additional_serializer_class = create_additional_serializer(path_structure)

class AdditionalViewSet(viewsets.ModelViewSet):
queryset = additional_serializer_class.Meta.model.objects.all()
for prefetch_path in construct_prefetch_path_set(path_structure):
queryset = queryset.prefetch_related(prefetch_path)
serializer_class = additional_serializer_class

def get_queryset(self):
# TODO: Improve this param handling by extending the parsing logic
# or by forwarding the params untouched
# The original 'self.request.query_params' could not be forwarded
# directly to django's ORM filter So as a work-around, a dictionary
# is created and its values are casted. Maybe there a possibility
# can be found to forward the params directly?
params = {}
was_parsed = False
for k, v in self.request.query_params.items():
# check for pagination params:
if k == "limit" or k == "offset":
continue
# check for int
try:
v = int(v)
except:
pass
else:
was_parsed = True
# check for boolean
if not was_parsed:
if v.lower() == "true":
v = True
was_parsed = True
elif v.lower() == "false":
v = False
was_parsed = True
# check for list
if not was_parsed:
if k.endswith("__in"):
try:
v = literal_eval(v)
except:
pass
else:
was_parsed = True
params[k] = v

return self.queryset.filter(**params)

AdditionalViewSet.__name__ = (
AdditionalViewSet.__qualname__
) = f"Additional{additional_serializer_class.Meta.model.__name__.title().replace(' ', '')}ViewSet"

return AdditionalViewSet

return main()

for additional_serializer in additional_serializers_list:
additional_serializer.viewset = create_additional_viewset(
additional_serializer.path_structure
)

return additional_serializers_list
8 changes: 0 additions & 8 deletions apis_core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from django.views.generic import TemplateView
from rest_framework import routers

from apis_core.api_routers import load_additional_serializers
from apis_core.api_routers import views

# from apis_core.apis_entities.api_views import (
Expand Down Expand Up @@ -38,13 +37,6 @@
# inject the manually created UriToObjectViewSet into the api router
router.register(r"metainfo/uritoobject", UriToObjectViewSet, basename="uritoobject")

for additional_serializer in load_additional_serializers():
router.register(
additional_serializer.url,
additional_serializer.viewset,
additional_serializer.name,
)

# router.register(r"users", UserViewSet)
# router.register(r"GeoJsonPlace", PlaceGeoJsonViewSet, "PlaceGeoJson")
# router.register(r"NetJson", NetJsonViewSet, "NetJson")
Expand Down

0 comments on commit 190e5f3

Please sign in to comment.