현재까지 작성한 애플리케이션은 누구나 질문을 추가하고 수정할 수 있다. 이제 User를 생성하여 질문을 생성할 때 질문작성자를 명시하고, 작성자만 수정이 가능하도록 만들어본다.
User는 프로젝트의 settings.py
의 INSTALLED_APPS
에 정의되어 있는 'django.contrib.auth'
모듈에 구현되어 있다.
앞서 관리자 페이지 로그인을 위한 superuser를 생성할 때에도 별다른 구현 없이 관리자를 생성할 수 있었던 것도 이미 구현된 User가 있는 모듈이 프로젝트에 설정되어 있기 때문이다.
owner
필드는 User
모델의 Primary Key를 참조한다. 하나의 질문이 여러 개의 선택지를 가지는 것처럼, 한 사용자가 여러 질문의 작성자가 된다.
on_delete=models.CASCADE
으로 지정하여 User 객체가 삭제될 때는 연결된 Question 객체도 함께 삭제된다.
class Question(models.Model):
question_text = models.CharField(max_length=200, verbose_name='질문')
pub_date = models.DateTimeField(auto_now_add=True, verbose_name='생성일')
# 작성자 필드 추가, 외래키 지정
owner = models.ForeignKey('auth.User', related_name='questions', on_delete=models.CASCADE, null=True)
...
related_name='questions'
으로 명시해줌으로써 User객체에 접근하여 해당 작성자의 질문리스트를 가져올 때 아래와 같이 사용된다.
>>> user = User.objects.first()
>>> user.questions.all() # user가 작성한 질문 모두 가져오기
마이그레이션을 진행하여 DB에 추가한 필드가 반영될 수 있도록 한다.
python manage.py makemigration
python manage.py migrate
REST API를 활용하여 UserSerializer
를 생성하고 view 작성, url을 설정한다.
User
의 id
를 통해서 불러들일 수 있는 question
들을 명시한다.
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())
class Meta:
model = User
fields = ['id', 'username', 'questions']
ListAPIView
와 RetrieveAPIView
클래스에서 필요한 함수들이 이미 구현되어 있어 간단하게 작성할 수 있다.
from django.contrib.auth.models import User
from polls_api.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
url을 리스트-상세 형태로 설정해준다.
from django.urls import path
from .views import *
urlpatterns = [
...
path('users/', UserList.as_view(),name='user-list'),
path('users/<int:pk>/', UserDetail.as_view()),
]
http://127.0.0.1:8000/rest/users/ 로 접속 시 아래와 같이 User List를 볼 수 있다.
아래와 같이 2가지 방법으로 생성할 수 있다.
UserCreationForm
는 '유저이름'과 '패스워드', '패스워드 확인' 필드를 제공하며 회원가입 폼을 자동으로 생성한다.
회원가입이 성공적으로 완료된 이후에는 'user-list' 뷰로 리디렉션되어, 새로 생성된 유저가 리스트에 포함되어 있는지 확인할 수 있다.
사용을 위해서는 view에서 django.views
모듈에서 generic
을 import한다. (*rest-framework사용할 때의 generics 와는 다르다)
generic.CreateView
를 상속받아 클래스로 구현한다.
template_name
에 명시한 경로에 파일을 만들어준다.
from django.views import generic
from django.urls import reverse_lazy
from django.contrib.auth.forms import UserCreationForm
class SignupView(generic.CreateView):
form_class = UserCreationForm
success_url = reverse_lazy('user-list') # url.py에 정의했던 name을 기반으로 url을 만들어준다
template_name = 'registration/signup.html'
{{ form.as_p }}
는 UserCreationForm
을 렌더링한 결과를 출력하는 코드이다.
from django.views import generic
from django.urls import reverse_lazy
from django.contrib.auth.forms import UserCreationForm
class SignupView(generic.CreateView):
form_class = UserCreationForm
success_url = reverse_lazy('user-list') # url.py에 정의했던 name을 기반으로 url을 만들어준다
template_name = 'registration/signup.html'
클래스 구현에 따른 url 설정을 해준다.
from django.urls import path
from . import views
from .views import *
app_name = 'polls'
urlpatterns = [
...
path('signup/', SignupView.as_view(), )
]
다른 Serializer를 구현했던 것과 같이 serializers.ModelSerializer
를 상속받아 구현한다. 패스워드를 2번 입력받아 유효성을 검사하는 메소드와 그 패스워드를 세팅하여 User를 생성하는 메소드를 구현한다.
attrs
는 딕셔너리로 'password'와 'password2'라는 키를 사용하여 입력받은 패스워드와 패스워드 확인용 필드의 값을 가져온다.
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True)
def validate(self, attrs):
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password": "두 패스워드가 일치하지 않습니다."})
return attrs
def create(self, validated_data):
user = User.objects.create(username=validated_data['username'])
user.set_password(validated_data['password'])
user.save()
return user
class Meta:
model = User
fields = ['username', 'password','password2']
from polls_api.serializers import RegisterSerializer
class RegisterUser(generics.CreateAPIView):
serializer_class = RegisterSerializer
from django.urls import path
from .views import *
urlpatterns = [
...
path('register/', RegisterUser.as_view()),
]
django의 rest-framework가 제공하는 authentication api를 사용하여 권한이 있는 User를 확인하고, 권한 유무에 따른 대응을 한다.
authentication api 사용을 위해 아래와 같이 정해진 코드를 입력한다.
from django.urls import path, include
from .views import *
urlpatterns = [
...
path('api-auth/', include('rest_framework.urls'))
]
로그인, 로그아웃 시 리다이렉트되는 경로를 설정한다.
from django.urls import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('question-list')
LOGOUT_REDIRECT_URL = reverse_lazy('question-list')
새로운 질문이 생성될 때 owner 필드에 현재 요청을 보낸 사용자가 저장될 수 있도록 한다.
class QuestionSerializer(serializers.ModelSerializer):
# 필드 추가
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Question
fields = ['id', 'question_text', 'pub_date', 'owner']
질문 상세화면에서도 login 하지 않았으면 질문을 수정하지 못한다. 이 부분은 rest_framework
의 permissions
가 제공하는 클래스를 사용할 수 있지만, 본인이 작성한 것이 아니라면 수정하지 못하도록 하는 제약은 제공되는 permission class가 아니라 따로 permissions.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
# SAFE_METHODS 해당하지 않으면 작성자와 수정하려고 하는 자가 같아야 한다.
return obj.owner == request.user
QuestionList - User
모델의 owner
필드는 읽기 전용이므로 사용자에게 값을 받아서 데이터를 저장할 수 없다. 또한 ListCreateAPIView
에 구현되어 있는 perform_create
함수에는 입력된 데이터를 받아 저장하기만 하므로 해당 함수를 오버라이드 하여 로그인한 user를 owner
필드에 저장할 수 있도록 해야한다.
QuestionDetail - permissions.py 에서 작성한 클래스 사용하여 본인이 작성했을 경우만 수정이 가능하도록 설정한다.
from rest_framework import generics,permissions
from .permissions import IsOwnerOrReadOnly
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
# permission_classes 지정 시 login하지 않았을 경우 질문을 생성할 수 없다
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
# perform_create를 오버라이드하여 데이터를 생성 시 owner가 현재 접속하고 있는 user로 설정되도록 한다.
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
# permissions.py 에서 작성한 클래스 사용
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
POSTMAN은 RESTful API 테스트를 위한 플랫폼으로, 다양한 HTTP 요청을 보내고 응답 결과를 쉽게 확인할 수 있도록 도와주고 API 요청과 응답 결과를 저장하고 공유할 수 있는 기능도 제공한다.
설치 : https://www.postman.com/
자식클래스에서 부모클래스의 내용을 사용하고 싶은 경우 super()
를 사용한다. 중복되는 작업을 하는 경우 super()
로 간단하게 처리할 수 있다.
class Animal():
def __init__(self, name):
self.name = name
def speak(self):
return "동물이 울음소리를 냅니다"
class Dog(Animal):
def __init__(self, name, age):
super().__init__(name) # 부모 __init__메소드에서 name을 할당하고 있으므로 self.name = name 과 같다.
self.age = age
def walk(self):
return "산책을 합니다."
def speak(self):
return "멍멍!"
기능을 구현하기 위한 방법이 여러가지여서 흐름을 이해하는 데에 조금 어려움이 있었다. python 가상환경을 설정하는 것과 git으로 코드를 관리하면서 개발하는 부분에서도 시행착오를 많이 하여 시간이 아주 오~래 걸렸다. 그래도 잘 해결되었고 이제 환경세팅은 문제없이 할 수 있을 것 같다 😁