유닛테스트 통과시켜야겠조?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
우선 처음 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
)
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
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
아래 정의된 필드들과 옵션들은 참고하면 좋을 것같아요.
참고 사이트 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 |