From 43dfa9f297cada7f3204f8c061a63b12c4c21658 Mon Sep 17 00:00:00 2001 From: delcroip Date: Tue, 24 Sep 2024 11:05:49 +0200 Subject: [PATCH 1/8] init serializer with user --- api_fhir_r4/mixins.py | 4 ++-- api_fhir_r4/multiserializer/mixins.py | 6 +++--- api_fhir_r4/views/fhir/claim.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api_fhir_r4/mixins.py b/api_fhir_r4/mixins.py index 05924c4..c4f72c6 100644 --- a/api_fhir_r4/mixins.py +++ b/api_fhir_r4/mixins.py @@ -48,7 +48,7 @@ class MultiIdentifierRetrieverMixin(mixins.RetrieveModelMixin, GenericMultiIdent 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) @@ -101,7 +101,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 diff --git a/api_fhir_r4/multiserializer/mixins.py b/api_fhir_r4/multiserializer/mixins.py index b4007df..8ea9467 100644 --- a/api_fhir_r4/multiserializer/mixins.py +++ b/api_fhir_r4/multiserializer/mixins.py @@ -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): diff --git a/api_fhir_r4/views/fhir/claim.py b/api_fhir_r4/views/fhir/claim.py index 8f867c4..4b62c2f 100644 --- a/api_fhir_r4/views/fhir/claim.py +++ b/api_fhir_r4/views/fhir/claim.py @@ -50,7 +50,7 @@ def list(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs): contained = bool(request.GET.get("contained")) ref_type, instance = self._get_object_with_first_valid_retriever(kwargs['identifier']) - serializer = self.get_serializer(instance, context={'contained': contained}, reference_type=ref_type) + serializer = self.get_serializer(instance, context={'contained': contained}, reference_type=ref_type, user=request.user) return Response(serializer.data) def get_queryset(self): From f61d2c9e3aaaecb8b8ecab73e7cc051bf1d34b36 Mon Sep 17 00:00:00 2001 From: delcroip Date: Fri, 27 Sep 2024 16:54:03 +0200 Subject: [PATCH 2/8] add message --- api_fhir_r4/tests/mixin/fhirApiUpdateTestMixin.py | 5 +++-- api_fhir_r4/tests/test_api_authorization.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/api_fhir_r4/tests/mixin/fhirApiUpdateTestMixin.py b/api_fhir_r4/tests/mixin/fhirApiUpdateTestMixin.py index a746a52..502f854 100644 --- a/api_fhir_r4/tests/mixin/fhirApiUpdateTestMixin.py +++ b/api_fhir_r4/tests/mixin/fhirApiUpdateTestMixin.py @@ -1,5 +1,5 @@ import copy - +import json from rest_framework import status @@ -33,7 +33,8 @@ def test_put_should_update_correctly(self): # create self.create_dependencies() response = self.client.post(self.base_url, data=self._test_request_data, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) + content = json.loads(response.content) + self.assertEqual(response.status_code, status.HTTP_201_CREATED, f"{response.content}") resource_id = self.get_id_for_created_resource(response) # update updated_data = copy.deepcopy(self._test_request_data) diff --git a/api_fhir_r4/tests/test_api_authorization.py b/api_fhir_r4/tests/test_api_authorization.py index 47efb4b..6de795e 100644 --- a/api_fhir_r4/tests/test_api_authorization.py +++ b/api_fhir_r4/tests/test_api_authorization.py @@ -34,7 +34,8 @@ def test_post_should_authorize_correctly(self): 'HTTP_AUTHORIZATION': f"Bearer {token}" } response = self.client.get(self.url_to_test_authorization, format='json', **headers) - self.assertEqual(response.status_code, status.HTTP_200_OK) + content = json.loads(response.content) + self.assertEqual(response.status_code, status.HTTP_200_OK, f"{response.content}") def test_post_should_raise_no_auth_header(self): response = self.client.get(self.url_to_test_authorization, format='json') From 0c96de39669ad417b8ce00185199f2949e5d60e7 Mon Sep 17 00:00:00 2001 From: delcroip Date: Mon, 30 Sep 2024 10:21:05 +0200 Subject: [PATCH 3/8] enforce uuid conversion in retriever --- api_fhir_r4/model_retrievers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api_fhir_r4/model_retrievers.py b/api_fhir_r4/model_retrievers.py index 9ca79e5..59403ad 100644 --- a/api_fhir_r4/model_retrievers.py +++ b/api_fhir_r4/model_retrievers.py @@ -36,6 +36,8 @@ def retriever_additional_queryset_filtering(cls, queryset): @classmethod def get_model_object(cls, queryset: QuerySet, identifier_value) -> Model: + if cls.serializer_reference_type == 'uuid_reference': + identifier_value = uuid.UUID(identifier_value) return queryset.get(**{cls.identifier_field: identifier_value}) @@ -50,7 +52,7 @@ def identifier_validator(cls, identifier_value): @classmethod def _is_uuid_identifier(cls, identifier): try: - uuid.UUID(str(identifier)) + cls.identifier_value = uuid.UUID(str(identifier)) return True except ValueError: return False From 62aa1644e43f620494085cca27a672c7d61cce6f Mon Sep 17 00:00:00 2001 From: delcroip Date: Mon, 30 Sep 2024 13:20:54 +0200 Subject: [PATCH 4/8] get rid of technical user --- api_fhir_r4/serializers/__init__.py | 11 +++++------ api_fhir_r4/tests/mixin/genericFhirAPITestMixin.py | 14 +++++--------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/api_fhir_r4/serializers/__init__.py b/api_fhir_r4/serializers/__init__.py index 999e83a..5fed29a 100644 --- a/api_fhir_r4/serializers/__init__.py +++ b/api_fhir_r4/serializers/__init__.py @@ -47,15 +47,14 @@ def update(self, instance, validated_data): raise NotImplementedError('`update()` must be implemented.') # pragma: no cover def get_audit_user_id(self): - request = self.context.get("request") - # Taking the audit_user_id from the query doesn't seem wise but there might be a use for it - # audit_user_id = request.query_params.get('auditUserId', None) - audit_user_id = request.user.id_for_audit if request.user else None - if audit_user_id is None: - audit_user_id = GeneralConfiguration.get_default_audit_user_id() + # the audit user is the user + if self.user: + return self.user.audit_user_id or self.user._u.id + audit_user_id = GeneralConfiguration.get_default_audit_user_id() if isinstance(audit_user_id, int): return audit_user_id else: + raise ValueError("User not available from the request for audit trail") return self.__get_technical_audit_user(audit_user_id) @property diff --git a/api_fhir_r4/tests/mixin/genericFhirAPITestMixin.py b/api_fhir_r4/tests/mixin/genericFhirAPITestMixin.py index 33aca85..511ff4a 100644 --- a/api_fhir_r4/tests/mixin/genericFhirAPITestMixin.py +++ b/api_fhir_r4/tests/mixin/genericFhirAPITestMixin.py @@ -13,9 +13,9 @@ from fhir.resources.R4B.bundle import Bundle from api_fhir_r4.utils import DbManagerUtils - +from core.test_helpers import create_test_interactive_user class GenericFhirAPITestMixin(object): - + user = None @property def base_url(self): return None @@ -28,19 +28,20 @@ def _test_json_path(self): def _test_json_path_credentials(self): return None - _TEST_SUPERUSER_NAME = 'admin' + _TEST_SUPERUSER_NAME = 'admin_api' _TEST_SUPERUSER_PASS = 'adminadmin'#'Admin123' _test_request_data = None _test_json_path_credentials = None def setUp(self): + self.user = create_test_interactive_user(username=self._TEST_SUPERUSER_NAME) dir_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) if self._test_json_path and self._test_request_data is None: json_representation = open(dir_path + self._test_json_path).read() self._test_request_data = json.loads(json_representation) if self._test_json_path_credentials and self._test_request_data_credentials is None: json_representation = open(dir_path + self._test_json_path_credentials).read() - self._test_request_data_credentials = json.loads(json_representation) + self._test_request_data_credentials = json.loads(json_representation) def apply_replace_map(self , payload): return payload @@ -70,13 +71,8 @@ def get_response_details(self, response_json): def login(self): user = DbManagerUtils.get_object_or_none(User, username=self._TEST_SUPERUSER_NAME) - if user is None: - user = self.__create_superuser() self.client.force_authenticate(user=user) - def __create_superuser(self): - User.objects.create_superuser(username=self._TEST_SUPERUSER_NAME, password=self._TEST_SUPERUSER_PASS) - return DbManagerUtils.get_object_or_none(User, username=self._TEST_SUPERUSER_NAME) def get_bundle_from_json_response(self, response): response_json = response.json() From e760979fbc723670ae8ee293f336ef6b984cf85b Mon Sep 17 00:00:00 2001 From: delcroip Date: Mon, 30 Sep 2024 14:48:43 +0200 Subject: [PATCH 5/8] better retrievers --- api_fhir_r4/model_retrievers.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/api_fhir_r4/model_retrievers.py b/api_fhir_r4/model_retrievers.py index 59403ad..04f9f4d 100644 --- a/api_fhir_r4/model_retrievers.py +++ b/api_fhir_r4/model_retrievers.py @@ -4,7 +4,7 @@ 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 @@ -36,9 +36,13 @@ def retriever_additional_queryset_filtering(cls, queryset): @classmethod def get_model_object(cls, queryset: QuerySet, identifier_value) -> Model: + filters = {} if cls.serializer_reference_type == 'uuid_reference': - identifier_value = uuid.UUID(identifier_value) - return queryset.get(**{cls.identifier_field: identifier_value}) + identifier_value = uuid.UUID(str(identifier_value)) + elif hasattr(queryset.model, 'validity_to'): + filters['validity_to__isnull'] = True + filters[cls.identifier_field] = identifier_value + return queryset.get(**filters) class UUIDIdentifierModelRetriever(GenericModelRetriever): @@ -52,7 +56,7 @@ def identifier_validator(cls, identifier_value): @classmethod def _is_uuid_identifier(cls, identifier): try: - cls.identifier_value = uuid.UUID(str(identifier)) + uuid.UUID(str(identifier)) return True except ValueError: return False @@ -75,10 +79,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): @@ -87,11 +88,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): From 694684f304d45119676acffd2fc6c4b419da92db Mon Sep 17 00:00:00 2001 From: delcroip Date: Tue, 1 Oct 2024 08:09:53 +0200 Subject: [PATCH 6/8] more precise errors --- api_fhir_r4/model_retrievers.py | 7 +++++-- api_fhir_r4/views/fhir/organisation.py | 22 +++++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/api_fhir_r4/model_retrievers.py b/api_fhir_r4/model_retrievers.py index 04f9f4d..b7ef684 100644 --- a/api_fhir_r4/model_retrievers.py +++ b/api_fhir_r4/model_retrievers.py @@ -6,7 +6,7 @@ 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): @@ -42,7 +42,10 @@ def get_model_object(cls, queryset: QuerySet, identifier_value) -> Model: elif hasattr(queryset.model, 'validity_to'): filters['validity_to__isnull'] = True filters[cls.identifier_field] = identifier_value - return queryset.get(**filters) + 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): diff --git a/api_fhir_r4/views/fhir/organisation.py b/api_fhir_r4/views/fhir/organisation.py index 0d47c73..fd25885 100644 --- a/api_fhir_r4/views/fhir/organisation.py +++ b/api_fhir_r4/views/fhir/organisation.py @@ -133,13 +133,18 @@ def __dispatch_page_data(self, page): def retrieve(self, request, *args, **kwargs): self._validate_retrieve_model_request() retrieved = [] + errors = [] for serializer, (qs, _, _) in self.get_eligible_serializers_iterator(): if qs.model is not ModuleConfiguration: - ref_type, instance = self._get_object_with_first_valid_retriever(qs, kwargs['identifier']) - if instance: - serializer = serializer(instance, reference_type=ref_type, user=request.user) - if serializer.data: - retrieved.append(serializer.data) + try: + ref_type, instance = self._get_object_with_first_valid_retriever(qs, kwargs['identifier']) + if instance: + serializer = serializer(instance, reference_type=ref_type, user=request.user) + if serializer.data: + retrieved.append(serializer.data) + except Exception as e: + # we will try with another retriever + errors.append(str(e)) else: if qs.count() > 0: data = self._get_insurance_organisation(kwargs.get('identifier', None)) @@ -150,8 +155,11 @@ def retrieve(self, request, *args, **kwargs): if len(retrieved) > 1: raise ValueError("Ambiguous retrieve result, object found for multiple serializers.") - if len(retrieved) == 0: - raise Http404(f"Resource for identifier {kwargs['identifier']} not found") + elif len(retrieved) == 0: + if len(errors) > 0: + raise Http404(f"Resource for identifier {kwargs['identifier']} cannot be reloved:{','.join(errors)}") + else: + raise Http404(f"Resource for identifier {kwargs['identifier']} not found") return Response(retrieved[0]) From 34ae42e395720f7b2b57c02d3e87007de4238fcb Mon Sep 17 00:00:00 2001 From: delcroip Date: Tue, 1 Oct 2024 23:18:06 +0200 Subject: [PATCH 7/8] rework to have user in serializer/converter --- .../containedResourceHandler.py | 2 +- .../containedResources/serializerMixin.py | 4 +- api_fhir_r4/converters/__init__.py | 40 ++++++++--- api_fhir_r4/converters/medicationConverter.py | 13 ++-- api_fhir_r4/mixins.py | 66 +++++++++++++++++-- .../multiserializer/serializerClass.py | 9 +++ .../paymentNotice/serializer/create.py | 6 +- api_fhir_r4/serializers/__init__.py | 15 +++-- .../activityDefinitionSerializer.py | 3 +- .../claimAdminPractitionerSerializer.py | 3 +- .../serializers/codeSystemSerializer.py | 6 +- .../serializers/communicationSerializer.py | 3 +- api_fhir_r4/serializers/contractSerializer.py | 3 +- api_fhir_r4/serializers/coverageSerializer.py | 3 +- api_fhir_r4/serializers/groupSerializer.py | 7 +- .../serializers/insurancePlanSerializer.py | 3 +- api_fhir_r4/serializers/locationSerializer.py | 3 +- .../serializers/locationSiteSerializer.py | 3 +- .../serializers/medicationSerializer.py | 65 ++++++++++-------- api_fhir_r4/serializers/patientSerializer.py | 2 +- .../policyHolderOrganisationSerializer.py | 2 +- .../subscriptions/subscriptionSerializer.py | 10 ++- api_fhir_r4/views/fhir/activity_definition.py | 5 +- api_fhir_r4/views/fhir/base/__init__.py | 12 +++- api_fhir_r4/views/fhir/claim.py | 6 +- api_fhir_r4/views/fhir/claim_response.py | 11 ++-- .../views/fhir/code_systems/diagnosis.py | 1 + .../code_systems/group_confirmation_type.py | 1 + .../views/fhir/code_systems/group_type.py | 1 + .../organization_hf_legal_form.py | 1 + .../code_systems/organization_hf_level.py | 1 + .../code_systems/organization_ph_activity.py | 1 + .../organization_ph_legal_form.py | 1 + .../code_systems/patient_education_level.py | 1 + .../patient_identification_type.py | 1 + .../fhir/code_systems/patient_profession.py | 1 + .../fhir/code_systems/patient_relationship.py | 1 + api_fhir_r4/views/fhir/communication.py | 7 +- .../views/fhir/communication_request.py | 10 ++- api_fhir_r4/views/fhir/contract.py | 11 ++-- .../fhir/coverage_eligibility_request.py | 3 +- api_fhir_r4/views/fhir/coverage_request.py | 7 +- api_fhir_r4/views/fhir/invoice.py | 3 +- api_fhir_r4/views/fhir/payment_notice.py | 8 ++- api_fhir_r4/views/fhir/subscription.py | 4 +- 45 files changed, 267 insertions(+), 102 deletions(-) diff --git a/api_fhir_r4/containedResources/containedResourceHandler.py b/api_fhir_r4/containedResources/containedResourceHandler.py index 67d89b8..375f193 100644 --- a/api_fhir_r4/containedResources/containedResourceHandler.py +++ b/api_fhir_r4/containedResources/containedResourceHandler.py @@ -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('_')} diff --git a/api_fhir_r4/containedResources/serializerMixin.py b/api_fhir_r4/containedResources/serializerMixin.py index e154ae3..940d4bf 100644 --- a/api_fhir_r4/containedResources/serializerMixin.py +++ b/api_fhir_r4/containedResources/serializerMixin.py @@ -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) diff --git a/api_fhir_r4/converters/__init__.py b/api_fhir_r4/converters/__init__.py index d3b45a5..5366afc 100644 --- a/api_fhir_r4/converters/__init__.py +++ b/api_fhir_r4/converters/__init__.py @@ -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): @@ -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): @@ -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 diff --git a/api_fhir_r4/converters/medicationConverter.py b/api_fhir_r4/converters/medicationConverter.py index 1232de0..9e9c3c9 100644 --- a/api_fhir_r4/converters/medicationConverter.py +++ b/api_fhir_r4/converters/medicationConverter.py @@ -18,6 +18,7 @@ from api_fhir_r4.configurations import GeneralConfiguration import core import re +from uuid import UUID class MedicationConverter(BaseFHIRConverter, ReferenceConverterMixin): @@ -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) @@ -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): diff --git a/api_fhir_r4/mixins.py b/api_fhir_r4/mixins.py index c4f72c6..ea1ab73 100644 --- a/api_fhir_r4/mixins.py +++ b/api_fhir_r4/mixins.py @@ -1,20 +1,77 @@ 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 rest_framework.mixins import RetrieveModelMixin from api_fhir_r4.model_retrievers import GenericModelRetriever from rest_framework.response import Response from api_fhir_r4.multiserializer.mixins import MultiSerializerUpdateModelMixin, MultiSerializerRetrieveModelMixin +from rest_framework.mixins import ( + CreateModelMixin as RestCreateModelMixin, + UpdateModelMixin as RestUpdateModelMixin, + ListModelMixin as RestListModelMixin +) 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 GenericMultiIdentifierMixin(ABC): lookup_field = 'identifier' @@ -44,7 +101,7 @@ 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']) @@ -52,7 +109,7 @@ def retrieve(self, request, *args, **kwargs): 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) @@ -126,3 +183,4 @@ def retrieve(self, request, *args, **kwargs): raise Http404(f"Resource for identifier {kwargs['identifier']} not found") return Response(retrieved[0]) + diff --git a/api_fhir_r4/multiserializer/serializerClass.py b/api_fhir_r4/multiserializer/serializerClass.py index 59f937e..854f04d 100644 --- a/api_fhir_r4/multiserializer/serializerClass.py +++ b/api_fhir_r4/multiserializer/serializerClass.py @@ -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" diff --git a/api_fhir_r4/paymentNotice/serializer/create.py b/api_fhir_r4/paymentNotice/serializer/create.py index 0ad2a82..eb66ba2 100644 --- a/api_fhir_r4/paymentNotice/serializer/create.py +++ b/api_fhir_r4/paymentNotice/serializer/create.py @@ -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) diff --git a/api_fhir_r4/serializers/__init__.py b/api_fhir_r4/serializers/__init__.py index 5fed29a..52ca08c 100644 --- a/api_fhir_r4/serializers/__init__.py +++ b/api_fhir_r4/serializers/__init__.py @@ -18,9 +18,14 @@ class BaseFHIRSerializer(serializers.Serializer): fhirConverter = BaseFHIRConverter user = None - def __init__(self, *args, **kwargs): + def __init__(self, *args, user=None, **kwargs): self._reference_type = kwargs.pop('reference_type', ReferenceConverterMixin.UUID_REFERENCE_TYPE) - self.user = kwargs.pop('user', None) + if user: + self.user = user + else: + context = kwargs.get('context', None) + if context and hasattr(context, 'user'): + self.user = context.user super().__init__(*args, **kwargs) def to_representation(self, obj): @@ -38,7 +43,9 @@ def to_representation(self, obj): def to_internal_value(self, data): audit_user_id = self.get_audit_user_id() - return self.fhirConverter(user=self.user).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): raise NotImplementedError('`create()` must be implemented.') # pragma: no cover @@ -55,7 +62,7 @@ def get_audit_user_id(self): return audit_user_id else: raise ValueError("User not available from the request for audit trail") - return self.__get_technical_audit_user(audit_user_id) + #return self.__get_technical_audit_user(audit_user_id) @property def reference_type(self): diff --git a/api_fhir_r4/serializers/activityDefinitionSerializer.py b/api_fhir_r4/serializers/activityDefinitionSerializer.py index 617c9bc..33acdb6 100644 --- a/api_fhir_r4/serializers/activityDefinitionSerializer.py +++ b/api_fhir_r4/serializers/activityDefinitionSerializer.py @@ -19,7 +19,8 @@ def create(self, validated_data): validated_data['uuid'] = uuid.UUID(validated_data['uuid']) copied_data = copy.deepcopy(validated_data) - del copied_data['_state'] + if '_state' in copied_data: + del copied_data['_state'] return Service.objects.create(**copied_data) def update(self, instance, validated_data): diff --git a/api_fhir_r4/serializers/claimAdminPractitionerSerializer.py b/api_fhir_r4/serializers/claimAdminPractitionerSerializer.py index 01e4063..c658464 100644 --- a/api_fhir_r4/serializers/claimAdminPractitionerSerializer.py +++ b/api_fhir_r4/serializers/claimAdminPractitionerSerializer.py @@ -23,7 +23,8 @@ def create(self, validated_data): if ClaimAdmin.objects.filter(code=code).count() > 0: raise FHIRException('Exists practitioner with following code `{}`'.format(code)) copied_data = copy.deepcopy(validated_data) - del copied_data['_state'] + if '_state' in copied_data: + del copied_data['_state'] return ClaimAdmin.objects.create(**copied_data) def update(self, instance, validated_data): diff --git a/api_fhir_r4/serializers/codeSystemSerializer.py b/api_fhir_r4/serializers/codeSystemSerializer.py index 5e7565b..4a10c92 100644 --- a/api_fhir_r4/serializers/codeSystemSerializer.py +++ b/api_fhir_r4/serializers/codeSystemSerializer.py @@ -9,10 +9,10 @@ class CodeSystemSerializer(BaseFHIRSerializer): def __init__(self, *args, **kwargs): self.model = {} - + if 'user' in kwargs: + user = kwargs.pop('user') for field in self.codeSystemFields: self.model[field] = kwargs.pop(field, None) - if 'data' in kwargs: self.model['data'] = kwargs.pop('data') elif 'model_name' in kwargs: @@ -22,7 +22,7 @@ def __init__(self, *args, **kwargs): else: self.model['data'] = {} - super().__init__(*args, **kwargs) + super().__init__(*args, user=user, **kwargs) def to_representation(self, obj): return CodeSystemConverter.to_fhir_obj(self.model, self.reference_type).dict() diff --git a/api_fhir_r4/serializers/communicationSerializer.py b/api_fhir_r4/serializers/communicationSerializer.py index 673b5cf..3750a3d 100644 --- a/api_fhir_r4/serializers/communicationSerializer.py +++ b/api_fhir_r4/serializers/communicationSerializer.py @@ -16,7 +16,8 @@ def create(self, validated_data): if Feedback.objects.filter(claim__id=claim, validity_to__isnull=True).count() > 0: raise FHIRException('Feedback exists for this claim') copied_data = copy.deepcopy(validated_data) - del copied_data['_state'] + if '_state' in copied_data: + del copied_data['_state'] from core import datetime copied_data['feedback_date'] = datetime.datetime.now() obj = Feedback.objects.create(**copied_data) diff --git a/api_fhir_r4/serializers/contractSerializer.py b/api_fhir_r4/serializers/contractSerializer.py index 2328e64..6ff2c4f 100644 --- a/api_fhir_r4/serializers/contractSerializer.py +++ b/api_fhir_r4/serializers/contractSerializer.py @@ -22,7 +22,8 @@ def create(self, validated_data): raise FHIRException('Contract exists for this patient') copied_data = copy.deepcopy(validated_data) - del copied_data['_state'] + if '_state' in copied_data: + del copied_data['_state'] #TODO should we implement a way to create a resource with a given uuid del copied_data['uuid'] diff --git a/api_fhir_r4/serializers/coverageSerializer.py b/api_fhir_r4/serializers/coverageSerializer.py index 6c35bbc..9fb3164 100644 --- a/api_fhir_r4/serializers/coverageSerializer.py +++ b/api_fhir_r4/serializers/coverageSerializer.py @@ -14,7 +14,8 @@ def create(self, validated_data): if Policy.objects.filter(family_id=family).count() > 0: raise FHIRException('Exists coverage with the family provided') copied_data = copy.deepcopy(validated_data) - del copied_data['_state'] + if '_state' in copied_data: + del copied_data['_state'] return Policy.objects.create(**copied_data) def update(self, instance, validated_data): diff --git a/api_fhir_r4/serializers/groupSerializer.py b/api_fhir_r4/serializers/groupSerializer.py index fc7e00a..527be14 100644 --- a/api_fhir_r4/serializers/groupSerializer.py +++ b/api_fhir_r4/serializers/groupSerializer.py @@ -33,7 +33,8 @@ def create(self, validated_data): # assign members of family (insuree) to the family for mf in members_family: mf = mf.__dict__ - del mf['_state'] + if '_state' in mf: + del mf['_state'] mf['family_id'] = new_family.id InsureeService(user).create_or_update(mf) @@ -43,7 +44,7 @@ def update(self, instance, validated_data): #validated_data = resolve_id_reference(validated_data) # TODO: This doesn't work request = self.context.get("request") - validated_data.pop('_state') + validated_data.pop('_state', None) members_family = validated_data.pop('members_family') user = request.user head_id = validated_data.get('head_insuree_id', None) @@ -64,7 +65,7 @@ def update(self, instance, validated_data): instance = FamilyService(user).create_or_update(validated_data) for mf in members_family: mf = mf.__dict__ - mf.pop('_state') + mf.pop('_state', None) mf['family_id'] = instance.id InsureeService(user).create_or_update(mf) diff --git a/api_fhir_r4/serializers/insurancePlanSerializer.py b/api_fhir_r4/serializers/insurancePlanSerializer.py index d919297..b7e01ef 100644 --- a/api_fhir_r4/serializers/insurancePlanSerializer.py +++ b/api_fhir_r4/serializers/insurancePlanSerializer.py @@ -14,7 +14,8 @@ def create(self, validated_data): if Product.objects.filter(code=code).count() > 0: raise FHIRException('Exists product with following code `{}`'.format(code)) copied_data = copy.deepcopy(validated_data) - del copied_data['_state'] + if '_state' in copied_data: + del copied_data['_state'] # TODO services in product hasn't been developed yet. return Product.objects.create(**copied_data) diff --git a/api_fhir_r4/serializers/locationSerializer.py b/api_fhir_r4/serializers/locationSerializer.py index 4a01309..5d0cc8b 100644 --- a/api_fhir_r4/serializers/locationSerializer.py +++ b/api_fhir_r4/serializers/locationSerializer.py @@ -11,7 +11,8 @@ class LocationSerializer(BaseFHIRSerializer): def create(self, validated_data): copied_data = copy.deepcopy(validated_data) - del copied_data['_state'] + if '_state' in copied_data: + del copied_data['_state'] return Location.objects.create(**copied_data) def update(self, instance, validated_data): diff --git a/api_fhir_r4/serializers/locationSiteSerializer.py b/api_fhir_r4/serializers/locationSiteSerializer.py index 9e60a62..f7bdd0e 100644 --- a/api_fhir_r4/serializers/locationSiteSerializer.py +++ b/api_fhir_r4/serializers/locationSiteSerializer.py @@ -11,7 +11,8 @@ class LocationSiteSerializer(BaseFHIRSerializer): def create(self, validated_data): copied_data = copy.deepcopy(validated_data) - del copied_data['_state'] + if '_state' in copied_data: + del copied_data['_state'] return HealthFacility.objects.create(**copied_data) def update(self, instance, validated_data): diff --git a/api_fhir_r4/serializers/medicationSerializer.py b/api_fhir_r4/serializers/medicationSerializer.py index 3e6ea01..4401e9d 100644 --- a/api_fhir_r4/serializers/medicationSerializer.py +++ b/api_fhir_r4/serializers/medicationSerializer.py @@ -2,40 +2,53 @@ import uuid from medical.models import Item + from api_fhir_r4.converters import MedicationConverter from api_fhir_r4.exceptions import FHIRException from api_fhir_r4.serializers import BaseFHIRSerializer - class MedicationSerializer(BaseFHIRSerializer): fhirConverter = MedicationConverter def create(self, validated_data): - code = validated_data.get('code') - if Item.objects.filter(code=code).count() > 0: - raise FHIRException('Exists medical item with following code `{}`'.format(code)) - - if 'uuid' in validated_data.keys() and validated_data.get('uuid') is None: - # In serializers using graphql services can't provide uuid. If uuid is provided then - # resource is updated and not created. This check ensure UUID was provided. - validated_data['uuid'] = uuid.uuid4() - elif 'uuid' in validated_data and isinstance(validated_data['uuid'], str): - validated_data['uuid'] = uuid.UUID(validated_data['uuid']) - copied_data = copy.deepcopy(validated_data) - del copied_data['_state'] - return Item.objects.create(**copied_data) + imis_medication = Item(**validated_data) + imis_medication.audit_user_id = self.get_audit_user_id() + filters = {} + if imis_medication.id: + filters['id'] = imis_medication.id + if imis_medication.uuid: + filters['uuid'] = imis_medication.uuid + if imis_medication.code and not filters: + filters['code'] = imis_medication.code + filters['validity_to__isnull'] = True + + instance = Item.objects.filter(**filters).first() + if instance: + raise ValueError(f"cannot create an already existing object, filters: {filters}") + imis_medication.audit_user_id = self.get_audit_user_id() + imis_medication.save() + return imis_medication + def update(self, instance, validated_data): - if 'uuid' in validated_data and isinstance(validated_data['uuid'], str): - validated_data['uuid'] = uuid.UUID(validated_data['uuid']) - instance.code = validated_data.get('code', instance.code) - instance.name = validated_data.get('name', instance.name) - instance.package = validated_data.get('package', instance.package) - instance.price = validated_data.get('price', instance.price) - instance.type = validated_data.get('type', instance.type) - instance.care_type = validated_data.get('care_type', instance.care_type) - instance.frequency = validated_data.get('frequency', instance.frequency) - instance.patient_category = validated_data.get('patient_category', instance.patient_category) + imis_medication = Item(**validated_data) + imis_medication.audit_user_id = self.get_audit_user_id() + filters = {} + if imis_medication.id: + filters['id'] = imis_medication.id + if imis_medication.uuid: + filters['uuid'] = imis_medication.uuid + if imis_medication.code and not filters: + filters['code'] = imis_medication.code + filters['validity_to__isnull'] = True + + instance = Item.objects.filter(**filters).first() + if not instance: + raise ValueError(f"cannot update a not found object, filters: {filters}") instance.audit_user_id = self.get_audit_user_id() - instance.save() - return instance + instance.save_history() + imis_medication.id = instance.id + imis_medication.uuid = instance.uuid + imis_medication.code = instance.code + imis_medication.save() + return imis_medication \ No newline at end of file diff --git a/api_fhir_r4/serializers/patientSerializer.py b/api_fhir_r4/serializers/patientSerializer.py index 811e424..0d0dbc4 100644 --- a/api_fhir_r4/serializers/patientSerializer.py +++ b/api_fhir_r4/serializers/patientSerializer.py @@ -28,7 +28,7 @@ def create(self, validated_data): def update(self, instance, validated_data): #validated_data = resolve_id_reference(Insuree, validated_data) request = self.context.get("request") - validated_data.pop('_state') + validated_data.pop('_state', None) user = request.user chf_id = validated_data.get('chf_id', None) if Insuree.objects.filter(chf_id=chf_id).count() == 0: diff --git a/api_fhir_r4/serializers/policyHolderOrganisationSerializer.py b/api_fhir_r4/serializers/policyHolderOrganisationSerializer.py index 0302922..b001429 100644 --- a/api_fhir_r4/serializers/policyHolderOrganisationSerializer.py +++ b/api_fhir_r4/serializers/policyHolderOrganisationSerializer.py @@ -12,7 +12,7 @@ def create(self, validated_data): if PolicyHolder.objects.filter(code=validated_data['code']).count() > 0: raise FHIRException('Exists Organization with following code `{}`'.format(validated_data['code'])) validated_data.pop('_original_state') - validated_data.pop('_state') + validated_data.pop('_state', None) request = self.context.get('request', None) if request: validated_data['user_created_id']=request.user.id diff --git a/api_fhir_r4/subscriptions/subscriptionSerializer.py b/api_fhir_r4/subscriptions/subscriptionSerializer.py index b15e8fc..2b5a80a 100644 --- a/api_fhir_r4/subscriptions/subscriptionSerializer.py +++ b/api_fhir_r4/subscriptions/subscriptionSerializer.py @@ -28,7 +28,10 @@ def create(self, validated_data): self.check_resource_rights(user, validated_data) service = SubscriptionService(user) copied_data = deepcopy(validated_data) - del copied_data['_state'], copied_data['_original_state'] + if '_state' in copied_data: + del copied_data['_state'] + if '_original_state' in copied_data: + del copied_data['_original_state'] result = service.create(copied_data) return self.get_result_object(result) @@ -40,7 +43,10 @@ def update(self, instance, validated_data): service = SubscriptionService(user) copied_data = {key: value for key, value in deepcopy(validated_data).items() if value is not None} copied_data['id'] = instance.id - del copied_data['_state'], copied_data['_original_state'] + if '_state' in copied_data: + del copied_data['_state'] + if '_original_state' in copied_data: + del copied_data['_original_state'] result = service.update(copied_data) return self.get_result_object(result) diff --git a/api_fhir_r4/views/fhir/activity_definition.py b/api_fhir_r4/views/fhir/activity_definition.py index 4020185..9a044a2 100644 --- a/api_fhir_r4/views/fhir/activity_definition.py +++ b/api_fhir_r4/views/fhir/activity_definition.py @@ -1,7 +1,6 @@ -from rest_framework import mixins from rest_framework.viewsets import GenericViewSet -from api_fhir_r4.mixins import MultiIdentifierRetrieverMixin +from api_fhir_r4.mixins import MultiIdentifierRetrieverMixin, ListModelMixin from api_fhir_r4.model_retrievers import UUIDIdentifierModelRetriever, CodeIdentifierModelRetriever from api_fhir_r4.permissions import FHIRApiActivityDefinitionPermissions from api_fhir_r4.serializers import ActivityDefinitionSerializer @@ -10,7 +9,7 @@ from medical.models import Service -class ActivityDefinitionViewSet(BaseFHIRView, MultiIdentifierRetrieverMixin, mixins.ListModelMixin, GenericViewSet): +class ActivityDefinitionViewSet(BaseFHIRView, MultiIdentifierRetrieverMixin, ListModelMixin, GenericViewSet): retrievers = [UUIDIdentifierModelRetriever, CodeIdentifierModelRetriever] serializer_class = ActivityDefinitionSerializer permission_classes = (FHIRApiActivityDefinitionPermissions,) diff --git a/api_fhir_r4/views/fhir/base/__init__.py b/api_fhir_r4/views/fhir/base/__init__.py index 90a677f..0171052 100644 --- a/api_fhir_r4/views/fhir/base/__init__.py +++ b/api_fhir_r4/views/fhir/base/__init__.py @@ -4,9 +4,19 @@ from api_fhir_r4.paginations import FhirBundleResultsSetPagination from api_fhir_r4.permissions import FHIRApiPermissions from api_fhir_r4.views import CsrfExemptSessionAuthentication +from api_fhir_r4.mixins import ( + UpdateModelMixin, + ListModelMixin, + CreateModelMixin +) -class BaseFHIRView(APIView): +class BaseFHIRView( + CreateModelMixin, + UpdateModelMixin, + APIView, + +): pagination_class = FhirBundleResultsSetPagination permission_classes = (FHIRApiPermissions,) authentication_classes = [CsrfExemptSessionAuthentication] + APIView.settings.DEFAULT_AUTHENTICATION_CLASSES diff --git a/api_fhir_r4/views/fhir/claim.py b/api_fhir_r4/views/fhir/claim.py index 4b62c2f..ea9977f 100644 --- a/api_fhir_r4/views/fhir/claim.py +++ b/api_fhir_r4/views/fhir/claim.py @@ -1,12 +1,11 @@ import datetime from django.db.models import Prefetch -from rest_framework import mixins from rest_framework.response import Response from rest_framework.serializers import ValidationError from rest_framework.viewsets import GenericViewSet -from api_fhir_r4.mixins import MultiIdentifierRetrieverMixin +from api_fhir_r4.mixins import MultiIdentifierRetrieverMixin, ListModelMixin from api_fhir_r4.model_retrievers import UUIDIdentifierModelRetriever, CodeIdentifierModelRetriever from api_fhir_r4.permissions import FHIRApiClaimPermissions from api_fhir_r4.serializers import ClaimSerializer @@ -16,8 +15,7 @@ from insuree.models import Insuree, InsureePolicy -class ClaimViewSet(BaseFHIRView, MultiIdentifierRetrieverMixin, mixins.ListModelMixin, - mixins.CreateModelMixin, GenericViewSet): +class ClaimViewSet(BaseFHIRView, MultiIdentifierRetrieverMixin, ListModelMixin, GenericViewSet): retrievers = [UUIDIdentifierModelRetriever, CodeIdentifierModelRetriever] serializer_class = ClaimSerializer permission_classes = (FHIRApiClaimPermissions,) diff --git a/api_fhir_r4/views/fhir/claim_response.py b/api_fhir_r4/views/fhir/claim_response.py index c84416f..5342b04 100644 --- a/api_fhir_r4/views/fhir/claim_response.py +++ b/api_fhir_r4/views/fhir/claim_response.py @@ -1,7 +1,6 @@ -from rest_framework import mixins from rest_framework.viewsets import GenericViewSet -from api_fhir_r4.mixins import MultiIdentifierRetrieverMixin +from api_fhir_r4.mixins import MultiIdentifierRetrieverMixin, ListModelMixin from api_fhir_r4.model_retrievers import UUIDIdentifierModelRetriever, CodeIdentifierModelRetriever from api_fhir_r4.permissions import FHIRApiClaimPermissions from api_fhir_r4.serializers import ClaimResponseSerializer @@ -10,8 +9,12 @@ from claim.models import Claim -class ClaimResponseViewSet(BaseFHIRView, MultiIdentifierRetrieverMixin, mixins.ListModelMixin, GenericViewSet, - mixins.UpdateModelMixin): +class ClaimResponseViewSet( + BaseFHIRView, + ListModelMixin, + MultiIdentifierRetrieverMixin, + GenericViewSet +): retrievers = [UUIDIdentifierModelRetriever, CodeIdentifierModelRetriever] serializer_class = ClaimResponseSerializer permission_classes = (FHIRApiClaimPermissions,) diff --git a/api_fhir_r4/views/fhir/code_systems/diagnosis.py b/api_fhir_r4/views/fhir/code_systems/diagnosis.py index f08476b..2634fed 100644 --- a/api_fhir_r4/views/fhir/code_systems/diagnosis.py +++ b/api_fhir_r4/views/fhir/code_systems/diagnosis.py @@ -21,6 +21,7 @@ def list(self, request, *args, **kwargs): if not request.user.has_perms(FHIRApiClaimPermissions.permissions_get): raise PermissionDenied("unauthorized") serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ "model_name": 'Diagnosis', diff --git a/api_fhir_r4/views/fhir/code_systems/group_confirmation_type.py b/api_fhir_r4/views/fhir/code_systems/group_confirmation_type.py index 33fc9f0..ff1fdab 100644 --- a/api_fhir_r4/views/fhir/code_systems/group_confirmation_type.py +++ b/api_fhir_r4/views/fhir/code_systems/group_confirmation_type.py @@ -21,6 +21,7 @@ def list(self, request): if not request.user.has_perms(FHIRApiGroupPermissions.permissions_get): raise PermissionDenied("unauthorized") serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ "model_name": 'ConfirmationType', diff --git a/api_fhir_r4/views/fhir/code_systems/group_type.py b/api_fhir_r4/views/fhir/code_systems/group_type.py index 408a191..e60e78d 100644 --- a/api_fhir_r4/views/fhir/code_systems/group_type.py +++ b/api_fhir_r4/views/fhir/code_systems/group_type.py @@ -21,6 +21,7 @@ def list(self, request): if not request.user.has_perms(FHIRApiGroupPermissions.permissions_get): raise PermissionDenied("unauthorized") serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ "model_name": 'FamilyType', diff --git a/api_fhir_r4/views/fhir/code_systems/organization_hf_legal_form.py b/api_fhir_r4/views/fhir/code_systems/organization_hf_legal_form.py index b20dc37..6cd2e42 100644 --- a/api_fhir_r4/views/fhir/code_systems/organization_hf_legal_form.py +++ b/api_fhir_r4/views/fhir/code_systems/organization_hf_legal_form.py @@ -16,6 +16,7 @@ class CodeSystemOrganizationHFLegalFormViewSet(viewsets.ViewSet): def list(self, request): # we don't use typical instance, we only indicate the model and the field to be mapped into CodeSystem serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ 'model_name': 'HealthFacilityLegalForm', diff --git a/api_fhir_r4/views/fhir/code_systems/organization_hf_level.py b/api_fhir_r4/views/fhir/code_systems/organization_hf_level.py index 08639d9..ead9bc2 100644 --- a/api_fhir_r4/views/fhir/code_systems/organization_hf_level.py +++ b/api_fhir_r4/views/fhir/code_systems/organization_hf_level.py @@ -17,6 +17,7 @@ class CodeSystemOrganizationHFLevelViewSet(viewsets.ViewSet): def list(self, request): # we don't use typical instance, we only indicate the model and the field to be mapped into CodeSystem serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ 'data': HealthFacilityLevel(request.user).get_all()['data'], diff --git a/api_fhir_r4/views/fhir/code_systems/organization_ph_activity.py b/api_fhir_r4/views/fhir/code_systems/organization_ph_activity.py index c72ef4c..5ffe542 100644 --- a/api_fhir_r4/views/fhir/code_systems/organization_ph_activity.py +++ b/api_fhir_r4/views/fhir/code_systems/organization_ph_activity.py @@ -17,6 +17,7 @@ class CodeSystemOrganizationPHActivityViewSet(viewsets.ViewSet): def list(self, request): # we don't use typical instance, we only indicate the model and the field to be mapped into CodeSystem serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ 'data': PolicyHolderActivity(request.user).get_all()['data'], diff --git a/api_fhir_r4/views/fhir/code_systems/organization_ph_legal_form.py b/api_fhir_r4/views/fhir/code_systems/organization_ph_legal_form.py index 1cf0e88..bb848ba 100644 --- a/api_fhir_r4/views/fhir/code_systems/organization_ph_legal_form.py +++ b/api_fhir_r4/views/fhir/code_systems/organization_ph_legal_form.py @@ -17,6 +17,7 @@ class CodeSystemOrganizationPHLegalFormViewSet(viewsets.ViewSet): def list(self, request): # we don't use typical instance, we only indicate the model and the field to be mapped into CodeSystem serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ 'data': PolicyHolderLegalForm(request.user).get_all()['data'], diff --git a/api_fhir_r4/views/fhir/code_systems/patient_education_level.py b/api_fhir_r4/views/fhir/code_systems/patient_education_level.py index b3a4e49..46e522e 100644 --- a/api_fhir_r4/views/fhir/code_systems/patient_education_level.py +++ b/api_fhir_r4/views/fhir/code_systems/patient_education_level.py @@ -21,6 +21,7 @@ def list(self, request): if not request.user.has_perms(FHIRApiInsureePermissions.permissions_get): raise PermissionDenied("unauthorized") serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ "model_name": 'Education', diff --git a/api_fhir_r4/views/fhir/code_systems/patient_identification_type.py b/api_fhir_r4/views/fhir/code_systems/patient_identification_type.py index e7f2854..f834b6a 100644 --- a/api_fhir_r4/views/fhir/code_systems/patient_identification_type.py +++ b/api_fhir_r4/views/fhir/code_systems/patient_identification_type.py @@ -21,6 +21,7 @@ def list(self, request): if not request.user.has_perms(FHIRApiInsureePermissions.permissions_get): raise PermissionDenied("unauthorized") serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ "model_name": 'IdentificationType', diff --git a/api_fhir_r4/views/fhir/code_systems/patient_profession.py b/api_fhir_r4/views/fhir/code_systems/patient_profession.py index 923a2ad..dee4da5 100644 --- a/api_fhir_r4/views/fhir/code_systems/patient_profession.py +++ b/api_fhir_r4/views/fhir/code_systems/patient_profession.py @@ -21,6 +21,7 @@ def list(self, request): if not request.user.has_perms(FHIRApiInsureePermissions.permissions_get): raise PermissionDenied("unauthorized") serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ "model_name": 'Profession', diff --git a/api_fhir_r4/views/fhir/code_systems/patient_relationship.py b/api_fhir_r4/views/fhir/code_systems/patient_relationship.py index bc3117d..1d6b438 100644 --- a/api_fhir_r4/views/fhir/code_systems/patient_relationship.py +++ b/api_fhir_r4/views/fhir/code_systems/patient_relationship.py @@ -21,6 +21,7 @@ def list(self, request): if not request.user.has_perms(FHIRApiInsureePermissions.permissions_get): raise PermissionDenied("unauthorized") serializer = CodeSystemSerializer( + user=request.user, instance=None, **{ "model_name": 'Relation', diff --git a/api_fhir_r4/views/fhir/communication.py b/api_fhir_r4/views/fhir/communication.py index 8b99b92..30f410b 100644 --- a/api_fhir_r4/views/fhir/communication.py +++ b/api_fhir_r4/views/fhir/communication.py @@ -11,7 +11,12 @@ import logging logger = logging.getLogger(__name__) -class CommunicationViewSet(BaseFHIRView, MultiIdentifierRetrieverMixin, viewsets.ModelViewSet): + +class CommunicationViewSet( + BaseFHIRView, + MultiIdentifierRetrieverMixin, + viewsets.ModelViewSet +): retrievers = [UUIDIdentifierModelRetriever, CodeIdentifierModelRetriever] serializer_class = CommunicationSerializer permission_classes = (FHIRApiCommunicationRequestPermissions,) diff --git a/api_fhir_r4/views/fhir/communication_request.py b/api_fhir_r4/views/fhir/communication_request.py index c3d1c56..6451ecb 100644 --- a/api_fhir_r4/views/fhir/communication_request.py +++ b/api_fhir_r4/views/fhir/communication_request.py @@ -1,7 +1,6 @@ -from rest_framework import mixins from rest_framework.viewsets import GenericViewSet -from api_fhir_r4.mixins import MultiIdentifierRetrieverMixin +from api_fhir_r4.mixins import MultiIdentifierRetrieverMixin, ListModelMixin from api_fhir_r4.model_retrievers import UUIDIdentifierModelRetriever from api_fhir_r4.permissions import FHIRApiCommunicationRequestPermissions from api_fhir_r4.serializers import CommunicationRequestSerializer @@ -10,7 +9,12 @@ from claim.models import Claim -class CommunicationRequestViewSet(BaseFHIRView, MultiIdentifierRetrieverMixin, mixins.ListModelMixin, GenericViewSet): +class CommunicationRequestViewSet( + BaseFHIRView, + MultiIdentifierRetrieverMixin, + ListModelMixin, + GenericViewSet +): retrievers = [UUIDIdentifierModelRetriever] serializer_class = CommunicationRequestSerializer permission_classes = (FHIRApiCommunicationRequestPermissions,) diff --git a/api_fhir_r4/views/fhir/contract.py b/api_fhir_r4/views/fhir/contract.py index 5f7c15c..c1e42fa 100644 --- a/api_fhir_r4/views/fhir/contract.py +++ b/api_fhir_r4/views/fhir/contract.py @@ -1,9 +1,8 @@ import datetime from django.db.models import Prefetch -from rest_framework import mixins +from rest_framework.mixins import RetrieveModelMixin, ListModelMixin from rest_framework.viewsets import GenericViewSet - from api_fhir_r4.permissions import FHIRApiCoverageRequestPermissions from api_fhir_r4.serializers import ContractSerializer from api_fhir_r4.views.fhir.base import BaseFHIRView @@ -12,8 +11,12 @@ from policy.models import Policy -class ContractViewSet(BaseFHIRView, mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, - GenericViewSet): +class ContractViewSet( + BaseFHIRView, + RetrieveModelMixin, + ListModelMixin, + GenericViewSet +): lookup_field = 'uuid' serializer_class = ContractSerializer permission_classes = (FHIRApiCoverageRequestPermissions,) diff --git a/api_fhir_r4/views/fhir/coverage_eligibility_request.py b/api_fhir_r4/views/fhir/coverage_eligibility_request.py index d3d6e43..b64aba8 100644 --- a/api_fhir_r4/views/fhir/coverage_eligibility_request.py +++ b/api_fhir_r4/views/fhir/coverage_eligibility_request.py @@ -1,4 +1,3 @@ -from rest_framework import mixins from rest_framework.viewsets import GenericViewSet from api_fhir_r4.permissions import FHIRApiCoverageEligibilityRequestPermissions @@ -8,7 +7,7 @@ from insuree.models import Insuree -class CoverageEligibilityRequestViewSet(BaseFHIRView, mixins.CreateModelMixin, GenericViewSet): +class CoverageEligibilityRequestViewSet(BaseFHIRView, GenericViewSet): queryset = Insuree.filter_queryset() serializer_class = CoverageEligibilityRequestSerializer permission_classes = (FHIRApiCoverageEligibilityRequestPermissions,) diff --git a/api_fhir_r4/views/fhir/coverage_request.py b/api_fhir_r4/views/fhir/coverage_request.py index 1cd6deb..0daa3cf 100644 --- a/api_fhir_r4/views/fhir/coverage_request.py +++ b/api_fhir_r4/views/fhir/coverage_request.py @@ -1,8 +1,8 @@ import datetime -from rest_framework import mixins +from rest_framework.mixins import RetrieveModelMixin from rest_framework.viewsets import GenericViewSet - +from api_fhir_r4.mixins import ListModelMixin from api_fhir_r4.permissions import FHIRApiCoverageRequestPermissions from api_fhir_r4.serializers.coverageSerializer import CoverageSerializer from api_fhir_r4.views.fhir.base import BaseFHIRView @@ -10,8 +10,7 @@ from policy.models import Policy -class CoverageRequestQuerySet(BaseFHIRView, mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.UpdateModelMixin, - mixins.CreateModelMixin, GenericViewSet): +class CoverageRequestQuerySet(BaseFHIRView, RetrieveModelMixin, ListModelMixin, GenericViewSet): lookup_field = 'uuid' serializer_class = CoverageSerializer permission_classes = (FHIRApiCoverageRequestPermissions,) diff --git a/api_fhir_r4/views/fhir/invoice.py b/api_fhir_r4/views/fhir/invoice.py index 9c6369a..1ac58d3 100644 --- a/api_fhir_r4/views/fhir/invoice.py +++ b/api_fhir_r4/views/fhir/invoice.py @@ -1,4 +1,5 @@ from rest_framework.request import Request +from django.utils.functional import cached_property from api_fhir_r4.mapping.invoiceMapping import InvoiceTypeMapping, BillTypeMapping from api_fhir_r4.mixins import ( @@ -31,7 +32,7 @@ class InvoiceViewSet(BaseMultiserializerFHIRView, retrievers = [UUIDIdentifierModelRetriever, DatabaseIdentifierModelRetriever, CodeIdentifierModelRetriever] lookup_field = 'identifier' - @property + @cached_property def serializers(self): return { InvoiceSerializer: diff --git a/api_fhir_r4/views/fhir/payment_notice.py b/api_fhir_r4/views/fhir/payment_notice.py index fc87595..49800fd 100644 --- a/api_fhir_r4/views/fhir/payment_notice.py +++ b/api_fhir_r4/views/fhir/payment_notice.py @@ -15,8 +15,12 @@ from invoice.models import PaymentInvoice -class PaymentNoticeViewSet(BaseFHIRView, MultiIdentifierRetrieverMixin, - MultiIdentifierUpdateMixin, viewsets.ModelViewSet): +class PaymentNoticeViewSet( + BaseFHIRView, + MultiIdentifierRetrieverMixin, + MultiIdentifierUpdateMixin, + viewsets.ModelViewSet +): retrievers = [UUIDIdentifierModelRetriever, GroupIdentifierModelRetriever] serializer_class = PaymentNoticeSerializer permission_classes = (FHIRApiPaymentPermissions,) diff --git a/api_fhir_r4/views/fhir/subscription.py b/api_fhir_r4/views/fhir/subscription.py index d48430d..82f715a 100644 --- a/api_fhir_r4/views/fhir/subscription.py +++ b/api_fhir_r4/views/fhir/subscription.py @@ -10,7 +10,7 @@ from api_fhir_r4.subscriptions.subscriptionSerializer import SubscriptionSerializerSchema from api_fhir_r4.views.fhir.base import BaseFHIRView from api_fhir_r4.views.filters import DateUpdatedRequestParameterFilter - +from api_fhir_r4.mixins import ListModelMixin @extend_schema_view( list=extend_schema(responses={(200, 'application/json'): SubscriptionSerializerSchema()}), @@ -28,7 +28,7 @@ ), destroy=extend_schema(responses={204: None}) ) -class SubscriptionViewSet(BaseFHIRView, ModelViewSet): +class SubscriptionViewSet(BaseFHIRView, ListModelMixin, ModelViewSet): _error_while_deleting = 'Error while deleting a subscription: %(msg)s' serializer_class = SubscriptionSerializer http_method_names = ('get', 'post', 'put', 'delete') From cbb74a981e0d49156aeecde114e9e9f62874c618 Mon Sep 17 00:00:00 2001 From: delcroip Date: Wed, 2 Oct 2024 15:07:18 +0200 Subject: [PATCH 8/8] missing migration --- api_fhir_r4/mixins.py | 34 +++++++++++++++++-- .../subscriptions/subscriptionSerializer.py | 8 ++--- api_fhir_r4/views/fhir/base/__init__.py | 5 ++- api_fhir_r4/views/fhir/contract.py | 2 +- api_fhir_r4/views/fhir/coverage_request.py | 5 ++- api_fhir_r4/views/fhir/subscription.py | 4 +-- 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/api_fhir_r4/mixins.py b/api_fhir_r4/mixins.py index ea1ab73..e9346ef 100644 --- a/api_fhir_r4/mixins.py +++ b/api_fhir_r4/mixins.py @@ -8,15 +8,16 @@ from django.core.exceptions import ObjectDoesNotExist, FieldError from django.http import Http404 -from rest_framework.mixins import RetrieveModelMixin 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 + ListModelMixin as RestListModelMixin, + DestroyModelMixin as RestDestroyModelMixin, + RetrieveModelMixin as RestRetrieveModelMixin, ) logger = logging.getLogger(__name__) @@ -71,6 +72,33 @@ def list(self, request, *args, **kwargs): 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' diff --git a/api_fhir_r4/subscriptions/subscriptionSerializer.py b/api_fhir_r4/subscriptions/subscriptionSerializer.py index 2b5a80a..bc53ca6 100644 --- a/api_fhir_r4/subscriptions/subscriptionSerializer.py +++ b/api_fhir_r4/subscriptions/subscriptionSerializer.py @@ -11,9 +11,9 @@ FHIRApiHealthServicePermissions from api_fhir_r4.serializers import BaseFHIRSerializer from api_fhir_r4.services import SubscriptionService +from api_fhir_r4.mixins import RetrieveModelMixin - -class SubscriptionSerializer(BaseFHIRSerializer): +class SubscriptionSerializer(BaseFHIRSerializer, RetrieveModelMixin): fhirConverter = SubscriptionConverter _error_while_saving = 'Error while saving a subscription: %(msg)s' @@ -24,7 +24,7 @@ class SubscriptionSerializer(BaseFHIRSerializer): } def create(self, validated_data): - user = self.context['request'].user + user = self.user or self.context['request'].user self.check_resource_rights(user, validated_data) service = SubscriptionService(user) copied_data = deepcopy(validated_data) @@ -36,7 +36,7 @@ def create(self, validated_data): return self.get_result_object(result) def update(self, instance, validated_data): - user = self.context['request'].user + user = self.user or self.context['request'].user self.check_instance_id(instance, validated_data) self.check_object_owner(user, instance) self.check_resource_rights(user, validated_data) diff --git a/api_fhir_r4/views/fhir/base/__init__.py b/api_fhir_r4/views/fhir/base/__init__.py index 0171052..7c387c3 100644 --- a/api_fhir_r4/views/fhir/base/__init__.py +++ b/api_fhir_r4/views/fhir/base/__init__.py @@ -7,16 +7,19 @@ from api_fhir_r4.mixins import ( UpdateModelMixin, ListModelMixin, - CreateModelMixin + CreateModelMixin, + DestroyModelMixin ) class BaseFHIRView( CreateModelMixin, UpdateModelMixin, + DestroyModelMixin, APIView, ): + user = None pagination_class = FhirBundleResultsSetPagination permission_classes = (FHIRApiPermissions,) authentication_classes = [CsrfExemptSessionAuthentication] + APIView.settings.DEFAULT_AUTHENTICATION_CLASSES diff --git a/api_fhir_r4/views/fhir/contract.py b/api_fhir_r4/views/fhir/contract.py index c1e42fa..cf83401 100644 --- a/api_fhir_r4/views/fhir/contract.py +++ b/api_fhir_r4/views/fhir/contract.py @@ -1,11 +1,11 @@ import datetime from django.db.models import Prefetch -from rest_framework.mixins import RetrieveModelMixin, ListModelMixin from rest_framework.viewsets import GenericViewSet from api_fhir_r4.permissions import FHIRApiCoverageRequestPermissions from api_fhir_r4.serializers import ContractSerializer from api_fhir_r4.views.fhir.base import BaseFHIRView +from api_fhir_r4.mixins import RetrieveModelMixin, ListModelMixin from api_fhir_r4.views.filters import ValidityFromRequestParameterFilter from insuree.models import InsureePolicy from policy.models import Policy diff --git a/api_fhir_r4/views/fhir/coverage_request.py b/api_fhir_r4/views/fhir/coverage_request.py index 0daa3cf..4e080b9 100644 --- a/api_fhir_r4/views/fhir/coverage_request.py +++ b/api_fhir_r4/views/fhir/coverage_request.py @@ -1,8 +1,7 @@ import datetime - -from rest_framework.mixins import RetrieveModelMixin + from rest_framework.viewsets import GenericViewSet -from api_fhir_r4.mixins import ListModelMixin +from api_fhir_r4.mixins import ListModelMixin, RetrieveModelMixin from api_fhir_r4.permissions import FHIRApiCoverageRequestPermissions from api_fhir_r4.serializers.coverageSerializer import CoverageSerializer from api_fhir_r4.views.fhir.base import BaseFHIRView diff --git a/api_fhir_r4/views/fhir/subscription.py b/api_fhir_r4/views/fhir/subscription.py index 82f715a..27e474e 100644 --- a/api_fhir_r4/views/fhir/subscription.py +++ b/api_fhir_r4/views/fhir/subscription.py @@ -10,7 +10,7 @@ from api_fhir_r4.subscriptions.subscriptionSerializer import SubscriptionSerializerSchema from api_fhir_r4.views.fhir.base import BaseFHIRView from api_fhir_r4.views.filters import DateUpdatedRequestParameterFilter -from api_fhir_r4.mixins import ListModelMixin +from api_fhir_r4.mixins import ListModelMixin, RetrieveModelMixin @extend_schema_view( list=extend_schema(responses={(200, 'application/json'): SubscriptionSerializerSchema()}), @@ -28,7 +28,7 @@ ), destroy=extend_schema(responses={204: None}) ) -class SubscriptionViewSet(BaseFHIRView, ListModelMixin, ModelViewSet): +class SubscriptionViewSet(BaseFHIRView, ListModelMixin, RetrieveModelMixin, ModelViewSet): _error_while_deleting = 'Error while deleting a subscription: %(msg)s' serializer_class = SubscriptionSerializer http_method_names = ('get', 'post', 'put', 'delete')