I have a foreign key on my models like Patient, and Doctor, which point to a Clinic class. So, the Patient and Doctor are supposed to belong to this Clinic alone. Other Clinics should not be able to see any detail of these Models.
The models look like this:
class Clinic(models.Model):
clinicid = models.AutoField(primary_key=True, unique=True)
name = models.CharField(max_length=60, unique=True)
label = models.SlugField(max_length=25, unique=True)
email = models.EmailField(max_length=100, default='')
mobile = models.CharField(max_length=15, default='')
...
class Doctor(models.Model):
# Need autoincrement, unique and primary
docid = models.AutoField(primary_key=True, unique=True)
name = models.CharField(max_length=200)
username = models.CharField(max_length=15)
regid = models.CharField(max_length=15, default="", blank=True)
...
linkedclinic = models.ForeignKey(Clinic, on_delete=models.CASCADE)
class Patient(models.Model):
cstid = models.AutoField(primary_key=True, unique=True)
date_of_registration = models.DateField(default=timezone.now)
name = models.CharField(max_length=35, blank=False)
ageyrs = models.IntegerField(blank=True)
agemnths = models.IntegerField(blank=True)
dob = models.DateField(null=True, blank=True)
...
linkedclinic = models.ForeignKey(Clinic, on_delete=models.CASCADE)
class UserGroupMap(models.Model):
id = models.AutoField(primary_key=True, unique=True)
user = models.ForeignKey(
User, related_name='target_user', on_delete=models.CASCADE)
group = models.ForeignKey(UserGroup, on_delete=models.CASCADE)
clinic = models.ForeignKey(Clinic, on_delete=models.CASCADE)
...
From my Vue app, I will post using Axios to the django app which uses DRF, and thus get serialized data of Patients and Doctors. It all works fine if I try to use the following sample code in function view:
@api_view(['GET', 'POST'])
def register_patient_vue(request):
if request.method == 'POST':
print("POST details", request.data)
data = request.data['registration_data']
serializer = customerSpecialSerializer(data=data)
if serializer.is_valid():
a = serializer.save()
print(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
print("Serializer is notNot valid.")
print(serializer.errors)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Sample output:
POST details {'registration_data': {'name': 'wczz', 'ageyrs': 21, 'agemonths': '', 'dob': '', 'gender': 'unspecified', 'mobile': '2', 'email': '', 'alternate': '', 'address': '', 'marital': 'unspecified', 'city': '', 'occupation': '', 'linkedclinic': 10}}
data: {'name': 'wczz', 'ageyrs': 21, 'agemonths': '', 'dob': '', 'gender': 'unspecified', 'mobile': '2', 'email': '', 'alternate': '', 'address': '', 'marital': 'unspecified', 'city': '', 'occupation': '', 'linkedclinic': 10}
However, I need to authenticate the request by special custom authentication. I have another class called UserGroupMap which has Foreign Keys for both User and Clinic, so that if there is a match for a filter for the clinic and user, in the map, it will authenticate. Else it should fail authentication and the data should not be retrieved or serializer saved.
In my previous simple pure django project I used to employ a custom permission function, and decorating my view with it:
@handle_perm(has_permission_level, required_permission='EDIT_CLINICAL_RECORD', login_url='/clinic/')
def some_function(request, dept_id):
....
Some code which runs after authentication
And it would use the following:
def handle_perm(test_func, required_permission=None, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
"""
Decorator for views that checks that the user passes the given test,
redirecting to the log-in page if necessary. The test should be a callable
that takes the user object and returns True if the user passes.
"""
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
print(f"Required permission level is {required_permission}")
if has_permission_level(request, required_permission):
print("User has required permission level..Allowing entry.")
return view_func(request, *args, **kwargs)
print("FAILED! User does not have required permission level. Access blocked.")
path = request.build_absolute_uri()
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path()
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(
path, resolved_login_url, redirect_field_name)
return _wrapped_view
return decorator
def has_permission_level(request, required_permission, clinic=None):
print("has_permission_level was called.")
user = request.user
print(f'user is {user}')
clinic=clinic_from_request(request)
print(f"has_permission_level called with clinic:{clinic}")
if clinic is None:
print("clinic is none")
return HttpResponseRedirect('/accounts/login/')
group_maps = UserGroupMap.objects.filter(user=user, clinic=clinic)
print(f"No: of UserGroupMap memberships: {len(group_maps)}")
if len(group_maps) < 1:
# There are no UserGroupMap setup for the user. Kindly set them up.\nHint:Admin>Manage users and groups>Users
return False
# Now checking Group memberships whether the user has any with permisison
for map in group_maps:
rolesmapped = GroupRoleMap.objects.filter(group=map.group)
if len(rolesmapped) < 1:
print(f"No permission roles.")
else:
for rolemap in rolesmapped:
print(f"{rolemap.role}", end=",")
if rolemap.role.name == required_permission:
print(
f"\nAvailable role of [{map.group}] matched required permission of [{required_permission}] in {clinic.name} [Ok]")
return True
return False
I need to build a custom authentication using DRF, so that it reads the POSTed data, and checks the linkedclinic value, and employs similiar logic.
I started like this:
def has_permission_POST(request, required_permission, clinic=None):
print("has_permission_POST was called.")
user = request.user
print(f'user is {user}')
if request.method == 'POST':
print(request)
print(dir(request))
print("POST details: POST:", request.POST, "\n")
print("POST details: data:", request.data, "\n")
....
# Further logic to check the mapping
return True
else:
print("Not a valid POST")
return Response("INVALID POST", status=status.HTTP_400_BAD_REQUEST)
# And decorating my DRF view:
@handle_perm(has_permission_POST, required_permission='EDIT_CLINICAL_RECORD', login_url='/clinic/')
@api_view(['GET', 'POST'])
def register_patient_vue(request):
if request.method == 'POST':
print("POST details", request.data)
data = request.data['registration_data']
The problem is that if I run this, then, has_permission_POST cannot get the value of request.data, which contains the data posted from my frontend. I can work around this, by adding the @api_view(['GET', 'POST'])
decorator to has_permission_POST. But that introduces another error, a failed assertion:
AssertionError: Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<class 'bool'>`
This happens from has_permission_POST once it is decorated with @api_view.
So my problems:
- How to implement a custom authentication for my use case?
- If I am going about this right, by using this custom has_permission_level, how can I get the request.data in this function before my actual api view is called, so that I can read the clinic id and do the checks for permission that I need.
I have taken a look at the CustomAuthentication provided by DRF, but could not find out how to get the request.data parameters in the custom class.