Как проверить с помощью декоратора, имеет ли пользователь доступ к некоторым объектам в Django
Я работаю над проектом, структура которого выглядит следующим образом. И я пытался проверить, является ли пользователь членом запрашиваемой команды и может ли он видеть ее детали. Я пытался добиться этого с помощью декораторов, но все равно получаю ошибку http 500.
Я не уверен, что это правильное решение и просто плохая реализация, или лучше сделать это по-другому.
Я буду благодарен за Вашу помощь.
Team/Models.py
roles = (('admin', 'admin'), ('member', 'member'), ('guest', 'guest'))
class Team(models.Model):
name = models.CharField(max_length=150)
description = models.TextField(blank=True)
members = models.ManyToManyField(User, through='Membership')
cppTables = models.JSONField(default=list, blank=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return f'teams/{self.pk}/'
def has_user(self, user_id):
return self.members.get(pk=user_id).exists()
class Membership(models.Model):
member = models.ForeignKey(User, on_delete=models.CASCADE)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
role = models.CharField(max_length=15, choices=roles, default='member')
def __str__(self):
return str(self.member)
class Meta:
unique_together = [['member', 'team']]
Team/Decorators.py
def required_membership(view_func):
def wrapper(request, *args, **kwargs):
team_id = kwargs['team_id']
team = Team.objects.get(pk=team_id)
if team.has_user(request.user.id):
return view_func(request, *args, **kwargs)
else:
raise HttpResponseForbidden
return wrapper
Team/Views.py
@method_decorator(required_membership, name="dispatch")
class TeamDetail(APIView):
def get_object(self, pk):
try:
return Team.objects.get(pk=int(pk))
except Team.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
team = self.get_object(pk)
serializer = TeamSerializer(team, many=False)
return Response(serializer.data)
в декораторе поместите обертку внутрь внутренней функции. ваш код будет выглядеть примерно так и плюс не нужно помещать ее внутрь декоратора методов просто используйте вот так @required_membership()
from django.core.exceptions import PermissionDenied
def required_membership():
def inner(view_func):
def wrapper(request, *args, **kwargs):
team_id = kwargs['team_id']
team = Team.objects.get(pk=team_id)
if team.has_user(request.user.id):
return view_func(request, *args, **kwargs)
else:
raise PermissionDenied
return wrapper
return inner
UPDATE
поместите декоратор над методом get или post вместо класса
Самым простым способом, вероятно, является просто фильтрация в представлении:
from django.shortcuts import get_object_or_404
class TeamDetail(APIView):
def get_object(self, pk):
return get_object_or_404(Team, pk=pk, members=self.request.user)
def get(self, request, pk, format=None):
team = self.get_object(pk)
serializer = TeamSerializer(team, many=False)
return Response(serializer.data)
В случае, если пользователь не является членом команды, возвращается 404, что дает меньше информации для использования, когда пользователь стремится посетить команду, членом которой он не является.
Вы также можете поднять HttpResponseForbidden
вместо этого, проверив членство с помощью:
from django.shortcuts import get_object_or_404
class TeamDetail(APIView):
def get_object(self, pk):
team = get_object_or_404(Team, pk=pk)
if not team.members.filter(pk=self.request.user.pk).exists():
raise HttpResponseForbidden
return team
def get(self, request, pk, format=None):
team = self.get_object(pk)
serializer = TeamSerializer(team, many=False)
return Response(serializer.data)
Ваш метод has_user(…)
имеет ошибку: он должен использовать .filter(…)
, так как у объекта модели нет метода .exists()
, а .get(…)
вызовет ошибку, если не сможет найти элемент:
class Team(models.Model):
# …
def has_user(self, user_id):
return self.members.filter(pk=user_id).exists()