Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/missing user serialiser init #182

Merged
merged 10 commits into from
Oct 2, 2024
Merged
2 changes: 1 addition & 1 deletion api_fhir_r4/containedResources/containedResourceHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,4 @@ def _is_saved_in_db(self, obj: models.Model):

def _model_to_dict(self, instance):
# Due to how serializers are build simple __dict__ is used instead of builtin model_to_dict
return instance.__dict__
return {k: v for k, v in instance.__dict__.items() if not k.startswith('_')}
4 changes: 3 additions & 1 deletion api_fhir_r4/containedResources/serializerMixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ def _get_converted_resources(self, obj):

def to_internal_value(self, data):
audit_user_id = self.get_audit_user_id()
return self.fhirConverter.to_imis_obj(data, audit_user_id).__dict__
imis_obj = self.fhirConverter(user=self.user).to_imis_obj(data, audit_user_id)
# Filter out special attributes
return {k: v for k, v in imis_obj.__dict__.items() if not k.startswith('_') and v is not None}

def create(self, validated_data):
self._create_or_update_contained(validated_data)
Expand Down
40 changes: 31 additions & 9 deletions api_fhir_r4/converters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from fhir.resources.R4B.reference import Reference
from fhir.resources.R4B.identifier import Identifier
from api_fhir_r4.configurations import GeneralConfiguration

from uuid import UUID

class BaseFHIRConverter(ABC):

Expand All @@ -25,13 +25,8 @@ def __init__(self, user=None):
if user:
self.user = user
else:
self.user = core.models.InteractiveUser.objects.filter(
user_roles=core.models.UserRole.objects.filter(
role__is_system=64,
*core.utils.filter_validity()
),
*core.utils.filter_validity()
).first()
raise Exception("Converter init need a valid user")


@classmethod
def to_fhir_obj(cls, obj, reference_type):
Expand Down Expand Up @@ -275,7 +270,34 @@ def get_id_from_reference(cls, reference):
id_from_reference = splited_reference_string.pop()
return id_from_reference


@classmethod
def build_imis_identifier(cls, imis_obj, fhir_obj, errors):
history_model = issubclass(imis_obj.__class__, core.models.HistoryModel)
code = cls.get_fhir_identifier_by_code(
fhir_obj.identifier,
R4IdentifierConfig.get_fhir_generic_type_code()
)
uuid_str = cls.get_fhir_identifier_by_code(
fhir_obj.identifier,
R4IdentifierConfig.get_fhir_uuid_type_code()
)
id_str = None
if not history_model:
id_str = cls.get_fhir_identifier_by_code(
fhir_obj.identifier,
R4IdentifierConfig.get_fhir_acsn_type_code()
)
if id_str:
imis_obj.id = id_str
if uuid_str:
if history_model:
imis_obj.id = UUID(uuid_str)
else:
imis_obj.uuid = UUID(uuid_str)
# if we have the id th uuid in not used
if code:
imis_obj.code = code

from api_fhir_r4.converters.personConverterMixin import PersonConverterMixin
from api_fhir_r4.converters.referenceConverterMixin import ReferenceConverterMixin
from api_fhir_r4.converters.medicationConverter import MedicationConverter
Expand Down
13 changes: 7 additions & 6 deletions api_fhir_r4/converters/medicationConverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from api_fhir_r4.configurations import GeneralConfiguration
import core
import re
from uuid import UUID


class MedicationConverter(BaseFHIRConverter, ReferenceConverterMixin):
Expand All @@ -44,7 +45,7 @@ def to_imis_obj(cls, fhir_medication, audit_user_id):
imis_medication = Item()
imis_medication.audit_user_id = audit_user_id
cls.build_imis_identifier(imis_medication, fhir_medication, errors)
cls.build_imis_item_code(imis_medication, fhir_medication, errors)
#cls.build_imis_item_code(imis_medication, fhir_medication, errors)
cls.build_imis_item_name(imis_medication, fhir_medication, errors)
cls.build_imis_item_package(imis_medication, fhir_medication, errors)
cls.build_imis_item_extension(imis_medication, fhir_medication, errors)
Expand Down Expand Up @@ -85,11 +86,11 @@ def build_fhir_identifiers(cls, fhir_medication, imis_medication):

@classmethod
def build_imis_identifier(cls, imis_medication, fhir_medication, errors):
value = cls.get_fhir_identifier_by_code(fhir_medication.identifier,
R4IdentifierConfig.get_fhir_uuid_type_code())
if value:
imis_medication.code = value
cls.valid_condition(imis_medication.code is None, gettext('Missing the item code'), errors)
super().build_imis_identifier(imis_medication, fhir_medication, errors)
cls.valid_condition(
not imis_medication.code,
gettext('Missing medication `item_code` attribute'), errors
)

@classmethod
def build_fhir_package_form(cls, fhir_medication, imis_medication):
Expand Down
100 changes: 93 additions & 7 deletions api_fhir_r4/mixins.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,105 @@
import logging
from abc import abstractmethod, ABC
from collections.abc import Iterable

from typing import List
from rest_framework import status

from django.core.exceptions import ObjectDoesNotExist, FieldError
from django.http import Http404

from rest_framework import mixins

from api_fhir_r4.model_retrievers import GenericModelRetriever
from rest_framework.response import Response

from core.models import HistoryModel
from api_fhir_r4.multiserializer.mixins import MultiSerializerUpdateModelMixin, MultiSerializerRetrieveModelMixin
from rest_framework.mixins import (
CreateModelMixin as RestCreateModelMixin,
UpdateModelMixin as RestUpdateModelMixin,
ListModelMixin as RestListModelMixin,
DestroyModelMixin as RestDestroyModelMixin,
RetrieveModelMixin as RestRetrieveModelMixin,
)

logger = logging.getLogger(__name__)


class CreateModelMixin(RestCreateModelMixin):

def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, user=request.user)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data,
status=status.HTTP_201_CREATED,
headers=headers
)


class UpdateModelMixin(RestUpdateModelMixin):
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial, user=request.user)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)

if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}

return Response(serializer.data)


class ListModelMixin(RestListModelMixin):
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
if not isinstance(queryset, Iterable):
queryset = queryset.all().order_by('code' if hasattr(queryset.model, 'code') else 'id')
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True, user=request.user)
return self.get_paginated_response(serializer.data)

serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

class DestroyModelMixin(RestDestroyModelMixin):
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.user = request.user
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)

def perform_destroy(self, instance):
if issubclass(instance.__class__, HistoryModel):
instance.delete(user=self.user)
else:
instance.delete()


class RetrieveModelMixin(RestRetrieveModelMixin):
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
self.user = request.user
instance = self.get_object()
serializer = self.get_serializer(instance, user=self.user)
return Response(serializer.data)


class GenericMultiIdentifierMixin(ABC):
lookup_field = 'identifier'

Expand Down Expand Up @@ -44,15 +129,15 @@ def _get_object_with_first_valid_retriever(self, identifier):
raise Http404(f"Resource for identifier {identifier} not found")


class MultiIdentifierRetrieverMixin(mixins.RetrieveModelMixin, GenericMultiIdentifierMixin, ABC):
class MultiIdentifierRetrieverMixin(RetrieveModelMixin, GenericMultiIdentifierMixin, ABC):

def retrieve(self, request, *args, **kwargs):
ref_type, instance = self._get_object_with_first_valid_retriever(kwargs['identifier'])
serializer = self.get_serializer(instance, reference_type=ref_type)
serializer = self.get_serializer(instance, reference_type=ref_type, user=request.user)
return Response(serializer.data)


class MultiIdentifierUpdateMixin(mixins.UpdateModelMixin, GenericMultiIdentifierMixin, ABC):
class MultiIdentifierUpdateMixin(UpdateModelMixin, GenericMultiIdentifierMixin, ABC):

def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
Expand Down Expand Up @@ -101,7 +186,7 @@ def update(self, request, *args, **kwargs):
for serializer, (qs, _, _) in self.get_eligible_serializers_iterator():
ref_type, instance = self._get_object_with_first_valid_retriever(qs, kwargs['identifier'])
update_result = self._update_for_serializer(serializer, instance, request.data, partial,
reference_type=ref_type)
reference_type=ref_type, user=request.user)
results.append(update_result)

response = results[0] # By default there should be only one eligible serializer
Expand All @@ -126,3 +211,4 @@ def retrieve(self, request, *args, **kwargs):
raise Http404(f"Resource for identifier {kwargs['identifier']} not found")

return Response(retrieved[0])

26 changes: 14 additions & 12 deletions api_fhir_r4/model_retrievers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

from django.db.models.query import QuerySet
from django.db.models import Model

from insuree.services import validate_insuree_number
from api_fhir_r4.converters import ReferenceConverterMixin

from django.core.exceptions import ValidationError

class GenericModelRetriever(ABC):

Expand Down Expand Up @@ -36,7 +36,16 @@ def retriever_additional_queryset_filtering(cls, queryset):

@classmethod
def get_model_object(cls, queryset: QuerySet, identifier_value) -> Model:
return queryset.get(**{cls.identifier_field: identifier_value})
filters = {}
if cls.serializer_reference_type == 'uuid_reference':
identifier_value = uuid.UUID(str(identifier_value))
elif hasattr(queryset.model, 'validity_to'):
filters['validity_to__isnull'] = True
filters[cls.identifier_field] = identifier_value
try:
return queryset.get(**filters)
except Exception as e:
raise ValidationError(f"failed to retrieve {queryset.model.__name__} with the filter {filters}; details {e}")


class UUIDIdentifierModelRetriever(GenericModelRetriever):
Expand Down Expand Up @@ -73,10 +82,7 @@ class CodeIdentifierModelRetriever(GenericModelRetriever):
def identifier_validator(cls, identifier_value):
return isinstance(identifier_value, str)

@classmethod
def add_retriever_queryset_filtering(cls, queryset):
# By default no additional changes are made in queryset
return queryset.filter(validity_to__is_null=True)



class CHFIdentifierModelRetriever(CodeIdentifierModelRetriever):
Expand All @@ -85,11 +91,7 @@ class CHFIdentifierModelRetriever(CodeIdentifierModelRetriever):
@classmethod
def identifier_validator(cls, identifier_value):
# From model specification
return isinstance(identifier_value, str) and len(identifier_value) <= 12

@classmethod
def get_model_object(cls, queryset: QuerySet, identifier_value) -> Model:
return queryset.get(**{cls.identifier_field: identifier_value, 'validity_to__isnull': True})
return isinstance(identifier_value, str) and validate_insuree_number(identifier_value)


class GroupIdentifierModelRetriever(CHFIdentifierModelRetriever):
Expand Down
6 changes: 3 additions & 3 deletions api_fhir_r4/multiserializer/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,15 +379,15 @@ def update(self, request, *args, **kwargs):
results = []
for serializer, (qs, _, _) in self.get_eligible_serializers_iterator():
instance = self.get_object_by_queryset(qs=qs)
update_result = self._update_for_serializer(serializer, instance, request.data, partial)
update_result = self._update_for_serializer(serializer, instance, request.data, partial, user=request.user)
results.append(update_result)

response = results[0] # By default there should be only one eligible serializer
return Response(response)

def _update_for_serializer(self, serializer, instance, data, partial, *args, **kwargs):
def _update_for_serializer(self, serializer, instance, data, partial, user=None, *args, **kwargs):
context = self.get_serializer_context() # Required for audit user id
serializer = serializer(instance, data=data, partial=partial, context=context, *args, **kwargs)
serializer = serializer(instance, data=data, partial=partial, context=context, user=user, *args, **kwargs)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
Expand Down
9 changes: 9 additions & 0 deletions api_fhir_r4/multiserializer/serializerClass.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ class MultiSerializerSerializerClass(Serializer):
"""
Serves as base serializer class for instances using multiserializer mixin.
"""
user = None

def __init__(self, user=None, **kwargs):
if user:
self.user = user
else:
context = kwargs.get('context', None)
if context and hasattr(context, 'user'):
self.user = context.user

def update(self, instance, validated_data):
raise NotImplementedError("MultiSerializerSerializerClass `update` not supported. Should be"
Expand Down
6 changes: 4 additions & 2 deletions api_fhir_r4/paymentNotice/serializer/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ def create(cls, validated_data, request):
invoice_status = validated_data.pop('invoice_status')

payment_invoice = copy.deepcopy(validated_data)
del payment_invoice['_state']
del payment_invoice['_original_state']
if '_state' in payment_invoice:
del payment_invoice['_state']
if '_original_state' in payment_invoice:
del payment_invoice['_original_state']

payment_invoice_service = PaymentInvoiceService(user)
result = payment_invoice_service.create_with_detail(payment_invoice, imis_payment_detail)
Expand Down
Loading
Loading