[DEV] 4주차. 장고 활용한 API서버 만들기(4)
1. User 추가 및 관리
polls/models.py
owner = models.ForeignKey('auth.User', related_name='questions', on_delete=models.CASCADE, null=True)
추가
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)
@admin.display(boolean=True, description='최근생성(하루기준)')
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
# 생성된지 하루가 안지났는지
def __str__(self):
if self.was_published_recently():
new_badge = '[NEW]'
else:
new_badge = ''
return f'{new_badge} 제목: {self.question_text}, 날짜: {self.pub_date}'
polls_api/serializers.py
from rest_framework import serializers
from polls.models import 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']
polls_api/views/py
from polls_api.serializers import QuestionSerializer, UserSerializer
from rest_framework import generics
from polls.models import Question
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.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
2. Form으로 User 생성
polls/view.py
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') # urls.py에서 정의했던 이름을 기반으로 url 만들어줌
template_name = 'registration/signup.html'
polls/urls.py
path('signup', SignupView.as_view(), name='signup'),
추가하기
polls/templates/registration/signup.html
<h2>회원가입</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }} <!--form 클래스 사용-->
<button type="submit">가입하기</button>
</form>
회원가입 페이지
가입하기를 누르면 회원 리스트 페이지로 넘어감! reverse_lazy('user-list')
3. Serializer로 User 생성
polls_api/serializers.py
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 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']
extra_kwargs = {'password' : {'write-only':True}}
polls/views.py
from polls_api.serializers import *
from rest_framework import generics
class RegisterUser(generics.CreateAPIView):
serializer_class = RegisterSerializer
polls/urls.py
-path('register/', RegisterUser.as_view()),
추가
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='question-detail'),
path('users/', UserList.as_view(), name='user-list'),
path('users/<int:pk>/', UserDetail.as_view(), name='user-detail'),
path('register/', RegisterUser.as_view()),
]
UserList
view에 유저 생성 기능을 넣지 않고, 새로 만드는 이유- 생성할 때만 필요한 기능을 따로 serializer로 만들고 싶기 때문!
4. User 권한 관리
로그인, 로그아웃
pollst_api/urls.py
path('api-auth/', include('rest_framework.urls')),
추가
mysite/settings.py
from django.urls import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('question-list')
LOGOUT_REDIRECT_URL = reverse_lazy('question-list')
추가
로그인, 로그아웃 버튼 생성됨
권한 관리
polls_api/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: # ('GET', 'HEAD', 'OPTIONS') : 읽을 때
return True
return obj.owner == request.user
polls_api/views.py
- 로그인한 유저만 글을 작성할 수 있도록
- 본인이 작성한 글만 수정하거나 지울 수 있도록
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]
5. perform_create()
polls_api/views.py
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)
- http://127.0.0.1/rest/question/ ->
QuestionList
view로 이동 - QuestionList
- generics.ListCreateAPIView
- mixins.CreateModelMixin
- generics.ListCreateAPIView
- 안에 정의된 메소드들
- def get(self, request, *args, **kwargs)
- def create(self, request, *args, **kwargs)
- def perform_create(self, serializer)
- 지워짐: mixins.CreateModelMixin
- 동작함: QuestionList
- django shell
from polls_api.serializers import QuestionSerializer
question_serializer = QuestionSerializer(data={"question_text":"some text", "onwer":"someone"})
question_serializer.is_valid()
# True
question_serializer.validated_data
# OrderedDict([('question_text', 'some text')])
## owner는 ReadOnly 이기 때문
question = question_serializer.save(id=10000)
question.id
# 10000
## save할 때에는 주어진 값을 그대로 씀
question.question_text
# 'some text'
6. POSTMAN
- 여러 요청을 저장해두었다가 쉽게 재현해서 사용할 수 있어 API 서버 개발에 자주 사용함!
https://www.postman.com/ 다운로드 링크
PUT 요청
Headers에 {“Content-Type”:”application/json”}만 지정하고 PUT 요청을 보냈을 때 Authenticatoin credentials were not provided
에러 발생!
sessionid
와 csrftoken
값을 지정해주어야 함
웹브라우저에서 [개발자도구]-[애플리케이션] 에서 확인 가능 -> 복붙!
Headers 구성 후 정보를 잘 가져온 것을 볼 수 있음
GET 요청
따로 정보를 입력하지 않아도 해당 주소의 정보를 잘 가져온 것을 볼 수 있음