당신은 멋쟁이, 우리는 장고쟁이~

0%

Relationships & Hyperlinked APIs


현재 우리의 API 안에 있는 관계들은 primary key 를 사용하여 표현되어 있습니다.


이번 튜토리얼에서 우리는 우리의 API 의 결속력을 찾기 쉽게 향상 시킬겁니다. 이는 관계들을 하이퍼링크로 잇는 것을 사용하여 이룰수 있습니다.


Creating an endpoint for the root of our API


지금 현재 우리는 snippets 와 users 에 앤드포인트들을 가지고 있습니다. 하지만, 우리는 우리의 API 에 하나의 진입점을 가지고 있지 않습니다. 하나를 생성하기 위해, 우리는 일반 함수형 뷰와 @api_view 데코레이터를 사용할것 입니다.


snippets/views.py 파일을 열고, 아래 내용을 추가해 줍니다.


1
2
3
4
5
6
7
8
9
10
from rest_framework.decorators import api_view 
from rest_framework.response import Response
from rest_framework.reverse import reverse

@api_view(['GET'])
def api_root(request, format=None):
return Response({
'users': reverse('user-list'm request=request, format=format),
'snippets': reverse('snippet-list', request=request, format=format)
})

여기에서 두가지를 알고 넘어가야 합니다.


  1. 우리는 REST FRMAEWORK 의 reverse 함수를 사용하여, 완벽한 URL 을 반환합니다
  2. URL 패턴들은 쉬운 이름으로 정의가 되고, 나중에 snippets/urls.py 에 선언될것 입니다.

Adding login to the Browable API


지금 브라우저를 열어서 브라우저로 API 로 가보면, 더이상 여러분들은 새로운 코드 스니펫을 생성할수 없다는것을 발견할것입니다.



새로운 코드 스니펫을 생성하기 위해서는, 사용자로써 로그인을 해야 합니다.


브라우저로 API 를 사용하기 위해서, 로그인 뷰를 추가해줄수 있습니다.


이는, 프로젝트 레벨의 URLConf 를 수정함으로써 해줄수 있습니다. urls.py 파일을 아래와 같이 수정해 줍니다.


urls.py 를 아래와 같이 수정


1
2
3
4
5
from django.conf.urls import include 

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

api-auth 부분은 우리가 사용하고 싶은 어떤 URL 이름을 써도 괜찮습니다.


이제 브라우저를 다시 열고 새로고침을 하면, Login 링크가 페이지의 오른쪽 상단에 보일것입니다.


여러분들이 이전에 생성한 사용자 디테일로 로그인을 하면, 이제 다시 코드 스니펫을 생성할수 있을것입니다.


화면 오른쪽 상단에 Login 링크 확인



이전 포스팅에서 생성한 사용자로 로그인 진행



로그인 진행후 화면에는, 새로운 코드를 생성할수 있는 필드가 나옴



몇가지 코드 스니펫들을 생성 하였으면, ‘/users/‘ 엔드포인트로 이동하고,

각 사용자에 연결된 snippet ids 의 리스트가 표시되는것을 확인합니다, 각 사용자의 snippets 필드 안에 있습니다.


사용자 1의 디테일



사용자 2의 디테일



Authenticating with the API


우리는 이제 API 에 몇가지 권한 설정들을 가지고 있기 때문에,

우리가 어떠한 스니펫을 수정하고 싶을때에 우리의 요청들을 인증할 필요가 있습니다.


우리는 그 어떠한 authentication 클래스들을 설정해 주지 않았고, 그렇기 때문에 현재는 SessionAuthentication 과 BasicAuthentication 같은 기본값들만 적용 되어 있는 상태 입니다.


웹브라우저를 통해서 API 와 상호 작용할때에, 우리는 로그인 할수 있고. 브라우저 세션은 해당 요청들에 대한 필요한 인증 기능을 제공합니다.


하지만, 만약 우리가 API 를 프로그래밍적으로 이용할때에는, 명시적으로 인증서를 각 요청마다 제공해줘야 합니다.


만약, 터미널에서 인증 없이 snippet 을 생성하고자 한다면 에러가 날것입니다


1
2
3
4
5
http POST http://127.0.0.1:8000/snippets/ code="print(123)"

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

그렇지만, 사용자의 username 과 password 를 포함한 요청을 보내면, 성공적인 결과를 얻을수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
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"
}

정리


이제 우리는, 우리의 웹 API 에 추가로 사용자들의 앤드포인트들에 그리고 그들이 생성한 코드 스니펫에 대한 적당한 권한 설정들을 가지고 있습니다.


Part5 에서는, 어떻게 모든것들이 묶여서 하이라이트된 코드 스니펫들을 HTML endpoint 로 생성할수 있고, 우리의 API 시스템내에 존재하는 관계들을 하이퍼링크들을 사용하여 우리의 API 의 단결성을 향상시킬수 있는지 알아볼겁니다.


Object Level Permissions


실제로 우리는, 모든 코드 스니펫들이 누구에게나 보여지지만 오직 해당 스니펫을 생성한 사용자만 업데이트 혹은 삭제를 할수 있게 만들고 싶습니다.


이것을 하기 위해서는, 커스텀 권한을 생성할 필요가 있습니다.


permissions.py 라는 파일을, snippet 앱 안에 생성해 줍니다. snippets/permissions.py 파일에, 아래 내용을 추가해 줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from rest_framework import permissions 


class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it
"""
def has_object_permissions(self, request, view, obj):
# Read permissions are allowed to any request
# 읽기 권한은 어떤 요청에도 허용됨
# So we'll always allow GET, HEAD or OPTIONS requests
# 따라서, 우리는 언제나 GET, HEAD 혹은 OPTIONS 요청들을 허용함
if request.method in permissions.SAFE_METHODS:
return True

# Write permissions are only allowed to the owner of the snippet
# 해당 snippet 의 owner 만 작성 권한이 허용됨
return obj.owner == request.user

이제 이 커스텀 권한을 우리의 스니펫 인스턴스 앤드포인트에 추가해줍니다. SnippetDetail 뷰 클래스 안에 있는 permission_classes 속성에 추가해 줍니다.


1
2
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]

우리가 작성한 IsOwnerOrReadOnly 클래스를 가져오는것을 까먹으면 안됩니다.


1
from snippets.permissions import IsOwnerOrReadOnly

이제, 브라우저를 다시 열어보면, 코드 스니펫을 생성한 사용자가 로그인 하였을때, DELETEPUT 액션들이 스니펫 인스턴스의 앤드포인트에만 나타나는것을 확인 할수 있습니다.



Adding login to the browsable API


지금 브러우저를 열어서 브라우저로 API 로 접근을 해보면, 더이상 새로운 코드 스니펫을 생성할수 없다는것을 발견할수 있습니다.



새로운 코드 스니펫을 생성하기 위해서는, 사용자로써 로그인을 해야 합니다.


브라우저로 API 를 사용하기 위해서, 로그인 뷰를 추가해 줄수 있습니다.

이는, 프로젝트 레벨의 URLConf 를 수정함으로써 해줄수 있습니다. urls.py 파일을 아래와 같이 수정해 줍니다.


snippets/urls.py 파일말고, 프로젝트 urls.py 파일을 아래와 같이 수정해 줍시다.


1
2
3
4
5
from django.conf.urls import include

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

api-auth/ 부분은 우리가 사용하고 싶은 어떤 URL 이름을 써도 괜찮습니다


이제 브라우저를 다시 열고 새로고침을 하면, login 링크가 페이지의 오른쪽 상단에 볼수 있을겁니다.


여러분들이 이전에 생성한 사용자 디테일로 로그인을 하면, 다시 코드 스니펫을 생성할수 있을것입니다.


*화면 오른쪽 상단에 Login 링크 확인 *



이전 포스팅에서 생성한 사용자로 로그인 진행



로그인 진행후 화면에는, 새로운 코드를 생성할수 있는 필드가 나옵니다



몇가지 코드 스니펫들을 생성하였으면 /users/ 앤드포인트로 이동하고,


각 사용자에 연결된 snippet id 의 리스트가 표시되는것을 확인할수 있습니다. 각 사용자의 snippets 필드 안에 있습니다.


아래는, 사용자 1 의 디테일



사용자 2의 디테일


Adding required permissions to views


이제 코드 스니펫들은 사용자와 연결이 되어 있습니다.


우리는 오직 인증된 사용자들만 스니펫들을 생성하고, 업데이트 하고 삭제할수 있게 만들고 싶습니다.


REST FRAMEWORK 은 우리가 사용할수 있는 몇가지 권한 클래스들을 가지고 있습니다. 이를 사용하여, 해당 뷰를 누가 접근할수 있는지에 대한 제약을 줄수 있습니다.


이번 경우에, IsAuthenticatedOrReadOnly 를 볼수 있습니다. 이 클래스는, 오직 인증이된 요청들만 읽고-쓸수 있는 접근을 할수 있게 해줍니다. 그리고, 인증이 되지 않은 요청들은 오직 읽기전용 접근만 허용됩니다.


IsAuthenticatedOrReadOnly

오직 인증된 요청들만 읽고-쓸수 있는 접근을 가능하게 해줍니다.

인증이 되지 않은 요청들은 오직 읽기전용 접근만 허용됩니다.


snippets/views.py 파일에, 아래와 같이 permissions 를 불러와 줍니다.


1
from rest_framework import permissions

그리고 나서, snippets/views.py 파일 안에 있는 SnippetListSnippetDetail 클래스들에 아래 속성을 추가해 줍니다.


1
permission_class = [permissions.IsAuthenticatedOrReadOnly]

Serializer 업데이트 해주기


이제 snippets 는 해당 snippets 를 생성한 user 와 연계되어 있습니다.


snippets/serializers.pySnippetSerializer 를 업데이트 하여, 이를 반영하도록 해줍니다.


아래 코드를, snippets/serializers.py 파일에 추가 해줍니다.


1
2
3
4
5
6
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')

class Meta:
model = Snippet
fields = ['id', 'title', 'code', 'linenos', 'language', 'style', 'owner']

owner 를 Meta 클래스안에 fields 리스트에 확실하게 추가해 주는걸 잊지 않습니다.


이 필드는 꽤나 재미있는 일을 합니다. source 인자는 어떤 속성이 필드를 채우기 위해 사용되는지 제어합니다. 그리고, 직렬화된 인스턴스에 속성을 가르킬수도 있습니다.


또한, 위에 보이는데로, owner.username 같이 점을 찍은 표현 (dot-notation) 을 인자로 받아서, django 의 템플릿 언어와 같이 주어진 속성들을 파악 합니다.


우리가 추가한 필드는 타이핑이 되지 않는 ReadOnlyField 클래스로, CharField, BooleanField 등과 같은 다른 타이핑된 필드들과는 대조됩니다. 타이핑 되지 않은 ReadOnlyField 는 언제나 읽기 전용이고, 직렬화된 표현으로 사용될것 입니다.


하지만, 이것들이 역직렬화되었을때는, 모델 인스턴스들을 업데이트 할때 사용되지 않을것입니다. 우리는, CharField(read_only=True) 도 이곳에 사용할수도 있었습니다.


Associating Snippets with Users


지금 우리가 코드 스니펫을 생성했다면, 스니펫을 생성한 사용자와 스니펫 인스턴스가 연계되는길은 없습니다. 사용자는 직렬화된 형태를 전달받지 않습니다. 하지만, 들어오는 요청의 속성으로 있습니다.


이것을 해결하기 위해, snippet 뷰에 .perform_create() 메서드를 오버라이딩 해줍니다. 이는, 어떻게 인스턴스 저장이 관리되고, 들어오는 요청 혹은 요청된 URL 안에 숨겨져 있는 정보를 다룰수 있게 해줍니다.


SnippetList 뷰 클래스에, 아래 메서드를 추가해 줍니다.


1
2
3
4
5
6
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer

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

우리의 serializer 의 create() 메서드는, 이제 추가적인 owner필드를 요청으로부터 유효한 데이터와 함께 전달 받습니다.


Adding information to our model


Endpoint 란?

어떠한 소프트웨어나 제품의 최종목적지인 사용자를 가리킴

사전적 의미로는, 연결의 한쪽 끝에 있는 엔티티 (주체)


이제 우리는 작업을 같이 할 몇몇의 사용자가 있습니다. 우리의 API 에 이 사용자들이 표시되게 하는것이 나을것 같습니다. 새로운 serializer 를 생성하는것은 쉽습니다. snippets/serializers.py 파일안에, 아래 내용을 추가해 줍니다.


1
2
3
4
5
6
7
8
from django.contrib.auth.models import User 

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

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

Snippets 들은 User 모델에서 역방향 관계에 있기 때문에 ModelSerializer 클래스를 사용할때에 기본값으로 포함되어 있지 않을것입니다. 따라서, 우리는 명시적으로, 필드를 추가해 주어야 합니다.


Snippet Class 에 ForiegnKey 를 사용하여 User 모델에 연결되어 있기 때문에,


하나의 Snippet 객체에서 연결되어 있는 User 객체를 조회할때는 순방향 (Forward) 라고 하고,

하나의 User 객체에서, 연결되어 있는 Snippet 객체를 조회할때는, 역방향 (Backward or Reverse) 라고 합니다. 따라서, snippets 는 User 모델에서 역방향 관계에 있습니다.


더 읽어보기 »