
파이썬 장고 프레임웍을 사용해서 API 서버 만들기(4)
settings.py에 django.contrib.auth가 존재해 유저를 추가하거나 로그인하는 등을 할 수 있다.
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) #on_delete=models.CASCADE : 오너인 유저가 삭제되면 질문도 삭제
@admin.display(boolean=True, description='최근생성(하루기준)')
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
def __str__(self):
return f'제목: {self.question_text}, 날짜: {self.pub_date}'
questions라고 지정된 related_name을 통해 auth.User 모델에서 Question 모델을 참조할 때 questions로 참조가 가능하다.
시리얼라이저로 유저를 관리하는 모델을 만들어보자.
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']
여기서 questions는 User 모델의 Primary Key 값으로 연결된 모든 Question 모델의 오브젝트들을 가져오는 기능을 한다. questions가 가져오는 정보는 User 테이블에 있는 것이 아니기 때문에 해당 코드가 필요하다.
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
path를 추가하자.
from django.urls import path
from .views import *
urlpatterns = [
path('question/', QuestionList.as_view(), name='question-list'),
path('question/<int:pk>/', QuestionDetail.as_view()),
path('users/', UserList.as_view(),name='user-list'),
path('users/<int:pk>/', UserDetail.as_view()),
]
두 가지 방법으로 유저를 생성해보자
장고에서는 유저를 생성할 수 있는 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')
template_name = 'registration/signup.html'
reverse_lazy() : 이름을 통해 URL으로 접근 가능
<h2>회원가입</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">가입하기</button>
</form>
path를 추가하자
from django.urls import path
from . import views
from .views import *
app_name = 'polls'
urlpatterns = [
path('',views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/vote/', views.vote, name='vote'),
path('<int:question_id>/result/', views.result, name='result'),
path('signup/', SignupView.as_view(), )
]
class RegisterSerializer(serializers.ModelSerializer):
# 너무 간단한 password 방지
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True)
# 위의 두 password가 일치하는지 확인
def validate(self, attrs):
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password": "두 패스워드가 일치하지 않습니다."})
return attrs
# 유저모델에는 password2라는 필드가 없어 별도로 create 구현
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
path를 추가하자
from django.urls import path
from .views import *
urlpatterns = [
path('question/', QuestionList.as_view(), name='question-list'),
path('question/<int:pk>/', QuestionDetail.as_view()),
path('users/', UserList.as_view(),name='user-list'),
path('users/<int:pk>/', UserDetail.as_view()),
path('register/', RegisterUser.as_view()),
]
유저가 로그인하고 로그인한 유저가 question을 만들 때 누가 만들었는지 기록하는 기능을 구현해보자.
기록하는 기능은 models.py에서
owner = models.ForeignKey('auth.User', related_name='questions', on_delete=models.CASCADE, null=True)
라고 된 owner 필드에 로그인한 사용자 id를 넣어주면 된다. 이를 위해 로그인 기능을 구현하자.
먼저 path를 추가하자.
from django.urls import path,include
from .views import *
urlpatterns = [
path('question/', QuestionList.as_view(), name='question-list'),
path('question/<int:pk>/', QuestionDetail.as_view()),
path('users/', UserList.as_view(),name='user-list'),
path('users/<int:pk>/', UserDetail.as_view()),
path('register/', RegisterUser.as_view()),
path('api-auth/', include('rest_framework.urls'))
]
settings.py에서 로그인/로그아웃 후 redirect되는 URL을 정의해주자.
from django.urls import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('question-list')
LOGOUT_REDIRECT_URL = reverse_lazy('question-list')
QuestionSerializer를 수정해 질문을 작성한 유저가 owner가 되도록 하자.
class QuestionSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Question
fields = ['id', 'question_text', 'pub_date', 'owner']
질문을 만든다고 해서 owner가 자동 지정이 되는 것은 아니기 때문에 그 기능을 구현하자.
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
로그아웃 했을 때 질문을 작성하지 못하도록 코드를 작성하자.
from rest_framework import generics,permissions
from .permissions import IsOwnerOrReadOnly
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
API서버가 동작하는 상황에서는 로그인 정보 없이 요청이 들어오기도 하는데 이런 상황을 확인하기 위해 POSTMAN을 이용한다.