https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/
4 - Authentication and permissions - Django REST framework
Currently our API doesn't have any restrictions on who can edit or delete code snippets. We'd like to have some more advanced behavior in order to make sure that: Code snippets are always associated with a creator. Only authenticated users may create snipp
www.django-rest-framework.org
API에 대한 접근을 통제해보도록 하자:
- snippets는 만든사람과 연관되어 있다.
- 허가된 users만이 snippets를 create할 수 있다.
- 해당 snippets을 만든 사람만이 이를 update/delete 할 수 있다.
- 허가되지 않은 requests는 full read-only access가 가능하다.
model에 information 추가하기
Snippet model calss에 field를 추가하자:
1. snippet creator을 식별하기 위한 필드
2. highlighted HTML representation of code
# snippets/models.py에 Snippet class에 추가하자
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
그리고 model이 저장되면, pygments(code highlighting library)를 사용하여 highlighted field를 채워야한다.
다음 라이브러리를 import하고, Snippet class에 .save() 메소드를 추가하자
그러면 최종적으로 models.py의 코드는 다음과 같아야 할 것이다.
from django.db import models
from pygments.lexers import get_all_lexers, get_lexer_by_name
from pygments.styles import get_all_styles
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
objects = models.Manager() # because of vscode problem in handling models.objects
class Meta:
ordering = ['created']
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code Snippet
"""
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)
models를 수정했기에, database table을 업데이트해야한다.
원래는 database migration을 하지만,
이 튜토리얼에서는 db를 지우고 다시 만들어보자.
> rm -f db.sqlite3
> rm -r snippets/migrations
> python manage.py makemigrations snippets
> python manage.py migrate
API를 테스트하기 위해 users 몇개를 만들어놓자
> python manage.py createsuperuser
User models에 endpoints 추가하기
앞서 만든 users의 representation을 API에 추가하자.
새로운 serializer을 만들자
# snippets/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']
* serializer의 relation에 대해 더 공부하려면: https://www.django-rest-framework.org/api-guide/relations/
view를 추가하자
user representations를 위한 read-only view를 만들어야하기에 ListAPIView와 RetrieveAPIView generic class-based views를 사용할 것이다. (SnippetList나 SnippetDetail은 create/update/destroy 가능했는데 반해)
# snippets/views.py
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer
from rest_framework import generics
from django.contrib.auth.models import User
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
URL conf에서 참조하게 하자
# snippets/urls.py
from django.urls import path
from snippets import views
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
path('snippets/', views.SnippetList.as_view()),
path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns) # format suffix
Snippets를 User와 연관시키기
code snippet을 create하면, snippet instance만으로는 이를 user과 associate할 방법이 없다.
이를 위해 snippet views에 .perform_create() 메소드를 overriding하는 것이다.
1. instance save 관리되는 방식을 변경시킬 수 있게 하고
2. incoming request나 requested URL에 암시된 information을 다룰 수 있게 한다
SnippetList view class에 메소드를 추가하자
# snippets/views.py
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
serializer 업데이트하기
Snippets를 User와 associate시킨걸
SnippetSerializer을 업데이트해서 반영시켜야 한다.
# snippets/serializers.py
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username') # owner field 추가
class Meta:
model = Snippet
fields = ['id', 'owner', 'title', 'code', 'linenos', 'language', 'style'] # owner field 추가
* source 인자는 어떤 속성이 field를 채울것인지 컨트롤하고, serialized instance에 어떤 속성도 가리킬 수 있다.
* ReadOnlyField class는 serialized representations에 사용되지만, model instances가 deserialized되었을 때 이를 업데이트하는데 사용될 수는 없다. CharField(read_only=True) 로 사용해도 된다.
Views에 required permission 추가하기
required permission: only authenticated users are able to create/update/delete code snippets
IsAuthenticatedOrReadOnly:
- authenticated requests get read-wite access
- unauthenticated requests get read-only access
# snippets/views.py
from rest_framework import permissions # permission library
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly] # add permission
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly] # add permission
Browsable API에 login 추가하기
user로 login하는 기능을 추가해보자
browsable API에서 사용할 수 있는 login view는 URLconf를 수정하면 된다.(project-level의 urls.py)
# tutorial/urls.py
from django.urls import path, include
urlpatterns = [
path('', include('snippets.urls')),
]
# user login
urlpatterns += [
path('api-auth/', include('rest_framework.urls')),
]
Object level permissions
code snippets는 모두가 볼 수 있어야하는 반면, 오직 그 snippet code를 만든 user만 이를 update/delete할 수 있어야 한다.
이를 위해선, custom permission을 만들어야 한다.
* 앞에 required permissions은 로그인한 인증된 user을 말하는 것이지, 이것과는 다른 맥락이다.
snippets/permissions.py파일을 만들고 다음 코드를 넣자
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippets
return obj.owner == request.user # 같은면 True, 다르면 False 반환
그리고 이 custom permissions를 snippet instance endpoint에 추가해야 한다.
SnippetDetail view class에 permission_classes property를 추가하자
# snippets/views.py
from snippets.permissions import IsOwnerOrReadOnly # custom permission
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly] # add permission
테스트해보자
> python manage.py runserver
http://127.0.0.1:8000/snippets/
API로 인증하기
우리는 authenticated class에 대해선 아무것도 setup하지 않았지만,
defaults로 SessionAuthentication과 BasicAuthentication이 적용되어있다.
그래서 브라우저 말고 프로그래밍적으로 API와 interact하기 위해서는, authentication credentials를 명시해야한다:
> http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
'Python > Django' 카테고리의 다른 글
[Django REST framework] 6. ViewSets & Routers (0) | 2020.04.12 |
---|---|
[Django REST framework] 5. Relationships & Hyperlinked APIs (0) | 2020.04.11 |
[Django REST framework] 3. Class-based Views (0) | 2020.04.10 |
[Django REST framework] 2. Requests and Responses (0) | 2020.04.10 |
[Django REST framework] 1. Serialization (0) | 2020.04.08 |