Django REST framework tutorial 진행 중 스스로 기획하여 추가한 기능을 소개한다.
[기능 완성 소스]
: https://github.com/jseop-lim/drf-tutorial/tree/a83c8bda59590dce0c40add9efb1457d6803321a
[리팩토링 소스]
: https://github.com/jseop-lim/drf-tutorial/tree/f29b2166f63ba4fa7890d81d32103cf8132f7c0f
Django REST framework Tutorial 4: Authentication & Permissions에서는 UserSerializer를 정의하고 User Model과 Snippet Model을 외래키로 연결한다. 이때 튜토리얼에서는 터미널에 createsuperuser
명령어를 입력하는 방식으로 User instance를 생성한다.
왜냐하면 DRF에는 login과 logout view만 구현되어 있고 회원가입 기능은 제공하지 않기 때문이다. 그리하여 API를 통해 클라이언트가 직접 User instance를 생성하는 기능을 추가해보았다.
회원가입은 궁극적으로 User Model의 instance를 생성하는 과정이므로 create action에 해당한다.
클라이언트가 회원가입 URL로 ID(username)과 비밀번호(password)와 함께 POST 요청을 전달하면, Serializer는 이를 User instance로 변환하고 저장(save)하게 된다.
[snippets\serializers.py]
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
password = serializers.CharField(write_only = True) # 추가
class Meta:
model = User
fields = ['id', 'username', 'password', 'snippets'] # password 추가
# 추가
def create(self, validated_data):
user = User.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
)
return user
write_only=True
로 설정한다.serializer.create()
메서드는 create view에서 serializer.save()
가 실행될 때 호출되며, 모델 인스턴스를 생성하는 기능이다. 일반적으로는 MyModel.objects.create()
를 사용하지만, 이러한 방식은 DB에 비밀번호가 그대로 저장되는 문제를 남긴다. 따라서 사용자 비밀번호를 암호화 하는 기능이 포함된 User.objects.create_user()
를 사용한다.tutorial 4의 view 추상화 수준에서는 rest_framework.generics의 Concrete View Classes를 이용하여 UserList, UserDetail views를 구현한다. 그러므로 generics.CreateAPIView
를 상속받는 UserCreate view를 아래와 같이 정의한다.
[snippets\views.py]
class UserCreate(generics.CreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
[config\urls.py]
from django.contrib import admin
from django.urls import path, include
from snippets.views import UserCreate # 추가
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('snippets.urls')),
path('api-auth/', include('rest_framework.urls')),
path('api-auth/signup/', UserCreate.as_view(), name='user_create'), # 추가
]
'user-list
'', 'user-detail'
view가 포함된 snippets\url.py
에 추가하는 방안도 제안했으나, 로그인/로그아웃/회원가입 URL을 한 파일에 모아놓기 위해 전체 프로젝트의 URL 파일에 경로를 작성했다.이후 리팩토링 과정에서 router에 의해 결국
snippets\url.py
로 경로가 옮겨지긴 한다.
Django REST framework Tutorial 6: ViewSets & Routers에서는 ViewSet과 Router를 이용해 한 모델에 대한 여러 actions에 관한 뷰를 하나로 통합하고, router를 이용해 URL Pattern 작성을 단순화한다.
튜토리얼 상에서는 UserList와 UserDetail view를 UserViewSet으로 합친다. 튜토리얼에서 User는 read-only로 취급되기 때문에, ReadOnlyModelViewSet을 상속받아 UserViewSet을 정의한다.
하지만 방금 구현한 회원가입은 User create action을 포함하기 때문에 write 권한을 추가로 가지게 된다. 이를 구현하는 여러 방법이 있다.
비밀번호나 사용자 정보 변경, 회원탈퇴 등 create 이외의 action은 만들지 않을 것이므로 후자를 채택한다.
기존 ReadOnlyModelViewSet를 상속받는 뷰 클래스에 create 메서드를 추가하는 방법도 있으나, 더 짧은 코드를 위해 CreateModelMixin
을 상속받는 방법을 택했다.
[snippets\views.py]
from rest_framework.mixins import CreateModelMixin
(... 생략 ...)
class UserViewSet(viewsets.ReadOnlyModelViewSet, CreateModelMixin):
"""
This viewset automatically provides `list`, `retrieve`, and `create` actions.
+ 회원가입 추가
(GET, POST 요청 허용)
"""
queryset = User.objects.all()
serializer_class = UserSerializer
[참고] rest_framework 소스코드를 보면 두 ModelViewSet의 차이는 상속받는 부모클래스에 불과하다. 따라서 후자의 방법을 택한 것이다.
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet): """ A viewset that provides default `list()` and `retrieve()` actions. """ pass class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet): """ A viewset that provides default `create()`, `retrieve()`, `update()`, `partial_update()`, `destroy()` and `list()` actions. """ pass
다만, 개별 뷰 클래스가 아닌 ViewSet으로 사용자 생성 기능을 작성하면 router에 의해 자동으로 http://127.0.0.1:8000/users/
에서 회원가입을 진행하게 된다. 회원가입 URI는 아래와 같다.
POST /users/