20230427 TIL - django 프레임워크 (4)

ohyujeong·2023년 4월 27일
0

TIL

목록 보기
14/27
post-thumbnail

📖 오늘의 학습

  • django 프레임워크 : User 관리, POSTMAN

User 관리

현재까지 작성한 애플리케이션은 누구나 질문을 추가하고 수정할 수 있다. 이제 User를 생성하여 질문을 생성할 때 질문작성자를 명시하고, 작성자만 수정이 가능하도록 만들어본다.

User는 프로젝트의 settings.pyINSTALLED_APPS 에 정의되어 있는 'django.contrib.auth' 모듈에 구현되어 있다.

앞서 관리자 페이지 로그인을 위한 superuser를 생성할 때에도 별다른 구현 없이 관리자를 생성할 수 있었던 것도 이미 구현된 User가 있는 모듈이 프로젝트에 설정되어 있기 때문이다.

User field 추가

owner 필드는 User 모델의 Primary Key를 참조한다. 하나의 질문이 여러 개의 선택지를 가지는 것처럼, 한 사용자가 여러 질문의 작성자가 된다.

on_delete=models.CASCADE 으로 지정하여 User 객체가 삭제될 때는 연결된 Question 객체도 함께 삭제된다.

polls/models.py
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

User 관리

REST API를 활용하여 UserSerializer 를 생성하고 view 작성, url을 설정한다.

polls_api/serializers.py

Userid 를 통해서 불러들일 수 있는 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

ListAPIViewRetrieveAPIView 클래스에서 필요한 함수들이 이미 구현되어 있어 간단하게 작성할 수 있다.

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
polls_api/urls.py

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를 볼 수 있다.

User 생성

아래와 같이 2가지 방법으로 생성할 수 있다.

  1. django에서 제공하는 기능 UserCreationForm 사용
  2. django의 rest framework Serializer 사용

1. UserCreationForm 사용하여 User 생성

UserCreationForm 는 '유저이름'과 '패스워드', '패스워드 확인' 필드를 제공하며 회원가입 폼을 자동으로 생성한다.

회원가입이 성공적으로 완료된 이후에는 'user-list' 뷰로 리디렉션되어, 새로 생성된 유저가 리스트에 포함되어 있는지 확인할 수 있다.

사용을 위해서는 view에서 django.views 모듈에서 generic 을 import한다. (*rest-framework사용할 때의 generics 와는 다르다)

polls/views.py

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'
polls/templates/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'
polls/urls.py

클래스 구현에 따른 url 설정을 해준다.

from django.urls import path
from . import views
from .views import *

app_name = 'polls'
urlpatterns = [
    ... 
    path('signup/', SignupView.as_view(), )
]
  • UserCreationForm 사용한 signup form

2. django의 rest framework Serializer 사용하여 User 생성

다른 Serializer를 구현했던 것과 같이 serializers.ModelSerializer 를 상속받아 구현한다. 패스워드를 2번 입력받아 유효성을 검사하는 메소드와 그 패스워드를 세팅하여 User를 생성하는 메소드를 구현한다.

polls_api/serializers.py

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']
polls_api/views.py
from polls_api.serializers import RegisterSerializer

class RegisterUser(generics.CreateAPIView):
    serializer_class = RegisterSerializer
polls_api/urls.py
from django.urls import path
from .views import *

urlpatterns = [
    ... 
    path('register/', RegisterUser.as_view()),
]
  • Serializer 사용한 signup form

User 권한관리

django의 rest-framework가 제공하는 authentication api를 사용하여 권한이 있는 User를 확인하고, 권한 유무에 따른 대응을 한다.

polls_api/urls.py

authentication api 사용을 위해 아래와 같이 정해진 코드를 입력한다.

from django.urls import path, include
from .views import *

urlpatterns = [
    ... 
    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/serializers.py

새로운 질문이 생성될 때 owner 필드에 현재 요청을 보낸 사용자가 저장될 수 있도록 한다.

class QuestionSerializer(serializers.ModelSerializer):
    # 필드 추가
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Question
        fields = ['id', 'question_text', 'pub_date', 'owner']
polls_api/permissions.py

질문 상세화면에서도 login 하지 않았으면 질문을 수정하지 못한다. 이 부분은 rest_frameworkpermissions 가 제공하는 클래스를 사용할 수 있지만, 본인이 작성한 것이 아니라면 수정하지 못하도록 하는 제약은 제공되는 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
polls_api/views.py

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

POSTMAN은 RESTful API 테스트를 위한 플랫폼으로, 다양한 HTTP 요청을 보내고 응답 결과를 쉽게 확인할 수 있도록 도와주고 API 요청과 응답 결과를 저장하고 공유할 수 있는 기능도 제공한다.
설치 : https://www.postman.com/


📝 주요메모사항

super()

자식클래스에서 부모클래스의 내용을 사용하고 싶은 경우 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으로 코드를 관리하면서 개발하는 부분에서도 시행착오를 많이 하여 시간이 아주 오~래 걸렸다. 그래도 잘 해결되었고 이제 환경세팅은 문제없이 할 수 있을 것 같다 😁

profile
거친 돌이 다듬어져 조각이 되듯

0개의 댓글