Skip to content

Commit 5e98104

Browse files
dealing with query param issues and class structure
1 parent 3e85c02 commit 5e98104

File tree

5 files changed

+101
-65
lines changed

5 files changed

+101
-65
lines changed

apps/fhir/bluebutton/views/generic.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ class FhirDataView(APIView):
5151
ApplicationActivePermission,
5252
HasCrosswalk,
5353
ResourcePermission,
54-
DataAccessGrantPermission]
54+
DataAccessGrantPermission
55+
]
5556

5657
def __init__(self, version=1):
5758
self.version = version

apps/fhir/bluebutton/views/insurancecard.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,8 @@ def has_permission(self, request, view):
4141
return request.user.is_authenticated and hasattr(request.user, 'crosswalk')
4242

4343
def build_parameters(self, request):
44-
patient_id = request.query_params.get('patient', None)
45-
if not patient_id:
46-
patient_id = request.user.crosswalk.fhir_id
4744
return {
48-
'_format': 'application/json+fhir'
45+
'_format': 'application/json'
4946
}
5047

5148
def build_url(self, fhir_settings, resource_type, resource_id=None, *args, **kwargs):

apps/fhir/bluebutton/views/insurancecard_viewset.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
from rest_framework import viewsets, permissions
2-
from rest_framework.response import Response
3-
42
from apps.fhir.bluebutton.views.generic import FhirDataView
53
from apps.authorization.permissions import DataAccessGrantPermission
64
from apps.capabilities.permissions import TokenHasProtectedCapability
@@ -47,17 +45,6 @@ def __init__(self, version=1, **kwargs):
4745
def initial(self, request, *args, **kwargs):
4846
return super().initial(request, self.resource_type, *args, **kwargs)
4947

50-
def list(self, request, *args, **kwargs):
51-
'''Equivalent to get() in FhirDataView'''
52-
out = self.fetch_data(request, self.resource_type, *args, **kwargs)
53-
return Response(out)
54-
55-
def build_parameters(self, request):
56-
patient_id = request.query_params.get('patient', None)
57-
if not patient_id:
58-
patient_id = request.user.crosswalk.fhir_id
59-
return {'_format': 'application/json+fhir'}
60-
6148
def build_url(self, fhir_settings, resource_type, resource_id=None, *args, **kwargs):
6249
if fhir_settings.fhir_url.endswith('v1/fhir/'):
6350
# only if called by tests

apps/fhir/bluebutton/views/patient_viewset.py

Lines changed: 25 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
from rest_framework import viewsets, permissions
2-
from rest_framework.response import Response
3-
4-
from apps.fhir.bluebutton.views.generic import FhirDataView
1+
from apps.fhir.bluebutton.views.viewsets_base import ResourceViewSet
52
from apps.fhir.bluebutton.views.search import HasSearchScope, SearchView
63
from apps.authorization.permissions import DataAccessGrantPermission
74
from apps.capabilities.permissions import TokenHasProtectedCapability
@@ -11,9 +8,10 @@
118
ResourcePermission,
129
ApplicationActivePermission,
1310
)
11+
from rest_framework import permissions
1412

1513

16-
class PatientViewSet(FhirDataView, viewsets.ViewSet):
14+
class PatientViewSet(ResourceViewSet):
1715
"""Patient django-rest-framework ViewSet experiment
1816
1917
Args:
@@ -24,51 +22,31 @@ class PatientViewSet(FhirDataView, viewsets.ViewSet):
2422
version = 1
2523
resource_type = 'Patient'
2624

27-
QUERY_TRANSFORMS = getattr(SearchView, 'QUERY_TRANSFORMS', {})
28-
QUERY_SCHEMA = {**getattr(SearchView, 'QUERY_SCHEMA', {}), '_id': str, 'identifier': str}
29-
30-
required_scopes = ['patient/Patient.read', 'patient/Patient.rs', 'patient/Patient.s']
31-
32-
def __init__(self, version=1, **kwargs):
33-
super().__init__(version)
25+
# TODO - I don't love the separation here, could be indicative that we don't want to move to resource based ViewSets, or that
26+
# we need a better base class, or these differences should be defined in PatientViewSet.
27+
SEARCH_QUERY_TRANSFORMS = getattr(SearchView, 'QUERY_TRANSFORMS', {})
28+
SEARCH_QUERY_SCHEMA = {**getattr(SearchView, 'QUERY_SCHEMA', {}), '_id': str, 'identifier': str}
3429

35-
def initial(self, request, *args, **kwargs):
36-
return super().initial(request, self.resource_type, *args, **kwargs)
30+
SEARCH_PERMISSION_CLASSES = (
31+
permissions.IsAuthenticated,
32+
ApplicationActivePermission,
33+
ResourcePermission,
34+
SearchCrosswalkPermission,
35+
DataAccessGrantPermission,
36+
TokenHasProtectedCapability,
37+
HasSearchScope,
38+
)
3739

38-
def get_permissions(self):
39-
action = getattr(self, 'action', None)
40-
if action == 'list':
41-
perm_classes = [
42-
permissions.IsAuthenticated,
43-
ApplicationActivePermission,
44-
ResourcePermission,
45-
SearchCrosswalkPermission,
46-
DataAccessGrantPermission,
47-
TokenHasProtectedCapability,
48-
HasSearchScope,
49-
]
50-
else:
51-
perm_classes = [
52-
permissions.IsAuthenticated,
53-
ApplicationActivePermission,
54-
ResourcePermission,
55-
ReadCrosswalkPermission,
56-
DataAccessGrantPermission,
57-
TokenHasProtectedCapability,
58-
]
59-
return [p() for p in perm_classes]
60-
61-
def list(self, request, *args, **kwargs):
62-
'''Equivalent to get()/search in FhirDataView'''
63-
out = self.fetch_data(request, self.resource_type, *args, **kwargs)
64-
return Response(out)
40+
READ_PERMISSION_CLASSES = (
41+
permissions.IsAuthenticated,
42+
ApplicationActivePermission,
43+
ResourcePermission,
44+
ReadCrosswalkPermission,
45+
DataAccessGrantPermission,
46+
TokenHasProtectedCapability,
47+
)
6548

66-
def retrieve(self, request, resource_id=None, *args, **kwargs):
67-
out = self.fetch_data(request, self.resource_type, resource_id=resource_id, *args, **kwargs)
68-
return Response(out)
69-
70-
def build_parameters(self, request):
71-
return {'_format': 'application/json+fhir'}
49+
required_scopes = ['patient/Patient.read', 'patient/Patient.rs', 'patient/Patient.s']
7250

7351
def build_url(self, fhir_settings, resource_type, resource_id=None, *args, **kwargs):
7452
if fhir_settings.fhir_url.endswith('v1/fhir/'):
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from rest_framework import viewsets, permissions
2+
from rest_framework.response import Response
3+
from voluptuous import Schema, REMOVE_EXTRA
4+
5+
from apps.fhir.bluebutton.views.generic import FhirDataView
6+
7+
8+
class ResourceViewSet(FhirDataView, viewsets.ViewSet):
9+
"""Base FHIR resource ViewSet, would replace FhirDataView if we decide to go in that direction
10+
11+
Args:
12+
FhirDataView: Base mixin, unchanged
13+
viewsets: django-rest-framework ViewSet base class
14+
"""
15+
16+
resource_type = None
17+
18+
SEARCH_PERMISSION_CLASSES = (permissions.IsAuthenticated,)
19+
READ_PERMISSION_CLASSES = (permissions.IsAuthenticated,)
20+
21+
def initial(self, request, *args, **kwargs):
22+
return super().initial(request, self.resource_type, *args, **kwargs)
23+
24+
def get_permissions(self):
25+
action = getattr(self, 'action', None)
26+
if action == 'list':
27+
permission_classes = getattr(self, 'SEARCH_PERMISSION_CLASSES', self.SEARCH_PERMISSION_CLASSES)
28+
elif action == 'retrieve':
29+
permission_classes = getattr(self, 'READ_PERMISSION_CLASSES', self.READ_PERMISSION_CLASSES)
30+
else:
31+
permission_classes = (permissions.IsAuthenticated,)
32+
return [permission_class() for permission_class in permission_classes]
33+
34+
def list(self, request, *args, **kwargs):
35+
out = self.fetch_data(request, self.resource_type, *args, **kwargs)
36+
return Response(out)
37+
38+
def retrieve(self, request, resource_id=None, *args, **kwargs):
39+
out = self.fetch_data(request, self.resource_type, resource_id=resource_id, *args, **kwargs)
40+
return Response(out)
41+
42+
# A lot of this is copied (haphazardly) from generic, and the names and handling of the schema could be cleaned up
43+
def get_query_schema(self):
44+
if getattr(self, 'action', None) == 'list':
45+
return getattr(self, 'SEARCH_QUERY_SCHEMA', getattr(self, 'QUERY_SCHEMA', {}))
46+
return {}
47+
48+
def get_query_transforms(self):
49+
if getattr(self, 'action', None) == 'list':
50+
return getattr(self, 'SEARCH_QUERY_TRANSFORMS', getattr(self, 'QUERY_TRANSFORMS', {}))
51+
return getattr(self, 'QUERY_TRANSFORMS', {})
52+
53+
def map_parameters(self, params):
54+
transforms = self.get_query_transforms()
55+
for key, correct in transforms.items():
56+
val = params.pop(key, None)
57+
if val is not None:
58+
params[correct] = val
59+
return params
60+
61+
def filter_parameters(self, request):
62+
if getattr(self, 'action', None) != 'list':
63+
return {}
64+
65+
params = self.map_parameters(request.query_params.dict())
66+
params['_lastUpdated'] = request.query_params.getlist('_lastUpdated')
67+
68+
schema = Schema(self.get_query_schema(), extra=REMOVE_EXTRA)
69+
return schema(params)
70+
71+
# TODO - investigate if this is needed, or if we can assume application/json+fhir everywhere
72+
def build_parameters(self, request):
73+
return {'_format': 'application/json+fhir'}

0 commit comments

Comments
 (0)