Django 5 для начинающих

Прогресс по курсу:  9/1004

8.7 Права доступа и токены в DRF
4 из 4 шагов пройдено

Пользовательские разрешения

Для нашего первого пользовательского разрешения мы хотим ограничить доступ так, чтобы только автор записи в блоге мог редактировать или удалять его.

Суперпользователь admin будет иметь доступ ко всему, но обычный пользователь может только обновлять/удалять свой собственный контент.

Внутри Django REST Framework опирается на класс BasePermission, от которого наследуются все остальные классы разрешений. Все встроенные параметры разрешений, такие как AllowAny или IsAuthenticated просто расширяют BasePermission.

class BasePermission(object):
    """
    Базовый класс, от которого должны наследоваться все классы разрешений.
    """

    def has_permission(self, request, view):
        """
        Возвращает `True`, если разрешение предоставлено, `False` в противном случае.
        """
        return True

    def has_object_permission(self, request, view, obj):
        """
        Возвращает `True`, если разрешение предоставлено, `False` в противном случае.
        """
        return True

Для пользовательского класса разрешений вы можете переопределить один или оба этих метода.

has_permission работает на представлениях списка, в то время как представления деталей выполняют оба метода:
Сначала has_permission, а затем, если это проходит, has_object_permission.

Настоятельно рекомендуется всегда задавать оба метода явно, потому что каждый из них по умолчанию имеет значение True.

В нашем случае мы хотим, чтобы только автор записи в блоге имел права на редактирование или удаление.
Мы также хотим ограничить просмотр списка только для чтения аутентифицированными пользователями.

Для этого мы создадим новый файл blog_api/permissions.py и заполним его следующим кодом:

from rest_framework import permissions


class IsAuthorOrReadOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        # Только аутентифицированные пользователи могут видеть представление списка
        if request.user.is_authenticated:
            return True
        return False

    def has_object_permission(self, request, view, obj):
        # Разрешение на чтение разрешено для любого запроса, поэтому мы всегда будем
        # разрешать запросы GET, HEAD или OPTIONS
        if request.method in permissions.SAFE_METHODS:
            return True
        # Права на запись разрешены только автору сообщения или администратору
        return obj.author == request.user or request.user.is_staff

Мы создаем пользовательский класс IsAuthorOrReadOnly, который расширяет BasePermission.

Первый метод, has_permission, требует, чтобы пользователь вошел в систему, чтобы иметь доступ.

Второй метод, has_object_permission, разрешает запросы только на чтение, но ограничивает права на запись только автором записи в блоге. Мы получаем доступ к полю author через obj.author и к текущему пользователю через request.user.

Вернемся к файлу views.py и добавим новое разрешение в permission_classes для PostDetail и PostList. И не забываем удалить или закомментировать строчку permission_classes = (permissions.IsAdminUser,):

......
from .permissions import IsAuthorOrReadOnly  # new

......

...

class PostList(generics.ListCreateAPIView):
    permission_classes = (IsAuthorOrReadOnly,)  # new
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['author']
    search_fields = ['body', 'author__profile__bio']
    ordering_fields = ['author_id', 'publish']
    ordering = ['body']
    pagination_class = StandardResultsSetPagination

...

...

class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (IsAuthorOrReadOnly,)  # new
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    # permission_classes = (permissions.IsAdminUser,)
 

Давайте проверим работу наших прав доступа, для этого авторизуемся под администратором и проверим наш список постов:

Мы видим что у администратора есть все права, он может редактировать любой пост нашего блога.

Теперь выйдем из под аккаунта администратора и зайдем под пользователем. И перейдем к детальному просмотру поста, в моем случае по ссылке http://127.0.0.1:8000/api/1/:

И мы видим, что мы можем просматривать детальное отображение постов.

Но если мы перейдем к посту, который был создан от этого пользователя, в моем случае это http://127.0.0.1:8000/api/9/:

Установка надлежащих разрешений - очень важная часть любого API.

В качестве общей стратегии, хорошей идеей является установка строгой политики разрешений на уровне проекта, чтобы только аутентифицированные пользователи могли просматривать API.

Затем сделать разрешения на уровне представления или пользовательские разрешения более доступными по мере необходимости.


  • Комментария
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий

Возможно тут должно быть 

Только аутентифицированные пользователи могут видеть представление списка

Изменен Марат Асылбаев

@Марат_Асылбаев, исправил, спасибо.

Простите, но у меня руки не поднимаются писать столько if/else :)

class IsAuthorOrReadOnly(permissions.BasePermission):
    
    def has_permission(self, request, view):
        return request.user.is_authenticated
    
    def has_object_permission(self, request, view, obj):
        return (
                request.method in permissions.SAFE_METHODS 
                or obj.author == request.user 
                or request.user.is_staff
        )

@Кирилл_Семенихин, В лекции так написано чтобы было проще понимать логику)

У меня почему то администратору нельзя изменять посты других авторов, если не изменить строку

return obj.author == request.user
на
return obj.author == request.user or request.user.is_staff

и это как бы логично так, как проводится еще проверка на админа, но почему у Вас это работает без проверки?

Изменен Никита Айзиков

@Никита_Айзиков, Действительно, вы правы. Поправили в лекции. Как это работало у нас, я даже сам понять не могу, пытаюсь разобраться.

На странице http://127.0.0.1:8000/api/ у пользователей осталась возможность создавать посты от имени других пользователей, как можно это исправить?

@Марат_Асылбаев, нужно немного изменить наш сериализатор, добавив строчку:

class PostSerializer(serializers.ModelSerializer):
    author = serializers.HiddenField(default=serializers.CurrentUserDefault()) #new
 
    class Meta:
        fields = (
            "id",
            "author",
            "title",
            "body",
            "created",
        )
        model = Post

Этой строчкой, мы автоматически подставляем пользователя, убирая поле ввода.