현재 누구든 code snippet을 수정하거나 삭제할 수 있다.
그래서 다음을 추가할 것이다.
# 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
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()),
아직 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 필드를 추가로 전달할 것이다.
이제 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) 를 대신 사용할 수 있다.
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')),
]
우리는 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]
기존처럼 요청하면 에러가 발생한다.
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"
}