[DRF 튜토리얼] 4. Authentication & Permissions

HL·2021년 1월 29일
0

Django

목록 보기
13/15

현재 누구든 code snippet을 수정하거나 삭제할 수 있다.
그래서 다음을 추가할 것이다.

  • code snippet에 creator 추가
  • 인증된 유저만 snippet을 만들 수 있다
  • creator만 해당 snippet을 수정, 삭제할 수 있다
  • 인증되지 않은 request는 read-only

모델에 owner 추가

# snippets/models.py
class Snippet(models.Model):
    """생략"""
    owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
    highlighted = models.TextField()
    """생략"""

또, 모델이 저장될 때 highlights 필드를 추가하기 위해 추가

# snippets/models.py
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

class Snippet(models.Model):
    """생략"""
    def save(self, *args, **kwargs):
        lexer = get_lexer_by_name(self.language)
        linenos = 'table' if self.linenos else False
        options = {'title': self.title} if self.title else {}
        formatter = HtmlFormatter(style=self.style, linenos=linenos,
                                  full=True, **options)
        self.highlighted = highlight(self.code, lexer, formatter)
        super(Snippet, self).save(*args, **kwargs)
    """생략"""

보통 DB 테이블을 수정하기 위해 create migration 해야 하지만, 이번 튜토리얼의 목적을 위해 DB를 삭제하고 다시 생성한다.

rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate

python manage.py createsuperuser

User 모델 endpoint 추가하기

serializers.py에 유저를 추가한다.

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ['id', 'username', 'snippets']

snippets 은 User 모델과 'reverse' 관계이기 때문에, ModelSerializer 클래스를 사용할 때 자동으로 include 되지 않는다. 그래서 명시적으로 필드를 추가해야 한다.

또, views.py를 수정한다. 우리는 read-only 뷰만을 사용하기 위해, generic 의 ListAPIView 와 RetrieveAPIView 를 사용한다.

from django.contrib.auth.models import User
from snippets.serializers import UserSerializer


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

끝으로 snippets/urls.py 를 수정한다.

path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),

Snippets 과 Users 연결하기

아직 code snippet을 생성하면 user 와 연결되지 않는다. user는 아직 자동으로 serialized 되지 않지만, request의 속성으로 들어온다.

이를 snippet view 에서 .perform_create() 메소드를 오버라이딩함으로써 해결할 수 있다. 이를 통해 인스턴스가 어떻게 저장될지 관리하고 request 로 들어오는 정보를 명확하게 할 수 있다.

SnippetList 뷰 클래스에 추가한다.

def perform_create(self, serializer):
    serializer.save(owner=self.request.user)

이제 serializer 의 create() 메소드가 유효성이 검사된 데이터와 함께 owner 필드를 추가로 전달할 것이다.

Serializer 수정하기

이제 snippet 과 그것을 만든 user 가 연결되었다. 이것을 SnippetSerializer에 반영한다. Meta 클래스에도 추가한다.

owner = serializers.ReadOnlyField(source='owner.username')

source 인자는 어떤 속성이 필드를 채우는데 사용되는지 결정한다. dotted notation 을 사용한다. Template language와 유사하다.

ReadOnlyField 는 CharField 나 BooleanField 와 달리 타입이 없는 클래스이다. 이것은 항상 read-only이고 serializing에 사용되지만, 모델 인스턴스를 deserializing 할 때 수정하는데는 사용되지 않는다. 여기서 CharField(read_only=True) 를 대신 사용할 수 있다.

Views에 필요 권한 설정하기

SnippetList 와 SnippetDetail 에 모두 추가한다.
인증된 유저일 경우 read와 write, 그렇지 않을 경우 read만 가능하다.

from rest_framework import permissions

permission_classes = [permissions.IsAuthenticatedOrReadOnly]

브라우저에 로그인 추가하기

프로젝트의 루트 urls.py에 추가한다.

from django.urls import path, include

urlpatterns += [
    path('api-auth/', include('rest_framework.urls')),
]

Object level 권한 설정

우리는 code snippet을 누구나 볼 수 있지만, 이를 생성한 유저만 수정, 삭제하도록 하고싶다.
custom permission을 위해 snippet 앱에 permission.py를 생성한다.

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True

        return obj.owner == request.user

이를 views.py에 추가한다.

from snippets.permissions import IsOwnerOrReadOnly

permission_classes = [permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly]

API에서 인증하기

기존처럼 요청하면 에러가 발생한다.

http POST http://127.0.0.1:8000/snippets/ code="print(123)"

{
    "detail": "Authentication credentials were not provided."
}

다음처럼 username과 password를 추가하면 된다.

http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"

{
    "id": 1,
    "owner": "admin",
    "title": "foo",
    "code": "print(789)",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}
profile
Swift, iOS 앱 개발을 공부하고 있습니다

0개의 댓글