[drf | token] Build a Backend REST API - 26

Hyeseong·2021년 3월 4일
0
post-custom-banner

Add create token API🥳

유닛테스트 통과시켜야겠조?serializers.py 파일을 작성할게요.

writing serializers.py 😻

파일 작성은 전체적으로 보면 아래와 같이 되요.

from django.contrib.auth import authenticate
from django.utils.translation import ugettext_lazy as _
# Serializer can also be used without a model
# Since this is not a model, we inherit from serializers.Serializer
# This is for an API that will get some data from user, validate it
# and return some value
class AuthTokenSerializer(serializers.Serializer):
    """serializer for user authentication object"""
    # create fields to get data for authentication
    email = serializers.CharField()
    password = serializers.CharField(
                style={'input_type': 'password'},
                trim_whitespace=False
                )

    # override validate method and raise exception if invalid
    def validate(self, attrs):
        # attrs contains all the serializer fields defined above
        email = attrs.get('email')
        password = attrs.get('password')

        user = authenticate(
                request=self.context.get('request'),
                username=email,
                password=password
            )
        if not user:
            # we use gettext to enable language tranlation for this text
            msg = _("Unable to authenticate with credentials provided")
            # pass correct code will raise the relavant http status code
            raise serializers.ValidationError(msg, code='authentication')

        attrs['user'] = user
        return attrs

step -1 🤖

우선 처음 serializers.Serializer 클래스를 상속받아서 AuthTokenSerializer 클래스를 정의할게요.

근데 이런 궁금증이 들것 같은데요.

serializers.ModelSerializer Vs serializers.Serializer 지금 클래스는 이 파일에 2개가 선언되었고 상속을 받은 클래스는 다릅니다.

눈에 보이는 차이점음 클래스변수가 있냐?! 없냐?! 라는 점이종?
AuthTokenSerializer 클래스에는 마치 model에서 작성했던것처럼 CharField()가 보이거나 혹은 django의 form을 작성한것과 같이 소스코드를 짯는데요.

한마디로 serializers.Serializer 모델과 다르게 작성해야 할 부분이 더 많다는 것이고 그것 역시 필요에 의해 작성되는거니 명심하시길.

trim_whitespace=False를 한 이유는 비밀번호에 공백문자 역시 들어갈 가능성이 있기 때문에 False로 해뒀어요.

from django.contrib.auth import get_user_model, authenticate
from django.utils.translation import ugettext_lazy as _
...
...

class AuthTokenSerializer(serializers.Serializer):
    """Serializer for the user authentication object"""
    email = serializers.CharField()
    password = serializers.CharField(
        style={'input_type': 'password'},
        trim_whitespace=False
    )

step -2 🥶

django에서 제공하는 authenticate() 메소드를 활용하여 validation을 할거에요~

validate(self, attrs) 정의를 하는데. 여기서 두 번째 인자인 attrs인자가 뭔지 궁금증이 드네요. 알아보니깐. 앞서저 정의한 필드명 email, password에 해당해요.
그래서 고것들이 키값으로 attrs안에 있는거고~

attrs.get()을 통해서 키-벨류 쌍으로 이루어져있어서 해당 값을 가져와서 authenticate()메서드를 통해서 비교 대조를 하는거에요.

self.context가 보일텐데요. django에서 serializing한 것들을 context변수에 담아버려요.
context안의 키값은 request 객체 하나가 있습니다.

까보면 아래와 같아요.

{'_auth': None,
 '_authenticator': None,
 '_content_type': <class 'rest_framework.request.Empty'>,
 '_data': {'email': 'test@testtest.com',
           'password': 'testpass'},
 '_files': <MultiValueDict: {}>,
 '_full_data': {'email': 'test@testtest.com',
                'password': 'testpass'},
 '_request': <WSGIRequest: POST '/api/user/token/'>,
 '_stream': <WSGIRequest: POST '/api/user/token/'>,
 '_user': <django.contrib.auth.models.AnonymousUser object at 0x7f589f55f210>,
 'accepted_media_type': 'application/json',
 'accepted_renderer': <rest_framework.renderers.JSONRenderer object at 0x7f589f5b5a50>,
 'authenticators': [<rest_framework.authentication.SessionAuthentication object at 0x7f589f5b5d10>,
                    <rest_framework.authentication.BasicAuthentication object at 0x7f589f5b5f50>],
 'negotiator': <rest_framework.negotiation.DefaultContentNegotiation object at 0x7f589f5b5890>,
 'parser_context': {'args': (),
                    'encoding': 'utf-8',
                    'kwargs': {},
                    'request': <rest_framework.request.Request object at 0x7f589f5b5f10>,
                    'view': <user.views.CreateTokenView object at 0x7f589f5b5c50>},
 'parsers': [<rest_framework.parsers.FormParser object at 0x7f589f5b5fd0>,
             <rest_framework.parsers.MultiPartParser object at 0x7f589f5b5910>,
             <rest_framework.parsers.JSONParser object at 0x7f589f5b5d50>],
 'version': None,
 'versioning_scheme': None}

넘어가서 authenticate()메소드의 두 번째 인자,username=email 이렇게 작성된게 보이조. 기본적으로 장고는 username을 기준으로 인증시스템이 구축되어 있기에 꼼수를 써서 email을 대신 넣어 인증되도록 했어요.

만약 authenticate()메서드를 통해서 인증되면 토큰이 발행되겠지만 없으면 None을 던져줘서 if not user 분기에 걸려 버리겠조?

그게 아니면 토큰을 user키의 벨류값으로 심어주게되요.


    def validate(self, attrs):
        """Validate and authenticate the user"""
        email = attrs.get('email')
        password = attrs.get('password')

        user = authenticate(
            request=self.context.get('request'),
            username=email,
            password=password
        )

        if not user:
            msg = _('Unable to authenticate with provided credentials')
            raise serializers.ValidationError(msg, code='authentication')
        
        attrs['user'] = user
        return attrs


step -3 😳

from rest_framework import generics
from rest_framework.authtoken.views import ObtainAuthToken  #NEW
from rest_framework.settings import api_settings			#NEW

from user.serializers import UserSerializer, AuthTokenSerializer #NEW


class CreateUserView(generics.CreateAPIView):
    """Create a new user in the system"""
    serializer_class = UserSerializer


class CreateTokenView(ObtainAuthToken): #NEW
    """Create a new auth token for user"""
    serializer_class = AuthTokenSerializer
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES

Fields and options 🤪

아래 정의된 필드들과 옵션들은 참고하면 좋을 것같아요.

참고 사이트 https://www.programmersought.com/article/15611463603/

Field Field construction
BooleanField BooleanField()
NullBooleanField NullBooleanField()
CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
EmailField EmailField(max_length=None, min_length=None, allow_blank=False)
RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False)
SlugField SlugField(max_length=50, minlength=None, allow_blank=False) 
regular field, verify regular pattern [a-zA-Z0-9
-]+
URLField URLField(max_length=200, min_length=None, allow_blank=False)
UUIDField UUIDField(format='hex_verbose') 
format: 
1) 'hex_verbose' Such as"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 
2) 'hex' Such as "5ce0e9a55ffa654bcee01238041fb31a" 
3)'int' - Such as: "123456789012312313134124512351145145114" 
4)'urn' Such as: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
IPAddressField IPAddressField(protocol='both', unpack_ipv4=False, **options)
IntegerField IntegerField(max_value=None, min_value=None)
FloatField FloatField(max_value=None, min_value=None)
DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)
max_digits: maximum number of digits
decimal_palces: decimal point position
DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None)
TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
DurationField DurationField()
ChoiceField ChoiceField(choices)
choices are the same as Django
MultipleChoiceField MultipleChoiceField(choices)
FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ListField ListField(child=, min_length=None, max_length=None)
DictField DictField(child=)

Option parameters:

parameter name effect
max_length The maximum length
min_lenght Minimum length
allow_blank Whether it is allowed to be empty
trim_whitespace Whether to cut off whitespace characters
max_value Minimum value
min_value Maximum

General parameters:

parameter name Description
read_only Indicates that this field is only used for serialized output, default False
write_only Indicates that this field is only used for deserializing input, default False
required Indicates that the field must be entered when deserializing, default True
default Default value used when deserializing
allow_null Indicates whether the field is allowed to pass in None, the default False
validators The validator used by this field
error_messages Dictionary containing error numbers and error messages
label Field name displayed when used to display API pages in HTML
help_text Field help message displayed when used to display API pages in HTML
profile
어제보다 오늘 그리고 오늘 보다 내일...
post-custom-banner

0개의 댓글