this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Added CSV support To API listFacilityDischargedPatients (#2601)

Added CSV support To API listFacilityDischargedPatients (#2601)

Co-authored-by: Aakash Singh <mail@singhaakash.dev>

authored by

Anvesh Nalimela
Aakash Singh
and committed by
GitHub
7454ab93 d23cbcb8

+135 -68
+5 -68
care/facility/api/viewsets/patient.py
··· 19 19 from django.db.models.query import QuerySet 20 20 from django.utils import timezone 21 21 from django_filters import rest_framework as filters 22 - from djqscsv import render_to_csv_response 23 22 from drf_spectacular.utils import extend_schema, extend_schema_view 24 23 from dry_rest_permissions.generics import DRYPermissionFiltersBase, DRYPermissions 25 24 from rest_framework import filters as rest_framework_filters ··· 80 79 from care.facility.models.patient_consultation import PatientConsultation 81 80 from care.users.models import User 82 81 from care.utils.cache.cache_allowed_facilities import get_accessible_facilities 82 + from care.utils.exports.mixins import CSVExportViewSetMixin 83 83 from care.utils.filters.choicefilter import CareChoiceFilter 84 84 from care.utils.filters.multiselect import MultiSelectFilter 85 85 from care.utils.notification_handler import NotificationGenerator ··· 376 376 377 377 @extend_schema_view(history=extend_schema(tags=["patient"])) 378 378 class PatientViewSet( 379 + CSVExportViewSetMixin, 379 380 HistoryMixin, 380 381 mixins.CreateModelMixin, 381 382 mixins.ListModelMixin, ··· 475 476 "last_consultation_encounter_date", 476 477 "last_consultation_discharge_date", 477 478 ] 478 - CSV_EXPORT_LIMIT = 7 479 479 480 480 def get_queryset(self): 481 481 queryset = super().get_queryset().order_by("modified_date") ··· 520 520 521 521 return super().filter_queryset(queryset) 522 522 523 - def list(self, request, *args, **kwargs): 524 - """ 525 - Patient List 526 - 527 - `without_facility` accepts boolean - default is false - 528 - if true: shows only patients without a facility mapped 529 - if false (default behaviour): shows only patients with a facility mapped 530 - 531 - `disease_status` accepts - string and int - 532 - SUSPECTED = 1 533 - POSITIVE = 2 534 - NEGATIVE = 3 535 - RECOVERY = 4 536 - RECOVERED = 5 537 - EXPIRED = 6 538 - 539 - """ 540 - if settings.CSV_REQUEST_PARAMETER in request.GET: 541 - # Start Date Validation 542 - temp = filters.DjangoFilterBackend().get_filterset( 543 - self.request, self.queryset, self 544 - ) 545 - temp.is_valid() 546 - within_limits = False 547 - for field in self.date_range_fields: 548 - slice_obj = temp.form.cleaned_data.get(field) 549 - if slice_obj: 550 - if not slice_obj.start or not slice_obj.stop: 551 - raise ValidationError( 552 - { 553 - field: "both starting and ending date must be provided for export" 554 - } 555 - ) 556 - days_difference = ( 557 - temp.form.cleaned_data.get(field).stop 558 - - temp.form.cleaned_data.get(field).start 559 - ).days 560 - if days_difference <= self.CSV_EXPORT_LIMIT: 561 - within_limits = True 562 - else: 563 - raise ValidationError( 564 - { 565 - field: f"Cannot export more than {self.CSV_EXPORT_LIMIT} days at a time" 566 - } 567 - ) 568 - if not within_limits: 569 - raise ValidationError( 570 - { 571 - "date": f"Atleast one date field must be filtered to be within {self.CSV_EXPORT_LIMIT} days" 572 - } 573 - ) 574 - # End Date Limiting Validation 575 - queryset = ( 576 - self.filter_queryset(self.get_queryset()) 577 - .annotate(**PatientRegistration.CSV_ANNOTATE_FIELDS) 578 - .values(*PatientRegistration.CSV_MAPPING.keys()) 579 - ) 580 - return render_to_csv_response( 581 - queryset, 582 - field_header_map=PatientRegistration.CSV_MAPPING, 583 - field_serializer_map=PatientRegistration.CSV_MAKE_PRETTY, 584 - ) 585 - 586 - return super().list(request, *args, **kwargs) 587 - 588 523 @extend_schema(tags=["patient"]) 589 524 @action(detail=True, methods=["POST"]) 590 525 def transfer(self, request, *args, **kwargs): ··· 678 613 679 614 680 615 @extend_schema_view(tags=["patient"]) 681 - class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): 616 + class FacilityDischargedPatientViewSet( 617 + CSVExportViewSetMixin, GenericViewSet, mixins.ListModelMixin 618 + ): 682 619 permission_classes = (IsAuthenticated, DRYPermissions) 683 620 lookup_field = "external_id" 684 621 serializer_class = PatientListSerializer
care/utils/exports/__init__.py

This is a binary file and will not be displayed.

+129
care/utils/exports/mixins.py
··· 1 + from django.conf import settings 2 + from django.db import models 3 + from django_filters import rest_framework as filters 4 + from djqscsv import render_to_csv_response 5 + from rest_framework.exceptions import ValidationError 6 + 7 + 8 + class CSVExportViewSetMixin: 9 + """Mixin that adds CSV export functionality to a viewset""" 10 + 11 + csv_export_limit = 7 12 + date_range_fields = [] 13 + 14 + def get_model(self): 15 + """Get model class from viewset's queryset or model attribute""" 16 + if hasattr(self, "queryset"): 17 + return self.queryset.model 18 + if hasattr(self, "model"): 19 + return self.model 20 + msg = ( 21 + "Cannot determine model class from viewset, set model or queryset attribute" 22 + ) 23 + raise ValueError(msg) 24 + 25 + def get_date_range_fields(self): 26 + """Get date range fields from model and filterset""" 27 + if self.date_range_fields: 28 + return self.date_range_fields 29 + 30 + model = self.get_model() 31 + date_fields = [] 32 + 33 + # Get fields from model that are DateField/DateTimeField 34 + for field in model._meta.fields: # noqa: SLF001 35 + if isinstance(field, (models.DateField, models.DateTimeField)): 36 + date_fields.append(field.name) 37 + 38 + # Get date range fields from filterset if defined 39 + if hasattr(self, "filterset_class"): 40 + for name, field in self.filterset_class.declared_filters.items(): 41 + if isinstance(field, filters.DateFromToRangeFilter): 42 + date_fields.append(name) 43 + 44 + return list(set(date_fields)) 45 + 46 + def get_csv_settings(self): 47 + """Get CSV export configuration from model""" 48 + model = self.get_model() 49 + 50 + # Try to get settings from model 51 + annotations = getattr(model, "CSV_ANNOTATE_FIELDS", {}) 52 + field_mapping = getattr(model, "CSV_MAPPING", {}) 53 + field_serializers = getattr(model, "CSV_MAKE_PRETTY", {}) 54 + 55 + if not field_mapping: 56 + # Auto-generate field mapping from model fields 57 + field_mapping = {f.name: f.verbose_name.title() for f in model._meta.fields} # noqa: SLF001 58 + 59 + fields = list(field_mapping.keys()) 60 + 61 + return { 62 + "annotations": annotations, 63 + "field_mapping": field_mapping, 64 + "field_serializers": field_serializers, 65 + "fields": fields, 66 + } 67 + 68 + def validate_date_ranges(self, request): 69 + """Validates that at least one date range filter is within limits""" 70 + filterset = filters.DjangoFilterBackend().get_filterset( 71 + request, self.queryset, self 72 + ) 73 + if not filterset.is_valid(): 74 + raise ValidationError(filterset.errors) 75 + 76 + within_limits = False 77 + for field in self.get_date_range_fields(): 78 + slice_obj = filterset.form.cleaned_data.get(field) 79 + if slice_obj: 80 + if not slice_obj.start or not slice_obj.stop: 81 + raise ValidationError( 82 + { 83 + field: "both starting and ending date must be provided for export" 84 + } 85 + ) 86 + 87 + days_difference = ( 88 + filterset.form.cleaned_data.get(field).stop 89 + - filterset.form.cleaned_data.get(field).start 90 + ).days 91 + 92 + if days_difference <= self.csv_export_limit: 93 + within_limits = True 94 + else: 95 + raise ValidationError( 96 + { 97 + field: f"Cannot export more than {self.csv_export_limit} days at a time" 98 + } 99 + ) 100 + 101 + if not within_limits: 102 + raise ValidationError( 103 + { 104 + "date": f"At least one date field must be filtered to be within {self.csv_export_limit} days" 105 + } 106 + ) 107 + 108 + def export_as_csv(self, request): 109 + """Exports queryset as CSV""" 110 + self.validate_date_ranges(request) 111 + 112 + csv_settings = self.get_csv_settings() 113 + queryset = self.filter_queryset(self.get_queryset()) 114 + 115 + if csv_settings["annotations"]: 116 + queryset = queryset.annotate(**csv_settings["annotations"]) 117 + 118 + queryset = queryset.values(*csv_settings["fields"]) 119 + 120 + return render_to_csv_response( 121 + queryset, 122 + field_header_map=csv_settings["field_mapping"], 123 + field_serializer_map=csv_settings["field_serializers"], 124 + ) 125 + 126 + def list(self, request, *args, **kwargs): 127 + if settings.CSV_REQUEST_PARAMETER in request.GET: 128 + return self.export_as_csv(request) 129 + return super().list(request, *args, **kwargs)
+1
pyproject.toml
··· 83 83 "FBT001", # why not! 84 84 "S106", 85 85 "S105", 86 + "UP038" # this results in slower code 86 87 ] 87 88 88 89