I'm currently trying to figure out per user object permissions for our Django website API.
I have several models with sensitive information, that I need to be able to filter on a user basis.
For a simplified example of one of the models:
Restaurant, main customer of the website.
User, each user gets assigned a restaurant when the user account is created. As such, a restaurant can have many users and they all should only be able to access that restaurant's information.
Oven, which belong to a specific restaurant. A restaurant can have many ovens.
Recipe, which belong to an oven. An oven can have many different recipes.
Recipe Results, which belong to a recipe. There can be many different Recipe Results belonging to the same Recipe (different ingredients tried, etc).
There are at least 12+ different models. All models from a particular restaurant have to be hidden from other restaurants, we don't want them to be able to look at other restaurant recipes after all!
Not all models have a user = models.ForeignKey(User)
Without having to go into each one of my models and declaring owner = models.ForeignKey(User), is there a way to filter them in my API List Views and Detail Views?
Currently my List API View looks like this (simplified example):
class RecipeResultsListAPIView(ListAPIView):
queryset = RecipeResults.objects.all()
queryset = queryset.prefetch_related('oven')
serializer_class = RecipeResultsListSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('id', 'time', 'oven', 'recipe_name', 'recipe_description')
pagination_class = ExpertPageNumberPagination
def list(self, request):
user = User.objects.get(username=request.user)
restaurant = Restaurant.objects.get(user=user)
ovens = Oven.objects.filter(restaurant=restaurant)
queryset = RecipeResults.objects.filter(oven__in=ovens)
serializer = RecipeResultsListSerializer(queryset, many=True, context={'request':request})
return Response(serializer.data)
And the model for that looks like this:
class RecipeResults(models.Model):
time = models.DateTimeField()
oven = models.ForeignKey(Oven, on_delete=models.CASCADE)
recipe_name = models.CharField(max_length=20)
recipe_description = models.CharField(max_length=50)
def __str__(self):
return str(self.time) + ': ' + self.recipe_name + ' = ' + self.recipe_description
def __key(self):
return self.oven, self.time, self.recipe_name
def __eq__(self, y):
return isinstance(y, self.__class__) and self.__key() == y.__key()
def __hash__(self):
return hash(self.__key())
class Meta:
unique_together=(('time','recipe_name', 'oven-'),)
Specifically looking at the modified list method, currently this works properly to filter API call results to display only those Recipe Results that belong to the user that is logged in.
What I'm trying to figure out is if there's an easier way to do this, as for each model I would have to trace back ownership to the specific restaurant which would get confusing fast as I have 12+ different models.
What I'm not sure is if declaring "owner = models.ForeignKey(User)" on each of those models is the way to go. It feels like it would create many extra steps when retrieving the data.
I have also tried
class IsOwnerOrAdmin(BasePermission):
"""
Custom permission to only allow owners of an object to see and edit it.
Admin users however have access to all.
"""
def has_object_permission(self, request, view, obj):
# Permissions are only allowed to the owner of the snippet
if request.user.is_staff:
return True
return obj.user == request.user
But this didn't seem to filter properly, and besides, not all of the models have a user field assigned to them.
Please keep in mind I'm a junior developer and I'm learning a lot as I go. I'm only working on the API side of the company. The website and schema is already a work in progress and other systems depend on it, and so I'm trying not to modify the schema or models too much (I would like to avoid this if possible, but will do it if it's the only way). I was also brought in just to work on the API at first. The company understands I'm a junior developer and I'm extremely grateful to have been given the opportunity to grow while learning this project, but this one issue seems to be giving me a lot more trouble than actually building the rest of the API for the website.
I would greatly appreciate any help I can get with this!