Users와 Authentication
- User 추가
- User 관리
- Form을 사용하여 User 생성 ( 회원 가입 )
- User 권한 관리
- perform_create()
- POSTMAN
Field 추가 - owner
polls/models.py
# owner field 추가
from django.db import models
from django.utils import timezone
import datetime
from django.contrib import admin
class Question(models.Model):
question_text = models.CharField(max_length=200,verbose_name='질문')
pub_date = models.DateTimeField(auto_now_add=True,verbose_name='생성일')
# on_delete=models.CASCADE : owner가 삭제되면 Question도 삭제해라
# null=True : field가 비어있어도 됨
owner = models.ForeignKey('auth.User', related_name='question', on_delete=models.CASCADE, null=True)
Migration
python manage.py makemigrations
python manage.py migrate
Django Shell에서 확인
from django.contrib.auth.models import User
# 유저 모델을 확인
User._meta.get_fields()
# 어느 유저가 있는지 Query Set으로 확인
User.objects.all()
from polls.models import *
user = User.objects.first()
# user가 갖고 있는 question을 모두 가져옴
# where owner = user.id에 해당하는 question
# related_name = 'questions'로 지정했기에 가능
user.questions.all()
# Choice의 경우,
# 어느 모델도 related_name을 지정하지 않았기에 choice_set으로 자동 설정됨.
question = Question.objects.first()
question.choice_set.all()
polls_api/serializers.py
from rest_framework import serializers
from polls.models import Question
from django.contrib.auth.models import User
...
# User Class 추가
class UserSerializer(serializers.ModelSerializer):
# PrimaryKeyRelatedField : User의 primarykey에 해당하는 여러개의 질문이 있다.
# owner == user.id인 questions를 모두 불러옴
questions = serializers.PrimaryKeyRelatedField(many=True, queryset = Question.objects.all())
class Meta:
model = User
fields = ['id','username','questions']
polls_api/views.py
from polls.models import Question
from polls_api.serializers import QuestionSerializer, UserSerializer
from rest_framework import generics
# 장고 내 모델에 정의된 User를 불러옴
from django.contrib.auth.models import User
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
polls_api/urls.py
from django.urls import path
from .views import *
urlpatterns = [
path('question/', QuestionList.as_view() , name='question-list'),
path('question/<int:pk>/', QuestionDetail.as_view(),name="queston-detail"),
# user 경로 추가
path('users/',UserList.as_view()),
path('users/<int:pk>',UserDetail.as_view()),
]

1. Django에서 제공하는 기능으로 User 생성
( http://127.0.0.1:8000/polls/signup/ )
polls/views.py
...
# django에서 가져온 generic, rest_framework에서 가져온 generics와 다름
from django.views import generic
from django.urls import reverse_lazy
# Form 생성
from django.contrib.auth.forms import UserCreationForm
...
class SignupView(generic.CreateView):
# UserCreationForm : '유저이름'과 '패스워드', '패스워드 확인' 필드를 제공하며,
# 회원가입 폼을 자동으로 생성
form_class = UserCreationForm
# reverse_lazy : urls에서 정의한 name을 기반으로 url찾아서 전달해줌 ex) '/rest/users/'
# 즉, 회원가입 성공시, user-list로 리디렉션 된다.
success_url = reverse_lazy('user-list')
template_name = 'registration/signup.html'
polls/urls.py
from django.urls import path
from . import views
# class인 SignupView를 사용하기 위함
from .views import *
# 앞으로 템플릿에서 url 태그를 사용할 때, 이름 앞에 polls를 붙여줘야함
# App마다 다른 detail url을 사용하기 위함
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/result', views.result, name='result'),
path('<int:question_id>/vote', views.vote, name='vote'),
path('signup/',SignupView.as_view(),),
]
polls/templates/registration/signup.html
<h2>회원가입</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">가입하기</button>
</form>
템플릿에서 사용한 {{ form.as_p }}의 form은 SignupView에서 전달한 form_class의 객체이다.
{{ form.as_p }} : from에 정의한 subject, content 속성에 해당하는 HTML 코드를 자동으로 생성

2. Serializer을 사용하여 User 생성
( http://127.0.0.1:8000/rest/register/ )
polls_api/serializers.py
from rest_framework import serializers
from polls.models import Question
from django.contrib.auth.models import User
# validators를 사용하기 위함
from django.contrib.auth.password_validation import validate_password
...
# 회원 가입
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True)
# Serializer 안에 validate를 구현을 하면 원하는 내용에 대해서 validate를 구현할 수 있음
# password 일치 여부 확인
def validate(self, attrs) :
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password":"두 패스워드가 일치하지 않습니다."})
return attrs
# password2가 create에 들어가지 않게 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']
polls_api/views.py
from polls.models import Question
from polls_api.serializers import *
from rest_framework import generics
# 장고 내 모델에 정의된 User를 불러옴
from django.contrib.auth.models import User
# class 안에서 기능이 전부 구현이 되어있기 때문에, 이를 상속받아 사용
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
# RegisterUser를 UserList에서 한번에 처리하지 않는 이유?
# 별도의 Serializer class를 쓰기 위해서
# 생성할 때 필요한 추가 기능을 Register Serializer에 쓰고 싶기 때문에
class RegisterUser(generics.CreateAPIView):
serializer_class = RegisterSerializer
polls_api/urls.py
from django.urls import path
from .views import *
urlpatterns = [
path('question/', QuestionList.as_view() , name='question-list'),
# GenericAPIView에서 primary key를 pk로 받기 때문에, pk라 해줘야함
path('question/<int:pk>/', QuestionDetail.as_view(),name="queston-detail"),
path('users/',UserList.as_view()),
path('users/<int:pk>',UserDetail.as_view()),
path('register/', RegisterUser.as_view()),
]
( User가 만든 Question이라는 것을 기록하는 기능 구현 )
로그인 기능 및 로그인 버튼 추가
polls_api/urls.py
...
urlpatterns = [
...
# 로그인 버튼 추가
# django.contrib.auth의 login,logout view 사용한다는 의미
path('api-auth/', include('rest_framework.urls'))
]
settings.py
...
# 로그인 다음에 redirect되는 url을 정의
import os
from django.urls import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('question-list')
LOGOUT_REDIRECT_URL = reverse_lazy('question-list')
...

User(로그인 한 사람)가 만든 Question이라는 것을 기록
polls_api/serializers.py ( owner라는 컬럼 추가 )
from rest_framework import serializers
from polls.models import Question
from django.contrib.auth.models import User
from django.contrib.auth.password_validation import validate_password
class QuestionSerializer(serializers.ModelSerializer):
# readonly + 로그인된 사용자 이름이 바로 owner
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Question
# 작성한 사용자가 누구인지 owner 추가
fields = ['id','question_text','pub_date', 'owner']
polls_api/views.py ( 생성 시, owner 기록 )
from polls.models import Question
from polls_api.serializers import *
from rest_framework import generics
from django.contrib.auth.models import User
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
# 생성될 때, 로그인한 사용자를 owner로 지정
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
로그인 시에만 생성, 수정, 삭제할 수 있도록 설정
polls_api/views.py
from polls.models import Question
from polls_api.serializers import *
# permissions 추가
from rest_framework import generics, permissions
from django.contrib.auth.models import User
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
# 로그인 된 상태에서만 Question을 만들 수 있게 지정
# IsAuthenticated : 인증되지 않은 사용자에게 권한을 허용하지 않음
# IsAuthenticatedOrReadOnly : get만 인증되지 않아도 허용해줌,
# get->read, post-> create, put->update, del->delete
# 즉, question 내용을 볼 수 있게 해준다, 단 생성, 수정, 삭제는 못한다.
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]
...
User가 만든 질문만 수정할 수 있도록 권한 설정
polls_api/permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermissions):
def has_object_permission(self, request, view, obj):
# SAFE_METHODS : GET/HEAD/METHOD는 허용, 즉 readonly는 항상 허용하겠다.
if request.method in permissions.SAFE_METHODS:
return True
# SAFE_METHODS가 아닐 때, 객체(question)의 owner가 로그인한 user가 맞는지 확인
else :
return obj.owner == request.user
polls_api/views.py
...
from .permissions import IsOwnerOrReadOnly
...
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
# User가 만든 질문만 수정할 수 있도록 권한 추가
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
polls_api/views.py
class QuestionList(generics.ListCreateAPIView):
...
# 생성될 때, 로그인한 사용자를 owner로 지정
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
이 perform_create()를 자세히 살펴보면
처음에 url 요청이 들어오면, ->
QuestionList로 연결이 되는데,.asview()에 의해서 ->
ListCreateAPIView로 호출이 됨.여기서,
ListCreateAPIView내부에는post함수가 있는데,
이post가create()를 return한다.
또한, 이create()함수 내부에는perform_create()가 정의되어 있기 때문에,이
perform_create()오버라이드 한 것이다.정리하자면,
QuestionList가generics.ListCreateAPIView를 상속 받았고,
ListCreateAPIView는mixins.CreateModeMixin을 상속 받았기 때문에,perform_create()를 오버라이드 할 수 있었다.
serializer.save(owner=self.request.user)
serializer.save()에서 값을 지정하는 경우,
readonly여도 값을 지정할 수 있다.
심지어id(pk)도 지정 가능하기 때문에,
readonly인owner에 값을 지정할 수 있는 것이다.즉,
serialize를 할때는 readonly인 경우 값이 무시되지만,
save()에 직접 선언하면 값 지정이 가능하다.
POSTMAN을 사용하는 이유
Django서버로 열은 웹 사이트에서는 권한이 없어question을 수정 요청할 수 없지만,
이는 화면일 뿐이기 때문에,
실제 API가 동작하는 서버의 환경에서는 얼마든지 수정 요청이 들어올 수 있다.이런 상황을 확인해보기 위해서 POSTMAN을 사용
사용 예시 1
( 로그인 되지않은 상태에서는 권한이 없음을 확인 )
Header 설정

Body 설정

결과

사용 예시 2
( 로그인 시의 sessionid를 가져와 request 시도 )
sessionid, csrftoken (개발자도구)

Header 설정 및 결과

POSTMAN을 사용하면 여러 요청을 저장해뒀다가 쉽게 재현해서 사용할 수 있기에 API 서버 개발에 자주 사용됨.