💡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] 결과 확인
'데브코스 > TIL' 카테고리의 다른 글
[TIL] 5주차_Day16: 크롤한 웹데이터로 만들어보는 웹사이트 (1) (0) | 2023.11.06 |
---|---|
[TIL] 4주차_Day15: Django 프레임워크를 사용해서 API서버 만들기(5) (3) | 2023.11.06 |
[TIL] 4주차_Day13: Django 프레임워크를 사용해서 API서버 만들기(3) (1) | 2023.11.04 |
[TIL] 4주차_Day12: Django 프레임워크를 사용해서 API서버 만들기(2) (1) | 2023.11.03 |
[TIL] 4주차_Day11: Django 프레임워크를 사용해서 API서버 만들기(1) (0) | 2023.11.02 |