개발 기록장

04. Python Django 프레임웍을 사용해서 API 서버 만들기(4) 본문

데브코스(DE)/장고 활용한 API 서버 제작

04. Python Django 프레임웍을 사용해서 API 서버 만들기(4)

jxwxnk 2024. 4. 11. 17:13
반응형
학습 주제:  Django Rest Framework(DRF), User 생성 및 관리,  상속(Inheritance)와 오버라이딩(Overriding), POSTMAN

User

: 회원 객체

  • User 추가

         - 회원 객체 추가

 

  • polls/models.py

         - owner 변수에 유저 객체 생성

         - models.ForeignKey(...on_delete=models.CASCADE, ...): owner가 삭제되면 Question도 삭제(외래키 관계)

class Question(models.Model):
    question_text = models.CharField(max_length = 200, verbose_name = "질문")
    pub_data = 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_data >= 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_data}'

 

  • User 관리

         - 회원 기능 어디에/어떻게 사용할 것인가 정의

  • polls_api/serializers.py

         - User객체를 Serialize 해주는 Serializer 생성

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

         - 회원 리스트: ex)http://127.0.0.1:8000/rest/users/

         - 회원 상세(detail) 페이지: ex)http://127.0.0.1:8000/rest/users/1

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(), name='question-detail'),
    path('users/',UserList.as_view(), name = 'user-list'), #추가
    path('users/<int:pk>/',UserDetail.as_view(), name = 'signup'), #추가
]

User 생성

: 회원 가입

  • Form을 통한 User 생성

        - django.contrib.auth에서 지원하는 UserCreation폼을 사용하여 회원가입

  • polls/views.py

         - reverse_lazy: SignupView 내부 작업을 끝내고, 생성된 값을 url로 반환하기 위해 사용(reverse를 lazy하게)

         - template_name: 해당 html에 폼 작성

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') #회원가입 성공 시 '/rest/users'로 이동
    template_name ='registration/signup.html'

 

  • polls/templates/regitstration/signup.html

         - {{form.as_p}}: UserCreationForm 작성

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

 

  • polls/urls.py

         -  http://127.0.0.1:8000/polls/signup/

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>/result/',views.result, name = 'result'),
    path('<int:question_id>/vote/', views.vote, name = 'vote'),
    path('signup/',SignupView.as_view()), #추가
]

signup url 실행 결과

  • Serializer을 통한 User 생성

        - Rest API로  회원가입

 

  • polls_api/serializers.py

         - 패스워드 재확인하고 회원가입

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

         - RegisterSerializer로부터 User 정보 받아와서 generics의 CreateAPIView 상속받아 User 등록

from polls_api.serializers import RegisterSerializer

class RegisterUser(generics.CreateAPIView):
    serializer_class = RegisterSerializer

 

  • polls_api/urls.py

         -  http://127.0.0.1:8000/rest/register/

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(), name='question-detail'),
    path('users/',UserList.as_view(), name = 'user-list'),
    path('users/<int:pk>/',UserDetail.as_view(), name = 'signup'),
    path('register/',RegisterUser.as_view()), #추가
]

register url 실행 결과


User 권한 관리

: 로그인 된 상태 : Create, Read, Update, Delete 모두 가능

: 로그인 안 된 상태 : Read Only

  • permissions

         - 권한 생성

  • polls_api/urls.py

         - 권한 인증을 위한 url

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(), name='question-detail'),
    path('users/',UserList.as_view(), name = 'user-list'),
    path('users/<int:pk>/',UserDetail.as_view(), name = 'signup'),
    path('register/',RegisterUser.as_view()),
    path('api-auth/', include('rest_framework.urls')), #추가
]

 

  • mysite/settings.py

         - 로그인 성공: 'question-list'로 리다이렉트

         - 로그인 실패: 'question-list'로 리다이렉트

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

settins.py

 

  • polls_api/serializers.py

         - 질문 작성자가 아니라면 질문 읽기만 가능함.

class QuestionSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username') #질문 작성자X-> ReadOnly

    class Meta:
        model = Question
        fields = ['id', 'question_text', 'pub_data', 'owner']

 

  • polls_api/permissions.py

         - IsOwnerOrReadOnly: 자기가 작성한 글만 수정할 수 있도록 하는 권한 생성

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

 

  • polls_api/views.py

         - IsAuthenticatedOrReadOnly: 인증된 사용자만 질문 생성 가능 나머지는 읽기만 가능

         - IsOwnerOrReadOnly(;permmisions.py로부터 생성된 권한): 인증된 사용자만 질문 수정 가능 나머지는 읽기만 가능

         - perform_create(): 질문 생성할 때 로그인(인증)된 사용자를 owner로 지정

from polls.models import Question
from polls_api.serializers import *
from rest_framework import generics, permissions
from django.contrib.auth.models import User
from .permissions import IsOwnerOrReadOnly

class QuestionList(generics.ListCreateAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer
    #로그인된 사용자만 질문 생성 가능
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    
    #create할 때 이미 로그인된 사용지를 owner로 지정
    #오버라이드 됨
    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]

상속(Inheritance)와 오버라이딩(Overriding)

: 파이썬으로 개발을 하다보면, 코드의 재사용성과 생산성 증대를 위해 상속과 오버라이딩의 개념을 많이 이용한다.

  • 상속(Inheritance)

         - 부모가 자식에게 재산 또는 지위를 물려줌.

         - 부모 클래스(하나의 클래스)로부터 자식 클래스(다른 클래스)가 메소드와 속성을 물려받아 그대로 사용하는 것.

         - 부모 클래스의 기능을 물려받아 자식 클래스에서 필요한 기능을 추가해 사용

# 부모 클래스: Animal클래스
# 자식 클래스: Dog이라는 클래스
# Animal 클래스 -> Dog 클래스 + walk 메소드

class Animal():
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "동물이 울음소리를 냅니다"
    
class Dog(Animal):
    def walk(self):
        return "산책을 합니다."

 

  • 오버라이딩(Overriding)

         - 부모 클래스(하나의 클래스)로부터 메소드와 속성을 물려받은 자식 클래스(다른 클래스)가 원하는 형태로 내용을 변경하여

            사용하는 것.

         - 덮어쓰기, 재정의라고도 함.

         - 같은 이름의 메서드라도 자식 클래스에서 다른 기능으로 사용될 수 있음.

# 부모 클래스: Animal클래스
# 자식 클래스: Dog이라는 클래스
# Animal 클래스 -> Dog 클래스 + speak 메소드 재정의

class Animal():
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "동물이 울음소리를 냅니다"

class Dog(Animal):
    def walk(self):
        return "산책을 합니다."
    
    #오버라이드
    def speak(self):
        return "멍멍!"

 

  • super()

         - 자식 클래스에서 부모 클래스의 내용을 사용하고 싶은 경우

# 부모 클래스: Animal클래스
# 자식 클래스: Dog이라는 클래스
# Animal 클래스 -> Dog 클래스 + __init__메소드 재정의(super()) + speak 메소드 재정의

class Animal():
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "동물이 울음소리를 냅니다"

class Dog(Animal):

	#오버라이드 && super()
    def __init__(self, name, age):
        super().__init__(name)
        self.age =age
        
    def walk(self):
        return "산책을 합니다."
    
    def speak(self):
        return "멍멍!"

 


POSTMAN

: RESTful API 테스트 플랫폼

: 다양한 HTTP 요청 보내기/응답 결과 확인

: 설치(;POSTMAN 홈페이지)- https://www.postman.com/

 

Postman API Platform | Sign Up for Free

Postman is an API platform for building and using APIs. Postman simplifies each step of the API lifecycle and streamlines collaboration so you can create better APIs—faster.

www.postman.com

 

  • 로그인 X 상태에서 PUT 요청 

         - 브라우저에서는 로그인이 안 된 상태의 PUT 요청이 들어왔을때 어떻게 동작하는 지 알 수없다.

         - POSTMAN을 이용해 결과를 확인한다.

  • PUT 요청

         - Headers: Key - Content-Type    Value - application/json

         - Body: raw - {"question_text":"POSTMAN에서 보는 제목"}

         - 결과: 403 에러 - 인증 실패

로그인X PUT 결과

 

 

  • 로그인 O 상태에서 PUT 요청 

         - session 값을 이용해 로그인 인증하고, PUT 요청

  • PUT 요청

         - Headers: Key - Content-Type              Value - application/json

                            Key - Cookie                          Value - sessionid = {{개발자 도구에서 세션 id 가져오기}}

                            Key - X-CSRFToken              Value - {{개발자 도구에서 세션 CSRF-Token 가져오기}}

                            Key - Cookie                          Value - csrftoken = {{개발자 도구에서 세션 CSRF-Token 가져오기}}

         - Body: raw - {"question_text":"POSTMAN에서 보는 제목"}

         - 결과: 200 OK - PUT 성공

 

로그인O PUT 결과

 

  • GET 요청

         - 위에서 Update 성공한 질문 상세페이지(detail) 확인

         - 결과: 200 OK - GET 성공

결과 확인 GET


공부하며 느낀점

 

클래스를 구현하면서 상속의 강력함을 경험했다. 또 이전에는 권한 문제를 함수로만 해결했는데, Permissions을 사용할 수 있다는 것을 알게 되어 눈을 다시 뜬 기분이다. (지금까지가 거짓말 같다...)

반응형