[Django REST Framework] Part 2: 사용자(Users)와 인증(Authenitication)

김서연·2024년 4월 22일

Django

목록 보기
7/8

1. User 추가하기

  • user를 추가해 자기가 추가한 질문만 수정할 수 있도록 구현한다
  • 우리가 이전에 만든 것은 superuser
    • python manage.py createsuperuser
  • setting.py
    • admin이 있어서 admin 기능을 사용 가능했던 것

    • auth 은 user를 만들고(createsuperuser) 로그인 할 수 있던 것

      INSTALLED_APPS = [
          "django.contrib.admin",
          "django.contrib.auth",
          ...
  • Django Shell
    >>> from django.contrib.auth.models import User
    >>> User
    <class 'django.contrib.auth.models.User'>
    >>> User._meta.get_fields()
    (<ManyToOneRel: admin.logentry>, <django.db.models.fields.AutoField: id>, <django.db.models.fields.CharField: password>, <django.db.models.fields.DateTimeField: last_login>, <django.db.models.fields.BooleanField: is_superuser>, <django.db.models.fields.CharField: username>, <django.db.models.fields.CharField: first_name>, <django.db.models.fields.CharField: last_name>, <django.db.models.fields.EmailField: email>, <django.db.models.fields.BooleanField: is_staff>, <django.db.models.fields.BooleanField: is_active>, <django.db.models.fields.DateTimeField: date_joined>, <django.db.models.fields.related.ManyToManyField: groups>, <django.db.models.fields.related.ManyToManyField: user_permissions>)
    >>> User.objects.all() # 현재 있는 user
    <QuerySet [<User: admin>, <User: admin2>]>

Question에 owner 필드 값으로 추가

  • polls/models.py
    • owner: auth.User 모델의 PK 참조
    • on_delete: CASCADE 방식으로 진행
    • null 값 허용
    • FK
      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)
          ...
  • cmd
    • 필드 값 추가한 것 업데이트 (migrate)

      (django-venv) C:\Users\mool8\devcourse_week4\mysite>python manage.py makemigrations
      Migrations for 'polls':
        polls\migrations\0002_alter_question_pub_date_alter_question_question_text.py
          - Alter field pub_date on question
          - Alter field question_text on question
      
      (django-venv) C:\Users\mool8\devcourse_week4\mysite>python manage.py migrate
      Operations to perform:
        Apply all migrations: admin, auth, contenttypes, polls, sessions
      Running migrations:
        Applying polls.0002_alter_question_pub_date_alter_question_question_text... OK
  • Django Shell
    • choice를 가져오는 것과 같은 동작

      >>> from django.contrib.auth.models import User
      >>> from polls.models import *
      >>> user = User.objects.first()
      >>> user.questions.all()
      <QuerySet []>
      >>> print(user.questions.all().query)
      SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date", "polls_question"."owner_id" FROM "polls_question" WHERE "polls_question"."owner_id" = 1
      >>> user.id
      1
user.questions.all() ?? 
vs
question.choice_set.all() ??
  • choice_set 의 의미: models에서 그 필드의 related_name을 지정하지 않을 경우 그 모델의 이름에 “_set”을 붙인 형태로 가져올 수 있게 된다
    • question은 FK로써 Choice 모델에 속하기 때문

    • user는 FK로써 Question 모델에 속한다
      - 즉, User에서 Question 모델을 불러올 때 ‘questions’를 사용한다
      - related_name으로 지정했기에

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

2. User 관리하기

  • user별로 만든 question을 확인하고 관리하는 기능 추가
  • serilaizer 만들고 views 관리

공식 문서
Serializer relations - Django REST framework

PrimaryKeyRelatedField

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']
  • Arguments:
    • queryset - The queryset used for model instance lookups when validating the field input. Relationships must either set a queryset explicitly, or set read_only=True.
    • many - If applied to a to-many relationship, you should set this argument to True.
    • allow_null - If set to True, the field will accept values of None or the empty string for nullable relationships. Defaults to False.
    • pk_field - Set to a field to control serialization/deserialization of the primary key's value. For example, pk_field=UUIDField(format='hex') would serialize a UUID primary key into its compact hex representation.
  • Question 테이블의 owner_id를 통해 user를 가져오는 것
  • Serializer 입장에서는 자기 테이블에 있는 정보를 가져오는 게 아니므로 정의가 필요하다
    • polls_api/serializers.py

      from django.contrib.auth.models import User
      class UserSerializer(serializers.ModelSerializer):
          # 여러개 의 question을 가지게 된다 (many=True)
          questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())
      
          class Meta:
              model = User
              fields = ['id', 'username','questions']
  • polls_api/views.py
    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
    from django.urls import path
    from .views import *
    
    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()),
    
    ]
  • User List, Detail 페이지

3. Form을 사용하여 User 생성하기

  • django에서 제공하는 from을 이용해서 회원가입 form 만들기

  • polls/views.py

    • 회원가입 view, 템플릿과 연결

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

    • 객체 생성에 관련된 기능을 제공하는 CreateView를 상속 받는다

    • reverse_lazy('user-list') : url 패턴의 이름을 통해 url을 역으로 찾아주는 함수
      - path('users/', UserList.as_view(), name='user-list')
      - 즉, 회원가입에 성공했을 때 'user-list’ 뷰로 리디렉션

      ```bash
      >>> from django.urls import reverse_lazy
      >>> reverse_lazy('user-list')
      '/rest/users/'
      ```
      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'
  • polls/templates/registration/signup.html

    • {{ form.as_p }}: UserCreationForm을 렌더링한 결과를 출력

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

    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>/vote/', views.vote, name='vote'),
        path('<int:question_id>/result/', views.result, name='result'),
        path('signup/', SignupView.as_view(), )
    ]
  • 회원가입 페이지


4. Serializer를 사용하여 User 생성하기

  • rest framework에서 제공하는 serializer를 이용해서 회원가입 form 만들기

  • API 서버로 구현

  • polls_api/urls.py

    from django.urls import path, include
    from .views import *
    
    urlpatterns = [
        ...
        path('register/', RegisterUser.as_view())
    ]
  • polls_api/serializers.py

    class RegisterSerializer(serializers.ModelSerializer):
        class Meta:
            model = User
            fields = ['username', 'password']
            extra_kwargs = {'password': {'write_only':True}}
  • polls_api/views.py

    class RegisterUser(generics.CreateAPIView):
        serializer_class = RegisterSerializer

    "detail": "Method \"GET\" not allowed."

    • 메소드를 만들 때 CreateAPIView로 만들었기 때문

List 형태의 View로 수정

  • polls_api/views.py
    class RegisterUser(generics.ListCreateAPIView):
        queryset = User.objects.all()
        serializer_class = RegisterSerializer

중복된 User를 추가하려는 경우

  • 웹 상에서 오류 메시지가 발생한다
  • 이것은 User에 같은 사용자 이름이 들어가는 것을 방지하는 코드가 내장되어 있기 때문

간단한 password에 대한 제약 추가

  • polls_api/serializers.py
    • validatos = [validate_password] 를 추가함으로써 간단한 password로는 회원가입할 수 없다

      from django.contrib.auth.password_validation import validate_password
      
      class RegisterSerializer(serializers.ModelSerializer):
          password = serializers.CharField(write_only = True, required = True,
                                              validators = [validate_password])
          class Meta:
              model = User
              fields = ['username', 'password']
              extra_kwargs = {'password': {'write_only':True}}

비밀번호 확인 기능 추가하기

  • polls_api/serializers.py
    • validate() 를 통해 password와 확인용 password가 일치하지 않으면 에러를 발생시킨다

    • create: password2는 원래 User에 없었으므로 만들어준다

      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']
              extra_kwargs = {'password': {'write_only':True}}

5. User 권한 관리

  • 이전에 만든 user로 로그인 후 question을 만들 때 owner를 기록하도록 한다

로그인 버튼 만들기

  • mysite/settings.py
    • 로그인, 로그아웃 버튼을 눌렀을 때 리디렉션할 url 지정

      from django.urls import reverse_lazy
      
      LOGIN_REDIRECT_URL = reverse_lazy('question-list')
      LOGOUT_REDIRECT_URL = reverse_lazy('question-list')
  • polls_api/urls.py
    • rest framework가 제공하는 로그인 페이지 연결

      from django.urls import path,include
      from .views import *
      
      urlpatterns = [
          ...
          path('api-auth/', include('rest_framework.urls'))
      ]

질문을 만들면 owner를 기록하도록 구현하기

  • polls_api/serializers.py
    • 단순히 필드로 지정하는 방법
      class QuestionSerializer(serializers.ModelSerializer):
          class Meta:
              model = Question
              fields = ['id', 'question_text', 'pub_date', 'owner']
      • user를 선택하면 안된다
    • owner를 readonly로 설정
      class QuestionSerializer(serializers.ModelSerializer):
          owner = serializers.ReadOnlyField(source='owner.username')
          
          class Meta:
              model = Question
              fields = ['id', 'question_text', 'pub_date', 'owner']
  • polls_api/views.py
    • 사용자가 잘 지정되도록 설정하기

    • perform_create(): save할 때 owner를 user로 설정하도록

      class QuestionList(generics.ListCreateAPIView):
          queryset = Question.objects.all()
          serializer_class = QuestionSerializer
      
          def perform_create(self, serializer):
              serializer.save(owner=self.request.user)

    • 로그아웃 한 경우, 질문을 POST(생성)하려고 하면 오류가 난다

      → 로그인된 상태에서만 무언가를 만들 수 있도록 해야한다
      class QuestionList(generics.ListCreateAPIView):
          queryset = Question.objects.all()
          serializer_class = QuestionSerializer
          # 로그인된 상태에서만 무언가를 만들 수 있게 된다 (Create)
          permission_classes = [permissions.IsAuthenticatedOrReadOnly]
      
          def perform_create(self, serializer):
              serializer.save(owner=self.request.user)
              
      class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
          queryset = Question.objects.all()
          serializer_class = QuestionSerializer
          # 로그인된 상태에서만 무언가를 만들 수 있게 된다 (Create)
          permission_classes = [permissions.IsAuthenticatedOrReadOnly]

      → 로그아웃했을 때는 form이 보이지 않게 된다

자기가 만든 질문만 수정할 수 있도록 구현하기

  • polls_api/views.py
    • permission_classesIsOwnerOrReadOnly 를 추가한다

      class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
          queryset = Question.objects.all()
          serializer_class = QuestionSerializer
          # 로그인된 상태에서만 무언가를 만들 수 있게 된다 (Create)
          permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

6. perform_create()

pserform_create() 오버라이딩

class QuestionList(generics.ListCreateAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer
    # 로그인된 상태에서만 무언가를 만들 수 있게 된다 (Create)
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

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

QuestionList ← generics.ListCreatieAPIView ← mixins.CreateModelMixin

  • ListCreateAPIView 클래스에서는 post라는 메소드가 구현이 되어 있다. 이를 상속받음으로써 따로 QuestionList에서 정의하지 않아도 사용 가능하다
  • 따라서 post 입력이 들어오면 ListCreateAPIView에 정의된 post를 실행하고, 그 안에서는 create를 정의하는데 이건 ListCreateAPIView가 상속받은 CreateModelMixin이라는 클래스에 정의된 메소드다.
  • 따라서 QuestionList에서 CreateModelMixin에 있는 perform_crate()를 오버라이드해서 사용할 수 있다

ReadOnlyField의 값을 지정하는 방법

그렇다면.. 우리는 아래와 같은 owner는 ReadOnlyField로 정의했는데 어떻게 owner를 지정할 수 있는 것일까?

class QuestionSerializer(serializers.ModelSerializer):
    owner = serializers.**ReadOnlyField**(source='owner.username')
  • owner의 값을 serializer에서 ReadOnlyField로 지정했다
>>> from polls_api.serializers import QuestionSerializer
>>> question_serializer = QuestionSerializer(data={"question_text" : "some text", "owner" : "someone"})
>>> question_serializer.is_valid()
True
>>> question_serializer.validated_data
{'question_text': 'some text'}
  • ReadOnlyField는 인스턴스로부터 읽어올 수만 있다
>>> question = question_serializer.save(id=10000)
>>> question.id
10000
>>> question.question_text
'some text'
  • 하지만, save()를 통해서는 어떤 값으로도 지정할 수 있다. 심지어 id까지도.
  • save할 때는 주어진 필드 값을 그대로 사용하기 때문이다

7. POSTMAN

POSTMAN은 RESTful API 테스트를 위한 플랫폼으로, 다양한 HTTP 요청을 보내고 응답 결과를 쉽게 확인할 수 있도록 도와줍니다. 또한, API 요청과 응답 결과를 저장하고 공유할 수 있는 기능도 제공합니다.

  • API 개발에 많이 사용된다

POSTMAN에서 PUT하기

PUT하는 주체가 수정한 user라는 정보가 없으므로 에러 발생

  • status code: 403은 접근할 수 없는 대상이라는 것

  • session: 웹 서버와 브라우저 간의 상호작용을 통해 사용자의 상태 정보를 저장하고 관리한다

  • 로그인된 상태에서는 아래처럼 sessionid가 존재

    로그아웃하면 sessionid X

  • sessionid를 통해서 로그인이 되어있는지 아닌지를 볼 수 있다

    • 쿠키 값(sessionid, X-CSRFToken)을 채워 넣어 PUT하면 제대로 동작한다


8. RelatedField

PrimaryKeyRelatedField

  • polls_api/serializers.py
    • UserSerializer에 Question을 PrimaryKeyRelatedField로 정의한다

    • questions의 필드로 포함되어 있는 questions의 PK, 즉 id가 보이게 된다

      class UserSerializer(serializers.ModelSerializer):
          # 여러개 의 question을 가지게 된다 (many=True)
          questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())
      		...

StringRelatedField

  • polls_api/serializers.py
    • UserSerializer에 Question을 StringRelatedField로 정의한다

    • question 모델의 str() 메소드에 정의된 대로 보이게 된다

      class UserSerializer(serializers.ModelSerializer):
      		questions = serializers.StringRelatedField(many=True, read_only=True)
      		...

SlugRelatedField

  • polls_api/serializers.py
    • UserSerializer에 Question을 SlugRelatedField로 정의한다

    • question에 있는 필드 중 하나를 골라 보여줄 수 있다

    • slug_field 로 원하는 필드를 지정한다

      class UserSerializer(serializers.ModelSerializer):
      		questions = serializers.SlugRelatedField(many=True, read_only=True, slug_field='pub_date')
      		...

HyperlinkedRelatedField

  • polls_api/serializers.py
    • UserSerializer에 Question을 HyperlinkedRelatedField로 정의한다

    • 특정 view로 이동하는 하이퍼링크를 보여준다

    • view_name 으로 원하는 view를 지정할 수 있다

      class UserSerializer(serializers.ModelSerializer):
      		questions = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='question-detail')
      		...

ChoiceSerializer

  • polls_api/serializers.py
    • 하나의 question는 여러 개의 choice 객체를 가질 수 있다

    • QuestionSerializer에 ChoiceSerializer를 불러와 보여줄 수 있다

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

profile
가보자고! 🔥

0개의 댓글