Django3

안재영·2024년 4월 11일

이번엔 전에 작업했던 작업물을 사용하여 권한을 알아봅시다

polls의 question모델을 수정해준뒤

from django.db import models
from django.contrib import admin
from django.utils import timezone
import datetime
# Create your models here.

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 :
            return f'[NEW!!]{self.question_text}'
        return self.question_text

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

    def __str__(self):
        return f'[{self.question.question_text}]{self.choice_text}'

마이크래이션을 진행해줍니다

모델에 owner가 추가되었는데 owner는 user를 참조키로 받으며 해당 참조를한 데이터가 삭제되면 question또한 같이 삭제가되게 설정되어있습니다 null값이 대신들어가는것 또한 가능하네요

serializer도 수정해줍니다

from rest_framework import serializers
from polls.models import Question
from django.contrib.auth.models import User

class QuestionSerializer(serializers.ModelSerializer) :
    class Meta:
        model = Question
        fields=['id','question_text', 'pub_date']

    def create(self, validated_data):
        return Question.objects.create(**validated_data)

    def update(self, instance, validated_data):
            instance.question_text = validated_data.get('question_text', instance.question_text)
            instance.save()
            return instance
    
class UserSerializer(serializers.ModelSerializer):
    question = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())
    class Meta :
        model = User
        fields = ['id','username','question']

views 또한 수정해줍시다

from django.contrib.auth.models import User

class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

유저 목록과 유저 디테일을 들어갔을때 정보가 잘 출력되도록 view를 작업해줍니다

이제 urls를 수정하여 접근할수 있게 만들어줍시다

urlpatterns = [
    path('question/', question_list, 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'),
]

이제 서버를 실행하고 주소/rest/users로 접근하면 유저 정보를 볼수있게되었습니다

이번에는 유저를 추가하기위한 회원가입을 간단하게 구현해봅시다

회원가입 부분을 처리해줄 polls의 view를 작업해줍니다

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'

form_class는 회원가입화면을 처리하는데 도움을줄것이고

success_url 성공시 user-list로 페이지를 이동시킬것이며

template_name 은 간단하게 보여줄 화면을 뜻합니다

이번에는 회원가입을 처리해줄 화면을 작업해줍시다

<h2>회원가입</h2>
 <form method="post">
   {% csrf_token %}
   {{ form.as_p }}
   <button type="submit">가입하기</button>
</form>

UserCreationForm을 이용해서 form.as_p로 간단하게 회원가입폼을 처리할수있게되었습니다

urls를 처리해줍시다

from .views import SignupView

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())
]

SignupView사용하기 위해 views 안에있는 SignupView를 import해줍시다

이제 회원가입 페이지 작업이 끝났습니다

회원가입을 이제 회원가입에 성공하면 rest/users 로 이동하게되어 회원목록을 보여줄것입니다

이번에는 이 기능을 시리얼라이저로 처리해봅시다

일단은 serializer를 추가합니다

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']

RegisterSerializer 해석

password의 값을 validate_password가 통과하는지 확인한뒤 받아옵니다

password2의 값을 받아옵니다

validate에서 password와 password2가 맞는지 확인후 틀릴시 validationError를 반환합니다

맞으면 create를 한뒤 user정보를 return합니다

mata로 model은 User를 사용하고 field는 username, password, password2를 사용합니다

이제 views도 작업해줍시다

from polls_api.serializers import RegisterSerializer

class RegisterUser(generics.CreateAPIView):
    serializer_class = RegisterSerializer

마지막으로 해당기능에 접근하기 위한 urls또한 작업해줍시다

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()),
]

이제 서버에서 확인을하면 시리얼라이저로 회원가입 기능을 구현한것을 확인할수 있습니다

유저가 생겼으니 이번엔 로그인기능을 만들어봅시다

api의 urls로 가서 로그인할때 사용할 path를 추가해줍니다

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'))
]

이제 프로젝트의 setting으로가서 로그인과 로그아웃의 redirect를 설정해줍니다

from django.urls import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('question-list')
LOGOUT_REDIRECT_URL = reverse_lazy('question-list')

이제 로그아웃과 로그인후 question-list의 name을 가진 path로 redirect시켜줍니다

api파일에 permission을 다룰 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
        return obj.owner == request.user

내용은 간단하게 본인의 user정보와 글의 owner가 같지않으면 수정권한을 주지않는 permission입니다

api의 views로 가서 questionList와 Detail에 user로그인여부와 본인이 쓴글만 수정할수있게 permission을 붙여줍니다

from rest_framework import generics, permissions
from .permissions import *

class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer
    permission_classes = {permissions.IsAuthenticatedOrReadOnly, 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)

permission_classes 를 이용하여 로그인 여부와 본인글 여부를 확인하는 부분을 만들어줍시다

list에서는 question을 생성할때 owner에 user의 정보가 들어가게 수정해줍니다

save를 할때는 값을 전달해서 사용할수 있기때문에 serializer.save(owner=self.request.user)를 보면 readonly값인 owner의 값을 전달하여 유저의 네임을 가져다쓸수 잇습니다

시리얼라이즈 파일도 owner의 정보를 받을수 있게 수정을해줍시다

class QuestionSerializer(serializers.ModelSerializer) :
    owner = serializers.ReadOnlyField(source='owner.username')
    class Meta:
        model = Question
        fields=['id','question_text', 'pub_date', 'owner']

이제 서버를 켜서 지금까지 구현한 기능들을 확인해봅시다

로그인도 잘되고 로그인 여부와 권한에 따른 페이지의 변화도 확인할수 있습니다

POSTMAN

포스트맨이란?

API 개발 및 테스트에 사용되는 도구중 하나입니다

포스트맨의 장점

  • 포트스맨을 사용하면 api요청 항목들을 문서화 하여 재확인에 용이합니다
  • 컬랙션을 사용한 api들을 모아 볼수있어서 따로 홈페이지에 방문할필요 없이 재사용하게끔 api를 만들수있습니다
  • 요청 응답UI가 직관적입니다
  • 요청시 코드에 정보를 담는과정이 매우 편리합니다

0개의 댓글