In my Django-Rest-Framework application, I have the need to divide the API into two branches such as:
/api/public/.../api/private/...
Consider a model Analysis like so:
from django.db import models
from django.conf import settings
class Analysis(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
public = models.BooleanField(default=True)
What I want then is:
/api/public/analysis=> filter queryset withQ(public=True)/api/private/analysis=> filter queryset withQ(user=request.user) | Q(public=False)
Similar pattern is required in 5+ models.
I have thought of 2 half-baked solutions:
1. AbstractViewSet sub-classed into PublicViewSet and PrivateViewSet
class AbstractAnalysisViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Analysis.objects.all()
serializer_class = AnalysisSerializer
ordering = [
"-created_at",
]
permission_classes = [IsAuthenticated,]
class PrivateAnalysisViewSet(AbstractAnalysisViewSet):
def get_permissions(self):
permissions = super(PrivateAnalysisViewSet, self).get_permissions()
if self.action in ["retrieve"]:
permissions.append(HasObjectPermission())
return permissions
def get_queryset(self):
queryset = super(PrivateAnalysisViewSet, self).get_queryset()
if self.action in ["list"]:
auth_user = self.request.user
query = Q(user=auth_user) | Q(public=False)
queryset = queryset.filter(query)
return queryset
class PublicAnalysisViewSet(AbstractAnalysisViewSet):
def get_permissions(self):
permissions = super(PublicAnalysisViewSet, self).get_permissions()
if self.action in ["retrieve"]:
permissions.append(IsPublicObjectPermission())
return permissions
def get_queryset(self):
queryset = super(AnalysisViewSet, self).get_queryset()
if self.action in ["list"]:
query = Q(public=True)
queryset = queryset.filter(query)
return queryset
This would work but I don't really want to duplicate views into multiple classes like that because then I also need to register multiple viewsets with the DRF router. As I said, I have to apply this same pattern in 5 models and that means 5*3=15 viewset classes which is hard to maintain over time and the number increases fast as my application grows in size.
2. Determine private or public by parsing the self.request.path
class AnalysisViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Analysis.objects.all()
serializer_class = AnalysisSerializer
ordering = [
"-created_at",
]
permission_classes = [IsAuthenticated,]
def get_permissions(self):
permissions = super(PublicAnalysisViewSet, self).get_permissions()
if self.action in ["retrieve"]:
if "/private/" in self.request.path:
permissions.append(HasObjectPermission())
else:
permissions.append(IsPublicObjectPermission())
return permissions
I do like this approach but I can't figure out how to register this viewset with the DRF router such that it allows prefixing of /public/ or /private/.
Any other cool ideas to solve this are welcome and much appreciated.