장고(Django) - 사용자(Users)와 인증(Authentication)
User 추가하기
mysite의 settings.py에서 확인 가능한 'django.contrib.auth'를 사용하여 유저를 불러오고, Question을 조회해 보는 실습을 진행한다.
polls/models.py
Question을 관리할 ForeignKey를 설정하기 위해 owner 변수를 새로 생성하였다.
class Question(models.Model):
question_text = models.CharField(max_length=200, verbose_name='질문', default='')
pub_date = models.DateTimeField(auto_now_add=True, verbose_name='생성일')
owner = models.ForeignKey('auth.User', related_name='questions', on_delete=models.CASCADE, null=True)
...
Shell
위의 owner의 related_name='questions'라고 설정해 주었기에 user.questions.all()로 받아왔지만, 이전에 choice에서는 해당 인자를 적어주지 않았기 때문에 default로 설정되는 q.choice_set.all()로 받아온 것이다.
from django.contrib.auth.models import User
from polls.models import *
User.objects.all()
# <QuerySet [<User: admin>]>
user = User.objects.first()
user.questions.all() # 이전의 q.choice_set.all()과 똑같은 작업
User 관리하기
이전에 만든 QuestionList/Detail Serializer와 더불어 user를 관리할 수 있는 serializer를 만들고 페이지에 접속할 수 있도록 하는 실습을 진행한다.
polls_api/serializers.py
이전에 만들었던 것과 같이 ModelSerializer을 사용하여 Serializer를 만든다. 여기서 serializer는 User가 어떤 Question들을 갖고 있는지 알지 못한다. 그래서 User 모델의 pk값으로 연결된 모든 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
UserSerializer와 generics를 활용하여 페이지에 표시할 정보를 받아오도록 작성한다.
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
페이지에 접속할 수 있도록 users/와 users/<int:pk> path를 추가한다.
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()),
]
Form을 사용하여 User 생성하기
django.views의 generic을 사용하여 User를 생성하는 실습을 한다. REST framework의 generics 하고 다르다는 것을 알고 있자.
polls/views.py
generic과 reverse_lazy를 사용하여 views.py를 손쉽게 구현할 수 있다. reverse_lazy는 리다이렉션을 통해 name이 user-list인 곳으로 이동하게 한다. 즉 유저를 생성하면 이전에 구현했던 페이지로 이동하게 된다.
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
작성한 SignupView의 템플릿을 다음과 같이 작성하였다. form을 제공하기 때문에 제출할 수 있는 버튼만 추가로 구현하였다.
<h2>회원가입</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">가입하기</button>
</form>
polls/urls.py
해당 페이지에 접속할 수 있도록 urls.py에 path를 추가해주었다.
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(), )
]
Serializer를 사용하여 User 생성하기
이번에는 REST framework를 활용한 Serializer를 사용하여 User를 생성하는 실습을 진행한다.
polls_api/serializers.py
먼저 RegisterSerializer를 구현한다. 여기서 password의 validators를 넣지 않으면, 특별한 패스워드 검증 없이 유저가 생성된다. 또한 두 패스워드가 동일한지 확인하는 검증 작업을 위해 validate 메서드로 추가해 주었고, 올바르게 생성이 되도록 create 메서드를 추가해 주었다.
from rest_framework import serializers
from polls.models import Question
from django.contrib.auth.models import User
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)
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
generics를 활용하여 유저를 생성할 수 있도록 작성하였다. CreateAPIView이므로 다른 유저의 정보는 확인할 수 없다.
from polls_api.serializers import RegisterSerializer
class RegisterUser(generics.CreateAPIView):
serializer_class = RegisterSerializer
polls_api/urls.py
가입 페이지의 path를 추가해 주었다.
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()),
path('register/', RegisterUser.as_view()),
]
User 권한 관리
login/logout을 활용할 수 있는 UI를 추가하고, 특정 owner만 해당 Question을 조작하도록 하는 실습을 진행한다.
polls_api/urls.py
먼저 urls.py에 api-auth/라는 path를 추가해 준다.
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()),
path('users/', UserList.as_view(),name='user-list'),
path('users/<int:pk>/', UserDetail.as_view()),
path('register/', RegisterUser.as_view()),
path('api-auth/', include('rest_framework.urls'))
]
mysite/settings.py
mysite의 settings.py에는 login과 logout을 추가하여 리다이렉션이 될 경로를 지정한다.
from django.urls import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('question-list')
LOGOUT_REDIRECT_URL = reverse_lazy('question-list')
polls_api/serializers.py
QuestionSerializer의 필드에 owner를 추가하여 관리자를 표시하고, owner 변수를 통해서 해당 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
permissions.py 파일을 생성하여 다음과 같이 권한을 설정하였다. 해당 Question의 Owner만 수정이 가능하도록 설정한 것이다. 여기서 SAFE_METHODS는 GET, HEAD, OPTIONS이다.
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
위에서 설정한 권한과 더불어 REST framework의 권한을 함께 적용하였다.
from rest_framework import generics,permissions
from .permissions import IsOwnerOrReadOnly
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
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
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
perform_create()
이전에 polls/views.py에서 perform_create 메서드를 만들었다. serializers.py에서 owner를 ReadOnlyField로 지정하였는데 어떻게 perform_create에서는 owner의 값을 user로 지정할 수 있을까?
shell
아래 shell 명령들을 예로 보자. question_serializer에 owner를 넣어 값을 넣어주었다. 그렇지만 validated_data에는 owner가 저장되지 않은 것을 확인할 수 있다. 이런 방식으로는 ReadOnly처럼 동작한다. 그러나 save 메서드를 활용하면 값을 지정해 줄 수 있다. save(id=10000)을 실행해 주면, id 값이 10000으로 저장된 것을 확인할 수 있다. 이처럼 perform_create 메서드에서 save를 사용하여 owner의 값을 넣어준 것이다.
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
OrderedDict([('question_text', 'some text')])
question = question_serializer.save(id=10000)
question.id
# 10000
question.question_text
# 'some text'
POSTMAN
POSTMAN이라는 프로그램을 통해 만들었던 웹에 데이터를 전송하는 실습을 진행한다. 강의 영상 그대로 진행했을 때, csrftoken이 올바르지 않다는 에러가 발생하여 구글링을 통해 tests에 코드를 추가하고 value를 받는 방식을 변경하였다. 아래의 사진들은 question 페이지에 user1 유저가 새로운 질문을 생성하는 작업을 한 것이다.
결론
느낀 점
하루하루 지날수록 내용이 많다는 것을 느낀다. 강의에서 진행하는 것을 그대로 따라 치기만 하니 오류가 거의 없이 진행이 되지만, 막상 프로젝트를 진행하면 거의 모든 부분에서 막히고 오류 천지일 것 같다. 그런 경험으로 성장하는 거겠지만 벌써 두렵다.ㅠㅠ
어려웠던 점
내용 자체는 이해할 수 있었지만, 새로운 메서드를 많이 마주치다 보니 혼란스러운 것 같다. 그래서 지금은 "아 이런 것들이 있구나" 정도로 알아두고, 이후에 팀/개인프로젝트를 통해서 제대로 내 것으로 만드는 작업이 필요할 것 같다.
'[프로그래머스] 데이터 엔지니어링 데브코스 3기 > TIL(Today I Learn)' 카테고리의 다른 글
[TIL - 21일 차] 데이터 웨어하우스와 SQL과 데이터 분석 (1) (0) | 2024.04.22 |
---|---|
[TIL - 15일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (5) (0) | 2024.04.12 |
[TIL - 13일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (3) (0) | 2024.04.10 |
[TIL - 12일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (2) (0) | 2024.04.09 |
[TIL - 11일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (1) (0) | 2024.04.08 |