G’day all,
I’m trying to be a little clever and make my querysets more reusable. I have a long list of endpoints where I have objects related to either a case or a visit. In the example below, I demonstrate a queryset which returns objects of a particular model as defined in a view. The purpose of this queryset is to limit the objects in the queryset based on the access to the underlying objects that a user should or should not have.
def visit_related_queryset(view):
"""
We want to find all the objects which are related to the user
by way of `case` and the user's relation to it. e.g., did the user
create the case, are they a contributor or are they a uni admin
and should therefore see all of a uni's objects + any other objects
belonging to a case to which they have contributed.
:param view: The DRF view
:return: A queryset of the object defined in the view
"""
user = view.request.user
uni = Q(visit__case__contributing_uni=user.university)
contributor = Q(visit__case__contributors__contributor__user=user)
case_creator = Q(visit__case__creator__user=user)
permitted_users = Q(contributor | case_creator) # for regular users
admin = Q(contributor | case_creator | uni) # for uni admin staff
visit = Q(visit__id=view.kwargs["visit"])
if is_eclinic_admin(user):
return view.model.objects.all()
if is_uni_admin(user):
return view.model.objects.all().filter(visit, admin)
return view.model.objects.all().filter(visit, permitted_users)
This queryset works well, and after moving to it from similarly custom querysets under each view where the visit’s UUID is used as the filter, all tests continue to pass. That’s a relief!
In addition to the visit related views, I have many views that don’t directly related to a visit, but do so indirectly via a FK relationship.
As an example, a question
object has this relationship to Visit
and Case
:
quiz__visit__case
and an answer
object this relationship to Visit
and Case
:
question__quiz__visit__case
I’m trying to find a way whereby I can reuse my def visit_related_queryset(view)
method to filter queries for endpoints which do not have a direct relationship to Visit
. At first I thought I could use **kwargs
but I came unstuck when trying to workout how I would unpack **kwargs
into the correct Q
filters.
Additionally, I’m trying to work out how I can pass a string as an argument kwarg
, e.g., I was thinking I could pass a variable into Q
to achieve something like this: `Q(my_filter=my_value). This could be as much as a lack of Python specific knowledge as it is a lack of Django specific knowledge.
I’m playing around with the above two thoughts trying to find an elegant solution and avoiding have to write custom queryset methods with only very minor filtering variations. I thought it a good idea to raise my hand here whilst playing around and before I get myself in a pickle.
edit
I should mention what I am currently doing to make my code a bit more reusable. I have first made the following queryset:
def get_visit_relation_queryset(view, uni, contributor, creator):
user = view.request.user
permitted_users = Q(contributor | creator)
admin_staff = Q(contributor | creator | uni)
visit = Q(visit__id=view.kwargs["visit"])
if is_eclinic_admin(user):
return view.model.objects.all()
if is_uni_admin(user):
return view.model.objects.all().filter(visit, admin_staff)
return view.model.objects.all().filter(visit, permitted_users)
and then depending on the view, I do something like this:
def get_queryset(self):
user = self.request.user
uni = Q(history__visit__case__contributing_uni=user.university)
contributor = Q(history__visit__case__contributors__contributor__user=user)
creator = Q(history__visit__case__creator__user=user)
return querysets.get_visit_relation_queryset(self, uni, contributor, creator).select_related("visit")
As always folks, thank you for your input and help.
Cheers,
Conor