본문 바로가기

데브코스/TIL

[TIL] 4주차_Day14: Django 프레임워크를 사용해서 API서버 만들기(4)

💡Today I Learned

  • 파이썬 장고 프레임워크를 이용한 API 서버 만들기의 네 번째 강의를 진행했습니다.
  • polls_api를 사용하는 User 추가/관리/생성, User에 대한 권한 관리(읽기/쓰기 여부에 따른 페이지 표시), POSTMAN 테스팅에 대한 이론 및 실습

 


 

1. User 추가하기

: User만 모델을 수정할 수 있도록 사용자 추가

 

a. (복습) 관리자(admin) 생성

python manage.py createsuperuser

 

b. auth 앱(장고 디폴트 INSTALLED_APPS, settings.py에서 확인 가능)의 User 모델 불러오기

from django.contrib.auth.models import User

User._meta.get_fields()  # 모델 필드 살펴보기

 

c. Question 모델에 새로운 User 추가 (모델의 스키마 변경 → migration 생성 & migrate 수행하기! _in 장고 쉘)

class Question(models.Model):
    # ...
    owner = models.ForeignKey('auth.User', related_name='questions', on_delete=models.CASCADE, null=True)

Question: 나는 어떤 owner의 소속이에요 (-> 외래키로 참조)

User: 여러 개의 question을 가짐

User : Question = 1 : N

Question : Choice = 1 : N

 

d. user가 가지고있는 question 확인하기

user.questions.all() # 앞서 owner의 related_name='questions'로 지정, user.question_set.all() 이 아닌 user.questions.all()로 확인 가능

: Choice 모델에서는 참조중인 question의 related_name 지정 x  장고에서 자동으로 choice_set = [모델이름]_set 으로 지정

 

2. User 관리하기

: serializers.PrimaryKeyRelatedField = 1(user) : N(question), many=True, 즉 user의 primary_key(=id)를 통해 여러개의 questions를 가지게 됨을 명시해줌

: [Question 테이블]의 user_id(foreign_key)를 통해 가져옴 -> UserSerializer 입장에서는 다른 테이블(Question)의 필드를 찾아봐야 하므로 메타 데이터(class Meta:) 밖에 명시해줌

class UserSerializer(serializers.ModelSerializer):
	questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())
    	class Meta:
    	model = User
    	fields = ['id', 'username', 'questions']

 

3. User 생성하기(1): 장고에서 제공하는 form 기능 사용 (polls)

a. UserCreationForm 클래스를 이용한 SignupView 정의 (views.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에서 정의했던 path의 'name'을 기반으로 url을 만들어줌
    template_name = 'registration/signup.html'

: reverse_lazy('[urls.py 에서 정의한 path의 name]') = 'name'에 해당하는 url을 상대 경로로 연결

: reverse_lazy('user-list') → 'rest/users/' 으로 연결

 

b. view에서 사용할 Template 만들기 (registration.signup.html)

{{ form.as_p }}

: views.py의 SignupView 내 'form_class'를 그대로 사용함

 

c. url과 view 연결해주기 (urls.py)

path('signup/', SignupView.as_view())

 

 

4. User 생성하기(2): 장고 rest framework에서 제공하는 serializer 기능 사용 (polls_api)

a. serializers.ModelSerializer 이용한 RegisterSerializer 정의 (serializers.py)

: UserSerializer (사용자 리스트를 조회하는 시리얼라이저) 와 구분한 이유 = '사용자 등록' 시에만 필요한 '유효한 패스워드인지 확인'하는 기능 제공하기 위함

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)

	# validate 함수 오버라이딩으로 재정의
	def validate(self, attrs):
		if attrs['password'] != attrs['password2']:
			raise serializers.ValidationError({'password': '두 패스워드가 일치하지 않습니다.'})
		return attrs

	# 장고의 User 모델에는 우리가 정의한 'password2'라는 필드는 없음 -> create 함수도 오버라이딩 해주기
	def create(self, validated_data):
		user = User.objects.create(username=validated_data['username'])
		user.set_password(validated_data['password'])  # 'password', 'password2' 중에서 'password'를 set_password로
		user.save()

		return user

	class Meta:
		model = User
		fields = ['username', 'password']

 

b. RegisterUser 뷰 정의 (views.py)

: POST만 구현 → CreateAPIView

 

c. rest/register/ 로 들어오는 url에 대한 view 연결 (urls.py)

: *) polls_api는 localhost/rest/~) !!

 

5. User 권한 관리

a. 로그인/로그아웃 시에도 Question 목록을 보여주는 페이지(=rest/question)으로 리다이렉트 (settings.py)

from django.urls import reverse_lazy

LOGIN_REDIRECT_URL = reverse_lazy('question-list')
LOGOUT_REDIRECT_URL = reverse_lazy('question-list')

: reverse_lazy('question-list') → 'rest/question/' 으로 연결

 

b. serializer에서 'owner' 필드 읽기 전용으로 설정

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

: 로그인한 사용자만 자신의 질문 작성 가능 → owner 필드는 읽기 전용 

: 읽어올 때 사용자의 이름(source='owner.username')으로 표시하기

 

c. Question 등록 시(=.perform_create() 호출 시) 이미 로그인 한 User(=self.request.user)를 owner로 지정하도록 하기

class QuestionList(generics.ListCreateAPIView): # get, post 구현
	# ...

	def perform_create(self, serializer):
		serializer.save(owner=self.request.user)

 

d. 로그아웃 상태에서 질문 생성/수정 = Question.owner 필드가 'User' 인스턴스가 아닌 경우 = perform_create() 호출 시 self.request.user가 null 인 경우 ValueError → 따라서 로그아웃 상태에서는 질문 등록/수정 폼 숨기기

*) 등록: QuestionList

*) 수정: QuestionDetail

from rest_framework import permissions
 
	class QuestionList(generics.ListCreateAPIView): # get, post 구현
		# ...
		permission_classes = [permissions.IsAuthenticatedOrReadOnly]
 
	class QuestionDetail(generics.RetrieveUpdateDestroyAPIView): # get, put, delete 구현
 		# ...
		permission_classes = [permissions.IsAuthenticatedOrReadOnly]
 

: permissions.IsAuthenticatedOrReadOnly = 로그인 된 상태에서만 무언가를 만들 수 있도록

: 로그아웃 상태에서 질문 입력/수정창 사라져있음

: 로그인 상태에서는 질문 입력/수정창 생김

 

e. '내가 만든 질문' 만 수정 가능하도록 설정

: isOwnerOrReadOnly 클래스 생성 (owner가 아니라면 read only _ 수정 불가)

from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission): # permissions.py
	def has_object_permission(self, request, view, obj):
		if request.method in permissions.SAFE_METHODS: # SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
			return True # get, head, options에 대해서는 사용자 확인 x

		return obj.owner == request.user # put(수정) .. 에 대해서는 질문 등록한 사용자인지 확인 o

 

class QuestionDetail(generics.RetrieveUpdateDestroyAPIView): # view.py
	# ...
	permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

 

6. perform_create()

: 부모 클래스로(타고타고 들어가면 CreateModelMixin 클래스에 정의돼있음..)부터 상속받은 .perform_create() 함수 오버라이딩

: QuestionList 클래스에서 재정의(오버라이딩)된 .perform_create()가 동작함

 

*) 주의할 점

: serialize 할 때는 read_only 필드 (id, owner) 는 무시

: 하지만, serializer.save() 시에는 read_only 필드까지 넘겨줄 수 있음 = id, owner 내맘대로 값을 주고 지정이 가능함

 

7. postman

: RESTful API 테스트용 플랫폼, HTTP 요청/응답 결과 확인/저장/공유 ... 기능

: ex) 실제 api 서버가 동작하는 환경에서는 로그인 정보 없이 요청이 들어올 수 있음 → 확인하고싶음

 

a. PUT: 테스트 할 url, Headers/Body 구성 후 Send 해보기

: Content-Type - application/json 을 통해 전달할 값 (Body에서 json 형식으로 question_text 전달)

: Cookie - sessionid='세션 id' 전달 / 개발자도구 - Application - Cookies - sessionid를 통해 (로그인/로그아웃 상태 확인 가능)

: X-CSRFToken - '토큰 값' 전달

: Cookie - csrftoken='토큰 값' 전달

 

b. GET: 동일한 url 작성 후 Send로 [a. PUT] 결과 확인

반응형