Django REST framework - Serializers


_“Expanding the usefulness of the serializers is something that we would like to address. However, it’s not a trivial problem, and it will take some serious design work.”
“serializer의 유용성을 확장하는 것은 우리가 다루고자 하는 것입니다.

그러나, 그것은 사소한 문제가 아니며. 심각한 디자인 작업을 필요로 합니다.”
— Russell Keith-Magee


<1. Serializers>

Serializers는 쿼리셋들 및 모델 인스턴스와 같은 복잡한 데이터를 JSON, XML 또는 기타 컨텐트 유형으로 쉽게 렌더링 할 수 있는 Python 기본 데이터 유형으로 변환해 줍니다. 또한 serializer는 deserialization을 제공하여, 들어오는 데이터의 유효성을 처음 확인한 후에 구문 분석 된 데이터를 복합 형식으로 다시 변환 할 수 있습니다.

DJango에서 Client으로 복잡한 데이터(모델 인스턴스 등)를 보내려면 ‘string’으로 변환해야합니다.
이 변환을 serializer 라고 합니다.

반대로 client의 ‘string’을 Djagno로 받을 때
Python 기본 데이터 유형으로 받아야 하는데 이 변환을 deserializer라고 합니다.

REST 프레임워크의 serializers는 Django의 Form 및 modelForm 클래스와 매우 유사하게 작동합니다.

REST 프레임워크는 ModelSerializer(모델 인스턴스와 쿼리셋을 다루는 시리얼라이저를 생성하기 유용한 클래스)뿐만 아니라 응답의 출력을 제어하는 강력하고 일반적인 방법을 제공하는 Serializer 클래스를 제공합니다.

 


1. Declaring Serializers

예제를 위해 사용할 간단한 객체를 만들어 봅니다.

from datetime import datetime

class Comment(object):
    def __init__(self, email, content, created=None):
        self.email = email
        self.content = content
        self.created = created or datetime.now()

comment = Comment(email='leila@example.com', content='foo bar')

comment객체에 해당하는 데이터를 serializer 및 deserializer화하는데 사용할 수 있는 serializer를 선언합니다.

serializer를 선언하면 '양식'을 선언하는 것과 매우 유사합니다.

from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

2. Serializing objects

CommentSerializer를 사용하여 주석 또는 주석 목록을 serializer 할 수 있습니다.

다시 말하면, Serializer클래스를 사용하는 것은 Form클래스를 사용하는 것과 비슷합니다.

serializer = CommentSerializer(comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}

이 시점에서 모델 인스턴스를 파이썬 기본 데이터 유형으로 변환했습니다.

serializer 과정을 마무리하기 위해 데이터를 json으로 렌더링합니다.

from rest_framework.renderers import JSONRenderer

json = JSONRenderer().render(serializer.data)
json
# b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'

3. Deserializing objects

Deserialization도 비슷합니다.

1. 먼저 파이썬 데이터 형식으로 stream을 파싱합니다.

from django.utils.six import BytesIO
from rest_framework.parsers import JSONParser

stream = BytesIO(json)
data = JSONParser().parse(stream)

 

2. 그런 다음 기본 데이터 유형을 검증 된 데이터 dict로 복원합니다.

serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}

4. Saving instances

유효성이 검사 된 데이터를 기반으로 완전한 객체 인스턴스를 반환하려면,

create(), update()메소드 중 하나나 둘 모두를 구현해야합니다.

예를 들어:

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

    def create(self, validated_data):
        return Comment(**validated_data)

    def update(self, instance, validated_data):
        instance.email = validated_data.get('email', instance.email)
        instance.content = validated_data.get('content', instance.content)
        instance.created = validated_data.get('created', instance.created)
        return instance

객체 인스턴스가 Django 모델과 일치하는 경우

이 메소드가 객체를 데이터베이스에 저장하도록 해야합니다.

 

예를 들어, Comment가 Django 모델인 경우 메소드는 다음과 같습니다.

def create(self, validated_data):
        return Comment.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.email = validated_data.get('email', instance.email)
        instance.content = validated_data.get('content', instance.content)
        instance.created = validated_data.get('created', instance.created)
        instance.save()
        return instance

 

1. 이제 데이터를 deserializer 할 때 .save()를 호출하여

2. 유효성이 검사된 데이터를 기반으로 객체 인스턴스를 반환 할 수 있습니다.

comment = serializer.save()

 

1. save()를 호출하면 serializer 클래스를 인스턴스화 할 때

2. 기존 인스턴스가 전달되었는지 여부에 따라 새 인스터스를 만들거나 기존 인스턴스를 업데이트합니다.

# .save() will create a new instance.
serializer = CommentSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)

.create() 및 .update() 메서드는 모두 선택사항입니다.

serializer 클래스의 유사 케이스에 따라 하나 또는 둘 모두 구현 할 수 있습니다.


(1) 추가 속성을 save()에 전달합니다.

1. 인스턴스를 저장하는 시점에

2. 뷰 코드가 데이터를 추가할 수 있어야합니다.

 

이 추가 데이터에는 현재 사용자, 현재 시간 또는 요청 데이터의 일부가 아닌 '다른 정보'가 포함될 수 있습니다.

save()를 호출 할 때 '추가 키워드 인수'를 포함시켜서 그렇게 할 수 있습니다.

serializer.save(owner=request.user)

'추가 키워드 인수'는 create() 또는 update()가 호출 될 때 validated_data인수에 포함됩니다.


(2) save()를 직접 '재정의' 하세요.

어떤 경우에는 create() 및 update() 메소드 이름이 의미가 없을 수 있습니다.

예를 들어, 문의 양식에서 새 인스턴스를 만드는 대신 email이나 다른 메세지를 보냅니다.
이러한 경우에 대신 save()를 직접 읽고 무시할 수 있습니다.

예를 들어:

class ContactForm(serializers.Serializer):
    email = serializers.EmailField()
    message = serializers.CharField()

    def save(self):
        email = self.validated_data['email']
        message = self.validated_data['message']
        send_email(from=email, message=message)

위의 경우 serializer의 .validated_data속성에 직접 액서스해야 합니다.


5. Validation

데이터를 deserializer 할 때 유효성이 검사 된 데이터에 액서스하기 전에 항상

1. is_valid()를 호출하거나

2. 객체 인스턴스를 저장해야 합니다.

3. 유효성 검사 오류가 발생하면, .errors속성에는 결과 오류 메세지를 나타내는 dict이 포함됩니다.

 

예를 들어:

serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']}

1. dict의 각 키는 필드 이름이며, 값은 해당 필드에 해당하는 오류 메시지의 문자열 목록입니다. 

2. non_field_errors키가 있을 수도 있고 일반적인 유효성 검사 오류를 나열합니다.

 

non_field_errors 키의 이름은

REST 프레임 워크 설정의 NON_FIELD_ERRORS_KEY을 사용하여 사용자 정의 할 수 있습니다.

 

항목 목록을 deserializer 할 때 오류는 각 deserializer화 항목을 나타내는 dict 목록으로 반환됩니다.


(1) 유효하지 않은 데이터에 대한 예외 발생

is_valid()메서드는 유효성 검사 오류가 있는 경우

serializers.ValidationError 예외를 발생시키는 선택적 raise_exception 플래그를 사용합니다.

 

이러한 예외는 REST 프레임워크에서 제공하는 기본 예외 처리기에서 자동으로 처리되며,

기본적으로 HTTP 400 ad Request 응답을 반환합니다.

# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)

(2) 필드 레벨 검증

Serializer 서브 클래스에 .validate_<field_name>메소드를 추가하여

custom 필드 레벨 유효성 검증을 지정할 수 있습니다.

이것들은 Django form의 .clean_<field_name>메소드와 비슷합니다.

 

이 메소드는 인수가 필요하며, 유효성 검사가 필요한 필드 값입니다. 

validate_<field_name> 메소드는 유효한 값을 반환하거나 serializers.ValidationError를 발생시켜야합니다.

 

예를 들어:

from rest_framework import serializers

class BlogPostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField()

    def validate_title(self, value):
        """
        Check that the blog post is about Django.
        """
        if 'django' not in value.lower():
            raise serializers.ValidationError("Blog post is not about Django")
        return value

NOTE : <field_name>이 required=False parameter를 사용하여 serializer에서 선언 된 경우, 필드가 포함되어 있지 않으면 이 유효성 검사 단계가 수행되지 않습니다.



(3) 객체 수준 유효성 검사

여러 필드에 대한 액서스가 필요한 다른 유효성 검사를 하려면,

Serializer 서브 클래스에 .validate() 메소드를 추가하세요.

 

이 메소드는 필드 값의 dict인 단일 인수를 취합니다.

필요한 경우 ValidationError를 발생시키거나 유효성 검사 된 값을 반환해야 합니다.

예를 들면:

from rest_framework import serializers

class EventSerializer(serializers.Serializer):
    description = serializers.CharField(max_length=100)
    start = serializers.DateTimeField()
    finish = serializers.DateTimeField()

    def validate(self, data):
        """
        Check that the start is before the stop.
        """
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data

(4) Validators (검사기)

Serializer의 개별 필드는 필드 인스턴스에 선언함으로써 유효성 검사기에 포함할 수 있습니다.

def multiple_of_ten(value):
    if value % 10 != 0:
        raise serializers.ValidationError('Not a multiple of ten')

class GameRecord(serializers.Serializer):
    score = IntegerField(validators=[multiple_of_ten])
    ...

또한 Serializer 클래스에는 전체 필드 데이터 집합에 적용되는 재사용 가능한 유효성 검사기가 포함될 수 있습니다.

이 유효성 검사기는 Meta클래스에 선언함으로써 포함됩니다.

class EventSerializer(serializers.Serializer):
    name = serializers.CharField()
    room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
    date = serializers.DateField()

    class Meta:
        # Each room only has one event per day.
        validators = UniqueTogetherValidator(
            queryset=Event.objects.all(),
            fields=['room_number', 'date']
        )

더 많은 정보는 validators documentation를 참조하세요.


6. Accessing the initial data and instance (초기 데이터 및 인스턴스 액서스)

serializer 인스턴스에 초기 객체 또는 쿼리셋을 전달 할 때 객체는 .instance로 사용 가능합니다.
초기 객체가 전달되지 않으면 .instance 속성이 None이 됩니다.

 

데이터를 serializer 인스턴스에 전달 할 때 수정되지 않은 데이터는 .initial_data로 사용 가능합니다.

data 키워드 인수가 전달되지 않으면 .initial_data속성이 존재 하지 않습니다.


7. Partial updates (부분 업데이트)

기본적으로 serializer는 모든 필수 필드에 값을 전달해야하며 그렇지 않으면 유효성 검사 오류가 발생합니다.

partial 업데이트를 허용하기 위해 partial인수를 사용 할 수 있습니다.

# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)

8. Dealing with nested objects (중첩된 객체 다루기)

앞의 예제는 단순한 데이터 유형만을 가진 객체를 다루는 경우에는 문제가 없지만

객체의 일부 속성이 문자열, 날짜 또는 정수와 같은 단순한 데이터 유형이 아닌 복잡한 객체를 표현할 수 있어야하는 경우가 있습니다.

 

Serializer 클래스 자체는 Field 유형이며,

한 객체 유형이 다른 객체 유형 내에 중첩되어있는 관계를 나타내는데 사용할 수 있습니다.

class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField(max_length=100)

class CommentSerializer(serializers.Serializer):
    user = UserSerializer()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

중첩 된 표현이 None값을 선택적으로 받아 들일 수 있으면 

required=False플래그를 중첩 된 serializer에 전달해야 합니다.

lass CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)  # May be an anonymous user.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

마찬가지로 중첩 표현이 항목 목록이어야하는 경우

중첩 된 serializer에 many=True 플래그를 전달해야 합니다.

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)
    edits = EditItemSerializer(many=True)  # A nested list of 'edit' items.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

9. Writable nested representations (쓰기 가능한 중첩 표현)

데이터의 deserializer를 지원하는 중첩 된 표현을 처리할 때,

중첩 된 객체의 오류는 중첩 된 객체의 필드 이름 아래에 중첩됩니다.

serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}

비슷하게, .validated_data속성은 중첩 된 데이터 구조를 포함합니다.


(1) 중첩 된 표현을 위한 .create() 메소드 작성하기

쓰기 가능한 중첩 표현을 지원하려면 여러 객체를 저장하는 .create() 또는 .update()메소드를 작성해야 합니다.

다음 예제는 중첩 된 프로필 객체가 있는 사용자 만들기를 처리하는 방법을 보여줍니다.

class UserSerializer(serializers.ModelSerializer):
    profile = ProfileSerializer()

    class Meta:
        model = User
        fields = ('username', 'email', 'profile')

    def create(self, validated_data):
        profile_data = validated_data.pop('profile')
        user = User.objects.create(**validated_data)
        Profile.objects.create(user=user, **profile_data)
        return user

(2) 중첩 된 표현을 위해 .update()메소드 작성하기

업데이트의 경우, 관계 업데이트를 처리하는 방법에 대해 신중하게 생각해야합니다.

예를 들어, 관계에 대한 데이터가 없거나 제공되지 않은 경우 어떤 일이 일어나야 할까요?

1. 관계를 데이터베이스에서 NULL로 설정하세요.

2. 연관된 인스턴스를 삭제하세요.

3. 데이터를 무시하고 인스턴스를 있는 그대로 두세요.

4. 유효성 검증 오류를 발생시키세요.

 

다음은 이전 UserSerializer 클래스의 update()메소드 예제입니다.

 def update(self, instance, validated_data):
        profile_data = validated_data.pop('profile')
        # Unless the application properly enforces that this field is
        # always set, the follow could raise a `DoesNotExist`, which
        # would need to be handled.
        profile = instance.profile

        instance.username = validated_data.get('username', instance.username)
        instance.email = validated_data.get('email', instance.email)
        instance.save()

        profile.is_premium_member = profile_data.get(
            'is_premium_member',
            profile.is_premium_member
        )
        profile.has_support_contract = profile_data.get(
            'has_support_contract',
            profile.has_support_contract
         )
        profile.save()

        return instance

중첩 된 생성 및 업데이트의 동작이 애매할 수 있고, 관련 모델간에 복잡한 종속성이 필요할 수 있기 때문에 REST 프레임워크3 에서는 이러한 메서드를 항상 명시적을 작성해야 합니다.

기본적을 ModelSerializer, .create(), .update()메소드는 쓰기 가능한 중첩 표현데 대한 지원을 포함하지 않습니다.

자동으로 기입 가능한 중첩 표현의 일부를 서포트하는 패키지가 3.1 릴리스와 함께 릴리스 될 가능성도 있습니다.


(3) 모델 관리자 클래스에서 관련 인스턴스 저장 처리

serializer에 여러 관련 인스턴스를 저장하는

대신 올바른 인스턴스를 생성하는 custom 모델 관리자 클래스를 작성 할 수 있습니다.

 

예를 들어, User 인스턴스와 Profile 인스턴스가 항상 쌍으로 함께 생성되도록하고 하고 싶다고 가정해보겠습니다.

다음과 같이 custom 매니저 클래스를 작성 할 수 있습니다.

class UserManager(models.Manager):
    ...

    def create(self, username, email, is_premium_member=False, has_support_contract=False):
        user = User(username=username, email=email)
        user.save()
        profile = Profile(
            user=user,
            is_premium_member=is_premium_member,
            has_support_contract=has_support_contract
        )
        profile.save()
        return user

이 관리자 클래스는 이제 전보다 훌륭하게 사용자 인스턴스와 프로필 인스턴스가 항상 동시에 생성된다는 사실을 캡슐화합니다. serializer 클래스의 .create() 메서드를 새 관리자 매서드를 사용하도록 다시 작성 할 수 있습니다.

def create(self, validated_data):
    return User.objects.create(
        username=validated_data['username'],
        email=validated_data['email']
        is_premium_member=validated_data['profile']['is_premium_member']
        has_support_contract=validated_data['profile']['has_support_contract']
    )

이 접근법에 대한 더 자세한 내용은 model managers와 this blogpost on using model and manager classes 을 참조하세요.


10. Dealing with multiple objects

Serializer 클래스는 객체 목록의 serializer 또는 deserializer를 처리 할 수도 있습니다.

(1) 여러 객체 serializer

단일 객체 인스턴스 대신 쿼리셋 또는 객체 목록을 serializer하려면 serializer를 인스턴스화 할 때 many=True플래그를 전달해야 합니다. 그런 다름 serializer 할 쿼리셋이나 객체 목록을 전달 할 수 있습니다.

queryset = Book.objects.all()
serializer = BookSerializer(queryset, many=True)
serializer.data
# [
#     {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'},
#     {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'},
#     {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'}
# ]

(2) 여러 객체를 deserializer

여러 객체를 deserializer화하는 기본동작은 객체 생성을 지원하지만 여러 객체 업데이트를 지원하지 않습니다. 이러한 경우 중 하나를 지원하거나 사용자 지정하는 방법에 대한더 자세한 내용은 ListSerializer를 참조하세요.


11. Including extra context (추가 문맥 포함)

serializer되고 있는 객체에 추가로, serializer에 여분의 문맥을 제공 할 필요가 이는 경우가 있습니다.

한 가지 일반적인 경우는 하이퍼링크 된 관계를 포함하는 serializer를 사용하는 경우이며,

serializer가 현재 요청에 액서스하여 정규화 된 URL을 제대로 생성 할 수 있어야합니다.

serializer를 인스턴스화 할 때 컨텍스트 인수를 전달하여 임의의 추가 컨텍스트를 제공 할 수 있습니다.

예를 들어:

serializer = AccountSerializer(account, context={'request': request})
serializer.data
# {'id': 6, 'owner': u'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}

컨텍스드 dict는

사용자 정의.to_representation()메소드와 같은 serializer 필드 로직 내에서 

self.context속성에 액서스하여 사용할 수 있습니다.

 

 


<2. ModelSerializer>

종종 Django 모델 정의와 밀접하게 매핑되는 serializer 클래스가 필요합니다.

 

ModelSerializer클래스는 모델 필드에 해당하는 필드가 있는 Serializer 클래스를 자동을 만들 수 있는 지름길을 제공합니다.

 

ModelSerializer클래스는 다음을 제외하고는 일반 Serializer 클래스와 동일합니다.

1. 모델을 기반으로 일련의 필드가 자동으로 생성됩니다.

2. unique_together validator와 같은 serializer에 대한 validator를 자동으로 생성합니다.

3. create()와 update()의 간단한 기본 구현을 포함합니다.

 

ModelSerializer 선언은 다음과 같습니다.

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id', 'account_name', 'users', 'created')

1. 기본적으로 클래스의 모든 모델 필드는 해당 serializer 필드에 매핑됩니다.

2. 모델의 외래 키와 같은 관계는 PrimaryKeyRelatedField에 매핑됩니다. 

3. 역직렬화 관계 문서에 명시된대로 명식적으로 포함되지 않으면 기본적으로 역관계가 포함되지 않습니다.


1. Inspecting a ModelSerializer (ModelSerializer을 검사)

Serializer 클래스는 유용한 필드 표현 문자열을 생성하므로, 필드의 상태를 완전히 검사 할 수 있습니다.

이는 자동으로 생성되는 필드 및 유효성 검사기들을 결정하려는 ModelSerializer로 작업 할 때 특히 유용합니다.
이렇게 하려면, Django 쉘을 열어서 serializer 클래스를 가져와서 인스턴스화하고, 객체 표현을 출력하세요.

>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print(repr(serializer))
AccountSerializer():
    id = IntegerField(label='ID', read_only=True)
    name = CharField(allow_blank=True, max_length=100, required=False)
    owner = PrimaryKeyRelatedField(queryset=User.objects.all())

2. Specifying which fields to include (포함 할 필드 지정)

기본 필드의 하위 집합을 모델 serializer에서만 사용하려는 경우 

ModelForm에서와 마찬가지로 필드를 사용하거나 옵션을 제외할 수 있습니다. 

 

fields속성을 사용하여 serializer해야하는 모든 필드를 명시적으로 설정하는 것이 좋습니다.

이렇게하면 모델이 변경 될 때 실수로 데이터가 노출 될 가능성이 줄어 듭니다.

예를 들어:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id', 'account_name', 'users', 'created')

 

또한 fields속성을 특수 값 '__all__'으로 설정하여 모델의 모든 필드를 사용해야 함을 나타낼 수 있습니다.

예들 들면:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = '__all__'

 

serializer에서 제외 할 필드 목록에 exclude속성을 설정할 수 있습니다.

예를 들어:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        exclude = ('users',)

위의 예에서 계정 모델에 account_name, users, created 필드가 3개 있는 경우, 

account_name필드가 만들어지고 serializer되도록 생성됩니다.

 

필드 및 제외 속성의 이름은 일반적으로 모델 클래스의 모델 필드에 매핑됩니다.
또는 필드 옵션의 이름은 모델 클래스에 존재하는 인수를 취하지 않는 속성이나 메서드에 매핑 할 수 있습니다.


3. Specifying nested serialization (중첩 된 serializer 지정)

1. 기본 ModelSerializer는 관계에 기본 키를 사용하지만 

2. depth 옵션을 사용하여 중첩 된 표현을 쉽게 생성 할 수도 있습니다.

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id', 'account_name', 'users', 'created')
        depth = 1

depth옵션은 flat 표현으로 되돌리기 전에 탐색해야하는 관계의 깊이를 나타내는 정수 값으로 설정해야합니다.
serializer가 수행되는 방식을 사용자 정의하려면 필드를 직접 정의해야합니다.


4. Specifying fields explicitly (명시적으로 필드 지정하기)

1. ModelSerializer에 추가 필드를 추가하거나

2. Serializer 클래스에서와 마찬가지로 클래스의 필드를 선언하여 기본 필드를 재정의 할 수 있습니다.

class AccountSerializer(serializers.ModelSerializer):
    url = serializers.CharField(source='get_absolute_url', read_only=True)
    groups = serializers.PrimaryKeyRelatedField(many=True)

    class Meta:
        model = Account

추가 필드는 모델의 모든 속성 또는 호출 가능 항목에 해당 할 수 있습니다.


5. Specifying read only fields (읽기 전용 필드 지정하기)

여러 필드를 읽기 전용으로 지정할 수 있습니다.

각 필드는 read_only=True 특성을 명시적으로 추가하는 대신,

바로가기 메타 옵션인 read_only_fields를 사용할 수 있습니다.

 

이 옵션은 필드 이름의 목록이나 튜플이어야하며 다음과 같이 선언됩니다.

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id', 'account_name', 'users', 'created')
        read_only_fields = ('account_name',)

editable=Falseset 과 AutoField필드가 있는 모델 필드는 기본적으로 읽기 전용으로 설정되며,

read_only_fields옵션에 추가 할 필요가 없습니다.


Note: 읽기 전용 필드가 모델 수준에서 unique_together 제약 조건의 일부인 특별한 경우가 있습니다. 이 경우 필드는 제약 조건의 유효성을 검사하기 위해 serializer 클래스에서 필요하지만 사용자가 편집할 수 없도록 해야합니다.
이를 처리하는 올바른 방법은 read_only=True와 default=...키워드 인수를 제공하여 serializer에서 필드를 명시적으로 지정하는 것입니다.
한가지 예는 현재 인증 된 사용자에 대한 읽기 전용 관계이며다른 식별자와 고유합니다. 이 경우 사용자 필드를 다음과 같이 선언합니다.

user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())

더 자세한 내용은 설명서를 참조하세요.. Validators DocumentationUniqueTogetherValidatorCurrentUserDefault


6. Additional keyword arguments (추가 키워드 인수)

또한 extra_kwargs옵션을 사용하여 필드에 임의의 추가 키워드 인수를 지정할 수 있는 단축키가 있습니다. 

 

read_only_fields의 경우와 마찬가지로,

이것은 serializer에서 필드를 명시적으로 선언 할 필요가 없음을 의미합니다.
이 옵션은 필드 이름을 키워드 인수 dict에 매핑하는 dict입니다.

 

예를 들면:

class CreateUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('email', 'username', 'password')
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = User(
            email=validated_data['email'],
            username=validated_data['username']
        )
        user.set_password(validated_data['password'])
        user.save()
        return user

(1) Relational fields

모델 인스턴스를 serializer 할 때 관계를 나타내기 위해 선택할 수 있는 여러 방법들이 있습니다. 

ModelSerializer의 기본 표현은 관련 인스턴스의 기본 키를 사용하는 것입니다.

 

다른 표현은 하이퍼링크를 사용하여 serializer, 완전한 중첩 표현을 serializer,사용자 정의 표현을 사용하여 serializer하는 것을 포함합니다.
자세한 내용은 serializer relations 참조하세요.


(2) Customizing field mappings (사용자 정의 필드 매핑)

ModelSerializer 클래스는 serializer를 인스턴스화 할 때

serializer 필드가 자동으로 결정되는 방식을 변경하기 위해 재정의 할 수 있는 API도 제공합니다.

 

일반적으로 ModelSerializer가 기본적으로 필요한 필드를 생성하지 않으면 클래스에 명시적으로 추가하거나 대신 일반 Serializer클래스를 사용해야 합니다. 그러나 경우에 따라 특정 모델에 대해 serializer 필드가 생성되는 방식을 정의하는 새 기본 클래스를 만들 수도 있습니다.

.serializer_field_mapping

Django 모델 클래스와 REST 프레임워크 serializer 클래스의 매핑.
이 맵핑을 겹쳐 쓰면 각 모델 클래스에 사용해랴 하는 기본 serializer 클래스를 변경 할 수 있습니다.

.serializer_related_field

이 속성은 기본적으로 관계형 필드에 사용되는 serializer 필드 클래스이어야 합니다.
ModelSerializer의 경우 기본값은 PrimaryKeyRelatedField입니다.
HyperlinkedModelSerializer이 기본값이 serializers.HyperlinkedRelatedField입니다.

serializer_url_field

serializer의 url필드에 사용해야하는 serializer 필드 클래스입니다. serializers.HyperlinkedIdentityField가 기본값입니다.

serializer_choice_field

serializer의 선택 필드에 사용해야하는 serializer 필드 클래스입니다. serializers.ChoiceField가 기본값입니다.

The field_class and field_kwargs API

다음 메서드는 serializer에 자동으로 포함되어야 하는 각 필드의 클래스 및 키워드 인수를 결정하기 위해 호출됩니다. 이 메소드들은 각각 (field_class, field_kwargs)의 두 튜플을 리턴해야합니다.

.build_standard_field(self, field_name, model_field)

표준 모델 필드에 매핑되는 serializer 필드를 생성하기 위해 호출됩니다. 디폴트의 구현은 serializer_field_mapping속성에 근거한 serializer 클래스를 돌려줍니다.

.build_relational_field(self, field_name, relation_info)

관계형 모델 필드에 매핑되는 serializer 필드를 생성하기 위해 호출됩니다.
디폴트의 ​​구현은 serializer_relational_field 속성에 근거한 serializer 클래스를 돌려줍니다.
relation_info 인수는 명명 된 튜플이며 model_field, related_model, to_many 및 has_through_model 속성을 포함합니다.

.build_nested_field(self, field_name, relation_info, nested_depth)

depth옵션이 설정 된 경우, 관계형 모델 필드에 매핑되는 serializer 필드를 생성하기 위해 호출됩니다.
기본 구현은 ModelSerializer또는 HtperlinkedModelSerializer를 기반으로 중첩 된 serializer 클래스를 동적으로 만듭니다.
nested_delth는 depth옵견의 값에서 1을 뺀 값입니다.
relation_info인수는 명명 된 튜플이며, model_field, related_model, to_many, has_through_model속성을 포함합니다.

.build_property_field(self, field_name, model_class)

모델 클래스의 속성 또는 인수가 없는 메서드에 매핑되는 serializer 필드를 생성하기 위해 호출됩니다.
기본 구현은 readOnlyField 클래스를 반환합니다.

.build_url_field(self, field_name, model_class)

serializer 자신의 url 필드에 대한 serializer 필드를 생성하기 위해 호출됩니다. 기본 구현은 HyperlinkedIdentityField 클래스를 반환합니다.

.build_unknown_field(self, field_name, model_class)

필드 이름이 모델 필드 또는 모델 속성에 매핑되지 않았을 때 호출됩니다. 서브 클래스에 의해 이 동작을 사용자 정의해도, 기본 구현에서는 에러가 발생합니다.


<3. HyperlinkedModelSerializer>

HyperlinkedModelSerializer 클래스는 기본 키가 아닌 관계를 나타 내기 위해

하이퍼 링크를 사용한다는 점을 제외하고는 ModelSerializer 클래스와 유사합니다.

기본적으로 serializer에는 기본 키 필드 대신 url 필드가 포함됩니다.

 

url 필드는 HyperlinkedIdentityField serializer 필드를 사용하여 표현되며

모델의 모든 관계는 HyperlinkedRelatedField serializer 필드를 사용하여 표시됩니다.

 

기본 키를 fields 옵션에 추가하여 명시적으로 포함시킬 수 있습니다.

 

예를 들어:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Account
        fields = ('url', 'id', 'account_name', 'users', 'created')

1. Absolute and relative URLs

HyperlinkedModelSerializer를 인스턴스화 할 때는 현재 request을 serializer 컨텍스트에 포함해야합니다. 예를 들면:

serializer = AccountSerializer(queryset, context={'request': request})

이렇게하면 하이퍼링크에 적절한 호스트 이름이 포함될 수 있으므로

결과 표현은 다음과 같은 정규화 된 URL을 사용합니다.

http://api.example.com/accounts/1/

다음과 같은 상대 URL이 아닙니다.

/accounts/1/

상대 URL을 사용하려면 serializer 컨텍스트에서 { 'request': None}을 명시적으로 전달해야합니다.


2. How hyperlinked views are determined (하이퍼링크로 연결된 뷰가 결정되는 방법)

모델 인스턴스에 하이퍼링크하기 위해 어떤 뷰를 사용해야하는지 결정 할 수 있는 방법이 필요합니다.
기본적으로 하이퍼링크는 '{model_name} -detail'스타일과 view 이름과 일치해야하며 pk 키워드 인수로 인스턴스를 찾습니다.
다음과 같이 extra_kwargs 설정에서 view_name과 lookup_field 옵션 중 하나 또는 둘 모두를 사용하여 URL 필드 view 이름 및 조회 필드를 무시할 수 있습니다.

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Account
        fields = ('account_url', 'account_name', 'users', 'created')
        extra_kwargs = {
            'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
            'users': {'lookup_field': 'username'}
        }

또는 serializer에서 필드를 명시적으로 설정할 수 있습니다.

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(
        view_name='accounts',
        lookup_field='slug'
    )
    users = serializers.HyperlinkedRelatedField(
        view_name='user-detail',
        lookup_field='username',
        many=True,
        read_only=True
    )

    class Meta:
        model = Account
        fields = ('url', 'account_name', 'users', 'created')

tip: 하이퍼 링크로 표시된 표현과 URL conf를 적절하게 일치시키는 것은 때로는 약간의 실수 일 수 있습니다. HyperlinkedModelSerializer 인스턴스의 repr을 인쇄하는 것은 관계가 매핑 할 것으로 예상되는 뷰 이름과 조회 필드를 정확하게 검사하는 데 특히 유용합니다.


3. Changing the URL field name

URL 입력란의 이름은 ‘url’로 기본 설정됩니다. URL_FIELD_NAME 설정을 사용하여이를 전역으로 재정의 할 수 있습니다.


<4. ListSerializer>

ListSerializer 클래스는 여러 개체를 한 번에 serialize하고 유효성을 검사하는 동작을 제공합니다.
일반적으로 ListSerializer를 직접 사용할 필요는 없지만 대신 serializer를 인스턴스화 할 때 many=True를 전달해야합니다.
serializer가 인스턴스화되고 many=True가 전달되면 ListSerializer 인스턴스가 만들어집니다. 그런 다음 serializer 클래스는 부모 ListSerializer의 자식이됩니다.
다음 인수는 ListSerializer필드나 many=True로 전달되는 serializer에도 전달할 수 있습니다.

allow_empty

기본적으로 True이지만 빈 입력을 허용하지 않으려면 False로 설정할 수 있습니다.

1. Customizing ListSerializer behavior

ListSerializer 동작을 사용자 정의하려는 경우가 몇 가지 있습니다.

  • 특정 요소가 목록의 다른 요소와 충돌하지 않는지 확인하는 등 목록의 특정 유효성 검사를 제공하려고합니다.
  • 여러 객체의 작성 또는 업데이트 동작을 사용자 정의하려고합니다.

이 경우 serializer 메타 클래스에서 list_serializer_class 옵션을 사용하여 many=True가 전달 될 때 사용되는 클래스를 수정할 수 있습니다.

class CustomListSerializer(serializers.ListSerializer):
    ...

class CustomSerializer(serializers.Serializer):
    ...
    class Meta:
        list_serializer_class = CustomListSerializer

(1) Customizing multiple create(여러 작성 사용자 정의)

여러 객체 생성을위한 기본 구현은 목록의 각 항목에 대해 .create()를 호출하는 것입니다. 이 동작을 사용자 정의하려면 many=True가 전달 될 때 사용되는 ListSerializer 클래스에서 .create() 메서드를 사용자 정의해야합니다.

class BookListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        books = [Book(**item) for item in validated_data]
        return Book.objects.bulk_create(books)

class BookSerializer(serializers.Serializer):
    ...
    class Meta:
        list_serializer_class = BookListSerializer

(2) Customizing multiple update(여러 업데이트 맞춤 설정)

기본적으로 ListSerializer 클래스는 다중 업데이트를 지원하지 않습니다. 이는 삽입 및 삭제에 대해 예상되는 동작이 모호하기 때문입니다.
여러 업데이트를 지원하려면 명시적으로 업데이트해야합니다. 여러 개의 업데이트 코드를 작성할 때 다음 사항을 염두에 두십시오.

  • 데이터 목록의 각 항목에 대해 어떤 인스턴스를 업데이트해야하는지 어떻게 결정합니까?
  • 삽입을 어떻게 처리해야합니까? 그것들은 유효하지 않습니까? 아니면 새로운 objects를 만드나요?
  • 제거물은 어떻게 처리해야합니까? 객체 삭제 또는 관계 제거를 의미합니까? 그들은 조용히 무시해야합니까, 아니면 무효합니까?
  • 주문은 어떻게 처리해야합니까? 두 항목의 위치 변경은 상태 변경을 의미합니까 아니면 무시됩니까?

인스턴스 serializer에 명시적으로 id 필드를 추가해야합니다. 기본적으로 생성된 id필드는 read_only로 표시됩니다. 이로 인해 업데이트시 제거됩니다. 명시적으로 선언하면 목록 serializer의 update 메소드에서 사용할 수 있습니다.
다음은 여러 업데이트를 구현하는 방법에 대한 예입니다.

class BookListSerializer(serializers.ListSerializer):
    def update(self, instance, validated_data):
        # Maps for id->instance and id->data item.
        book_mapping = {book.id: book for book in instance}
        data_mapping = {item['id']: item for item in validated_data}

        # Perform creations and updates.
        ret = []
        for book_id, data in data_mapping.items():
            book = book_mapping.get(book_id, None)
            if book is None:
                ret.append(self.child.create(data))
            else:
                ret.append(self.child.update(book, data))

        # Perform deletions.
        for book_id, book in book_mapping.items():
            if book_id not in data_mapping:
                book.delete()

        return ret

class BookSerializer(serializers.Serializer):
    # We need to identify elements in the list using their primary key,
    # so use a writable field here, rather than the default which would be read-only.
    id = serializers.IntegerField()

    ...
    id = serializers.IntegerField(required=False)

    class Meta:
        list_serializer_class = BookListSerializer

REST 프레임워크2에 있는 allow_add_remove 동작과 유사한 업데이트 작업에 대한 것은 3.1 릴리스에 포함될 수 있습니다.


(3) Customizing ListSerializer initialization (ListSerializer 초기화 사용자 정의)

many=True가 있는 serializer가 인스턴스화되면 자식 serializer 클래스와 상위 ListSerializer 클래스 모두에 대해 .__ init __() 메서드에 전달할 인수 및 키워드 인수를 결정해야합니다.
디폴트의 ​​구현은, validator를 제외 해, 양쪽 모두의 클래스에 모든 인수를 건네주는 것입니다. 양쪽 모두는 customizer 키워드의 인수입니다. 양쪽 모두, 아이디 serializer 클래스를 대상으로하고 있습니다. 때때로 many=True가 전달 될 때 하위 클래스와 부모 클래스의 인스턴스화 방법을 명시적으로 지정해야 할 수도 있습니다. many_init 클래스 메소드를 사용하면 그렇게 할 수 있습니다.

 @classmethod
    def many_init(cls, *args, **kwargs):
        # Instantiate the child serializer.
        kwargs['child'] = cls()
        # Instantiate the parent list serializer.
        return CustomListSerializer(*args, **kwargs)

<5. BaseSerializer>

BaseSerializer는 serializer와 deserializer 스타일을 쉽게 지원하는데 사용할수 있는 대안입니다.
이 클래스는 Serializer 클래스와 같은 기본 API를 구현합니다.

  • .date - 발신 기보 표현을 반환합니다.
  • .is_valid() - 들어오는 데이터를 serializer 해제와 검증합니다.
  • .validated_data - 검증 된 수신 데이터를 리턴합니다.
  • .errors - 검증 중에 에러를 반환합니다.
  • .save() - 검증 된 데이터를 객체 인스턴스에 저장합니다.

serializer 클래스에서 지원할 기능에 따라 무시할 수있는 네 가지 메서드가 있습니다.

  • .to_representation() - 읽기 조작을 위해 serializer를 지원하려면 이를 오버라이드하세요.
  • .to_internal_value() - 쓰기 조작을 위해 deserializer를 지원하려면 오버라이드하세요.
  • .create(), .update() - 인스턴스 저장을 지원하기 위해 이들 중 하나나 둘다 무시하세요.

이 클래스는 Serializer 클래스와 동일한 인터페이스를 제공하기 때문에 일반 Serializer 또는 ModelSerializer에서 사용하던 것과 똑같이 기존 CBV와 함께 사용할 수 있습니다.
BaseSerializer 클래스는 browsable API에서 HTML 양식을 생성하지 않는다는 점에서 주목할 것입니다. 이는 반환하는 데이터에 각 필드를 적절한 HTML 입력으로 렌더링 할 수 있는 모든 필드 정보를 포함하지 않기 때문입니다.


1. Read-only BaseSerializer classes

BaseSerializer 클래스를 사용하여 읽기 전용 serializer를 구현하려면 .to_representation() 메서드를 재정의해야합니다. 간단한 Django 모델을 사용하는 예제를 살펴 보겠습니다.

class HighScore(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    player_name = models.CharField(max_length=10)
    score = models.IntegerField()

HighScore 인스턴스를 원시 데이터 유형으로 변환하기위한 읽기 전용 serializer 컨버터를 만드는 것은 간단합니다.

class HighScoreSerializer(serializers.BaseSerializer):
    def to_representation(self, obj):
        return {
            'score': obj.score,
            'player_name': obj.player_name
        }

이제 이 클래스를 사용하여 단일 HighScore 인스턴스를 serializer 할 수 있습니다.

@api_view(['GET'])
def high_score(request, pk):
    instance = HighScore.objects.get(pk=pk)
    serializer = HighScoreSerializer(instance)
    return Response(serializer.data)

또는 이를 사용하여 여러 인스턴스를 serializer할 수 있습니다.

@api_view(['GET'])
def all_high_scores(request):
    queryset = HighScore.objects.order_by('-score')
    serializer = HighScoreSerializer(queryset, many=True)
    return Response(serializer.data)

2. Read-write BaseSerializer classes

읽기-쓰기 serializer를 만들려면 먼저 .to_internal_value()메서드를 구현해야 합니다. 이 메서드는 객체 인스턴스를 구성하는데 사용 될 유효성이 있는 값을 반환하고 제공된 데이터가 잘못된 형식인 경우 ValidationError를 발생시킬 수 있습니다.
.to_internal_value()를 구현하면 serializer에서 기본 유효성 검사 API를 사용할 수 있으며 .is_valid(), .validated_data 및 .errors를 사용할 수 있습니다.
.save()도 지원하려면 .create() 및 .update() 메소드 중 하나 또는 모두를 구현해야합니다.
다음은 읽기-쓰기 작업을 모두 지원하도록 업데이트 된 이전의 HighScoreSerializer의 전체 예제입니다.

class HighScoreSerializer(serializers.BaseSerializer):
    def to_internal_value(self, data):
        score = data.get('score')
        player_name = data.get('player_name')

        # Perform the data validation.
        if not score:
            raise ValidationError({
                'score': 'This field is required.'
            })
        if not player_name:
            raise ValidationError({
                'player_name': 'This field is required.'
            })
        if len(player_name) > 10:
            raise ValidationError({
                'player_name': 'May not be more than 10 characters.'
            })

        # Return the validated values. This will be available as
        # the `.validated_data` property.
        return {
            'score': int(score),
            'player_name': player_name
        }

    def to_representation(self, obj):
        return {
            'score': obj.score,
            'player_name': obj.player_name
        }

    def create(self, validated_data):
        return HighScore.objects.create(**validated_data)

3. Creating new base classes

BaseSerializer 클래스는 특정 serializer 스타일을 처리하거나 대체 저장 장치 백엔드와 통합하기 위해 새 generic serializer 클래스를 구현하려는 경우에도 유용합니다.
다음 클래스는 임의의 객체를 기본 자료형으로 강제 변환 할 수 있는 일반 serializer 컨버터의 예입니다.

class ObjectSerializer(serializers.BaseSerializer):
    """
    A read-only serializer that coerces arbitrary complex objects
    into primitive representations.
    """
    def to_representation(self, obj):
        for attribute_name in dir(obj):
            attribute = getattr(obj, attribute_name)
            if attribute_name('_'):
                # Ignore private attributes.
                pass
            elif hasattr(attribute, '__call__'):
                # Ignore methods and other callables.
                pass
            elif isinstance(attribute, (str, int, bool, float, type(None))):
                # Primitive types can be passed through unmodified.
                output[attribute_name] = attribute
            elif isinstance(attribute, list):
                # Recursively deal with items in lists.
                output[attribute_name] = [
                    self.to_representation(item) for item in attribute
                ]
            elif isinstance(attribute, dict):
                # Recursively deal with items in dictionaries.
                output[attribute_name] = {
                    str(key): self.to_representation(value)
                    for key, value in attribute.items()
                }
            else:
                # Force anything else to its string representation.
                output[attribute_name] = str(attribute)

<6. Advanced serializer usage (고급 사용법)>

1. Overriding serialization and deserialization behavior

serializer 클래스의 serialization, deserialization 또는 유효성 검사를 변경해야하는 경우 .to_representation() 또는 .to_internal_value() 메서드를 재정합니다.
유용한 이유는 다음과 같습니다.

  • 새로운 serializer 기본 클래스에 대한 새로운 동작을 추가
  • 기존 클래스의 동작을 약간 수정합니다.
  • 많은 양의 데이터를 반환하며 자주 액서스되는 API 엔드포인트의 serializer 성능 향상

이 메소드의 서명은 다음과 같습니다.

.to_representation(self, obj)

serializer가 필요한 객체 인스턴스를 가져와서 원시 표현을 반환해야합니다. 일반적으로 이것은 내장 파이썬 데이터 유형의 구조를 반환하는 것을 의미합니다. 처리 할 수 있는 정확한 유형은 API에 대해 구성한 렌더링 클래스에 따라 다릅니다.

.to_internal_value(self, data)

검증되지 않은 데이터를 입력 받아 serializer.validated_data로 사용할 수 있는 유효성이 검사 된 데이터를 반환해야합니다. serializer 클래스에서 .save()가 호출되면 반환 값도 .create() 또는 .update() 메서드에 전달됩니다.
유효성 검사가 실패하면 메서드는 serializers.ValidationError(오류)를 발생시켜야합니다. 일반적으로 여기에 있는 errors 인수는 필드 이름을 오류 메세지에 매핑하는 dict입니다.
이 메소드에 전달 된 data인수는 일반적으로 request.data의 값이므로, 제공하는 데이터 유형은 API에 대해 구성한 파서 클래스에 따라 다릅니다.


2. Serializer Inheritance (상속)

Django 폼과 마찬가지로 상속을 통해 serializer를 확장하고 다시 사용할 수 있습니다. 이를 통해 많은 수의 serializer에서 사용할 수 있는 부모 클래스의 공통 필드 또는 메서드 집합을 선언 할 수 있습니다. 예를 들면:

class MyBaseSerializer(Serializer):
    my_field = serializers.CharField()

    def validate_my_field(self):
        ...

class MySerializer(MyBaseSerializer):
    ...

Django의 Model과 ModelForm 클래스처럼, serializer의 내부 Meta 클래스는 부모의 내부 Meta 클래스를 상속받지 않습니다. Meta클래스가 부모 클래스에서 상속 받기를 원한다면 명시해야합니다. 예:

class AccountSerializer(MyBaseSerializer):
    class Meta(MyBaseSerializer.Meta):
        model = Account

일반적으로 내부 메타 클래스에서는 상속을 사용하지 않고 모든 옵션을 명시적으로 선언하는 것이 좋습니다.
또한 다음과 같은 주의사항이 serializer 상속에 적용됩니다.

  • 일반적인 Python 이름 해석 규칙이 적용됩니다. Meta 내부 클래스를 선언하는 여러 기본 클래스가 있는 경우, 첫번째 클래스만 사용됩니다. 이것은 자녀의 메타가 존재한다면 메타를 의미하고, 그렇지 않으면 첫번째 부모의 메타를 의미합니다.
  • 하위 클래스에서 이름을 없음으로 설정하여 부모 클래스에서 상속 된 Field를 선언으로 제거 할 수 있습니다.
class MyBaseSerializer(ModelSerializer):
    my_field = serializers.CharField()

class MySerializer(MyBaseSerializer):
    my_field = None

그러나 이 방법을 사용하는 경우에만 상위 클래스에 의해 선언적으로 정의 된 필드에서 선택 해재 할 수 있습니다 ModelSerializer가 디폴트 필드를 생성하는 것을 막지는 않습니다.기본 필드에서 선택해제하려면 Specifying which fields to include를 참조하세요.


3. Dynamically modifying fields (동적으로 필드를 수정)

serializer가 초기화되면 serializer에서 설정된 필드 dict에 .fields특성을 사용하여 액서스 할 수 있습니다.

이 속성에 액서스하고 수정하면 serializer 컨버터를 동적으로 수정할 수 있습니다.
fields인수를 직접 수정하면 serializer 선언 시점이 아닌 런타임시 serializer 필드의 인수 변경과 같은 흥미로운 작업을 수행 할 수 있습니다.

Example

예를 들어, serializer에서 초기화할 때 사용할 필드를 설정하려면 다음과 같이 serializer 클래스를 만들 수 있습니다.

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    """
    A ModelSerializer that takes an additional `fields` argument that
    controls which fields should be displayed.
    """

    def __init__(self, *args, **kwargs):
        # Don't pass the 'fields' arg up to the superclass
        fields = kwargs.pop('fields', None)

        # Instantiate the superclass normally
        super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

        if fields is not None:
            # Drop any fields that are not specified in the `fields` argument.
            allowed = set(fields)
            existing = set(self.fields.keys())
            for field_name in existing - allowed:
                self.fields.pop(field_name)

이렇게 하면 다음을 수행 할 수 있습니다.

>>> class UserSerializer(DynamicFieldsModelSerializer):
>>>     class Meta:
>>>         model = User
>>>         fields = ('id', 'username', 'email')
>>>
>>> print UserSerializer(user)
{'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'}
>>>
>>> print UserSerializer(user, fields=('id', 'email'))
{'id': 2, 'email': 'jon@example.com'}

Customizing the default fields

REST 프레임워크2에서는 개발자가 ModelSerializer클래스가 기본 필드 set을 자동으로 생성하는 방법을 재정의 할 수 있는 API를 제공했습니다.


이 API에는 .get_field(), get_pk_field()와 그외의 메소드가 포함되어 있습니다.
하지만 serializer가 근본적으로 3.0으로 다시 디자인 되었기 때문에 이 API는 더이상 존재하지 않습니다. 생성 된 필드는 여전히 수정할 수 있지만 소스코드를 참조해야하며, 변경사항이 API의 비공개 비트에 해당하면 변경 될 수 있음을 알고 있어야 합니다.


<7. Third party packages>

다음의 타사 패키지도 제공됩니다.

Django REST marshmallow

Django-rest-marshmallow 패키지는

파이썬 marshmallow 라이브러리를 사용하여 시리얼라이저의 대체 구현을 제공합니다.

REST 프레임워크 serializers와 동일한 API를 제공하며, 일부 use-cases는 drop-in 대체로 사용할 수 있습니다.


(1) Serpy

Serpy 패키지는 속도 향상을 위해 만들어진 serializer의 대안 구현입니다.

Serpy은 복잡한 데이터 유형을 단순한 기본 유형으로 serializer합니다.

기본 유형은 JSON 이나 필요한 다른 형식으로 쉽게 변환 할 수 있습니다.


(2) MongoengineModelSerializer

django-rest-framework-mongoengine패키지는 MongiEngineModelSerializer serializer 클래스를 제공하여 MongoDB를 Django REST 프레임워크의 저장소 계층으로 사용할 수 있도록 지원합니다.


(3) GeoFeatureModelSerializer

django-rest-framework-gis 패키지는 GeoJSON을 읽기와 쓰기 작업 모두 지원하는 GeoFeatureModelSerializer Serializer 클래스를 제공합니다.


(4) HStoreSerializer

django-rest-framework-hstore패키지는 django-hstore DictionaryField 모델 필드와 schema-mode기능을 지원하는 HStoreSerializer를 제공합니다.


(5) Dynamic REST

Dynamic-Rest 패키지는 ModelSerializer 및 ModelViewSet 인터페이스를 확장하고 필터링, 정렬, serializer에서 정의한 모든 필드와 관계를 포함/제외하는 API 쿼리 parameter를 추가합니다.


(6) Dynamic Fields Mixin

drf_dynamic-fields 패키지는 serializer당 필드를 URL parameter로 지정된 서브 세트로 동적으로 제한하기 위해 mixin을 제공합니다.


(7) DRF FlexFields

drf-flex-fields 패키지는 ModelSerializer 및 ModelViewSet을 확장하여 필드를 동적으로 설정하고 기본 필드를 중첩 모델로 확장하는데, 일반적으로 사용되는 기능을 URL parameter 및 serializer 클래스 정의에서 모두 제공합니다.


(8) Serializer Extensions

django-rest-framework-serializer-extensions 패키지는 보기/요청 단위로 필드를 정의 할 수있게 하여 serializer를 DRY 할 수 있는 도구 모음을 제공합니다. 필드를 허용 목록에 추가하고 블랙리스트에 올릴 수 있으며 자식 serializer를 선택적으로 확장 할 수 있습니다.


(9) HTML JSON Forms

html-json-forms 패키지는 HTML JSON Form specification(비활성)에 따라 <form> 제출을 처리하는 알고리즘 및 serializer를 제공합니다. serializer는 HTML 내에서 임의로 중첩 된 JSON 구조를 쉽게 처리합니다. 예를 들어, <input name="item[0][id]" value="5">는 {"items": [{"id": "5"}]}으로 해석됩니다.


(10) DRF-Base64

DRF-Base64는 base64 인코딩 파일을 업로드를 처리하는 일련의 필드와 model serializers를 제공합니다.


(11) QueryFields

djangorestframework-queryfields를 사용하면 API 클라이언트가 포함/제외 검색어 parameter를 통해 응답에서 어떤 필드를 보낼지 지정할 수 있습니다.


(12) DRF Writable Nested

drf-writable-nested 패키지는 중첩 된 관련 데이터로 모델을 작성/업데이트 할 수 있는 쓰기 가능한 중첩 model serializer를 제공합니다.

직렬화

모든 프로그래밍 언어의 통신에서 데이터는 필히 문자열로 표현되야 합니다.

송신자 : 객체를 문자열로 변환하여 전송 -> 직렬화
수신자 : 수신한 문자열을 다시 객체로 변환하여, 활용 -> 비직렬화

 

각 언어에서 모두 지원하는 직렬화 포맷(JSON, XML 등) 과

특정언어에서만 지원하는 직렬화 포맷(Python의 Pickle) 이 있습니다.

요즘의 API서버에서는 대개 JSON 인코딩된 요청/응답 사용을 합니다.


 

1. JSON 포맷

- 표준 라이브러리 json 제공

- 다른 언어/ 플랫폼과 통신할 때 주로 사용

 

- pickle에 비해 직렬화를 지원하는 데이터 타입 수가 적지만, 커스텀 Rule 지원도 가능합니다.

import jsonpost_list=[{'message': 'hello'},]#직렬화
json_string = json.dumps(post_list)
print(json_string)      # [{'message': 'hello'},]#비직렬화
print(json.loads(json_string))

2. Pickle 포맷

- 파이썬 전용 포맷으로써 파이썬 시스템 끼리만 통신할 때 사용 가능합니다.

- 표준 라이브러리 pickle 제공.

import picklepost_list=[{'message': 'hello'},]pickle_bytes = pickle.dumps(post_list)
print(pickle_bytes) # b'\x80\0x3]q\x00........print(pickle.loads(pickle_bytes))

json/pickle 모두 파이썬 기본 라이브러리이며 장고의 Model, QuerySet 등에 대해서는 직렬화 Rule이 없기때문에 그대로 dumps()에 넣으면 에러가 발생합니다. (TypeError: Object of type QuerySet is not JSON serializable)

(1) rest_framework.renderer.JSONRender

rest_framework/utils/encoders.py 의 JSONEncoder를 통한 직렬화 지원

  • __getitem__ 속성 지원시 dict(obj) 변환
  • __iter__ 속성 지원시 tuple 변환
  • QuerySet 타입일 시 tuple 변환
  • .tolist 속성 지원 시 obj.tolist()

Model 타입은 미지원 -> ModelSerializer를 통해 변환이 가능합니다.

(2) rest_framework.renderers.JSONRenderer

json.dumps에 대한 래핑 클래스 -> 보다 편리한 직렬화를 지원

from rest_framework.renderers import JSONRendererdata = Post.objects.all()
JSONRenderer().render(data)# 하지만 Model에 대한 변환 Rule은 아직 없음.

(3) ModelSerializer를 통한 JSON 직렬화

Serializer/ModelSerializer는 Form/ModelForm과 유사합니다.
=> 역할 면에서 Serializer는 POST 요청만 처리하는 Form임.

장고 기본 View에서의 HttpResponse JSON 응답

모든 View는 HttpResponse 타입의 응답을 해야만 함.

  1. 직접 json.dumps를 통해 문자열을 획득, HttpResponse를 통해 응답
  2. 1번을 정리하여 jsonResponse 지원 -> 내부적 json.dumps를 사용하며 DjangoJSONEncoder가 디폴트 지정됨.

DRF에서의 JSON 응답

1. DRF를 통한 HttpResponse JSON 응답

qs = Post.objects.all()
serializer = PostModelSerializer(qs, many=True)from rest_framework.response import Response
response = Response(serializer.data) # text/html 기본값# Response에서는 JSON 직렬화가 Lazy하게 동작
# 실제 응답 생성시 .rendered_content 속성에 접근하며 이때 변환 이뤄짐

DRF의 모든 뷰는 APIView를 상속받습니다.
APIView를 통해 Response에 다양한 속성이 지정됩니다.


2. DRF 에서의 활용예제

from rest_framework import genericsclass PostListAPIView(generics.ListAPIView):
   queryset = Post.objects.all()
   serializer_class = PostModelSerializerpost_list = PostListAPIView.as_view()

ListAPIView는 리스트 기능만 지원해줍니다!

(5가지 다 사용하고 싶을 땐 ViewSet사용을..)


3. 포스팅 조회 응답에서 글쓴이 이름을 조회하고 싶을 때

author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) 필드가 있을 때,

serializers.py 에서 fields = ‘__all__’ 로 사용시

author : 1 

이런식으로 알아보기 쉽지 않게 author의 pk로 뜨게 됩니다.
하지만 serializers.py에서 serializer.ReadOnlyField를 통해 FK의 필드값을 읽어와서

class PostSerializer(serializers.ModelSerializer):
username = serializers.ReadOnlyField(source='author.username')
# fk인 author의 username을 가져와서
   class Meta:
     model = Post
     fields = ['pk', 'username', 'message', 'created_at',         'updated_at']

이런식으로 읽어온 값을 fields에 지정해주면
author : 1 이 아닌 username : ‘홍길동’ 이런 식으로 응답을 받을 수 있겠죠!

# 아니면 이 방법으로도 가능합니다!( 중첩된 Serializer 사용 )
class AuthorSerializer(serializers.ModelSerializer):
   class Meta:
      model = get_user_model()
      fields = ['username', 'email']class PostSerializer(serializers.ModelSerializer):
    author = AuthorSerializer()    class Meta:
       model = Post
       fields = '__all__'
 

[Django Rest Framework] 직렬화(Serializer에 대해서 알아보자

직렬화

donis-note.medium.com

 

 

 
 

git은 오늘날 가장 많이 사용하는 버전 관리 시스템 중 하나입니다.
오늘은 git의 자주 사용하는 명령어에 대해서 살펴보고자 합니다.

먼저 각 명령어에 따른 흐름이 어떻게 변하는지 다음 그림을 통해 알 수 있습니다.


이미지 출처 : https://jrebel.com/rebellabs/git-commands-and-best-practices-cheat-sheet/

1. 생성하기

- 새로운 로컬 저장소를 생성하기

$ git init [project_name]

 

- 저장소 가져오기

$ git clone [url]

 


2. 살펴보기

- 작업 디렉토리에 변경된 파일 보기

$ git status

 

- 변경된 staged 파일 보기

$ git diff

 

- 변경 이력 보기

$ git log


3. 브랜치 작업하기

- 로컬 브랜치 보기

$ git branch

 

- 로컬과 원격 브랜치 보기

$ git branch -av

 

- 브랜치 변경하기

$ git checkout <branch>

 

- 브랜치 생성하기

$ git branch <new-branch>

 

- 브랜치 삭제하기

$ git branch -d <branch>

 

- 원격 브랜치를 추적하는 새로운 브랜치 만들기

$ git checkout --track <remote/branch>

 

- 원격 브랜치 추적하기

$ git branch -u <remote/branch>

 

- 현재 커밋에 태그 달기

$ git tag <tag-name>


4. 변경하기

- 파일의 변경 사항을 다음 커밋에 반영하기

$ git add [file]

 

- 모든 변경 사항을 다음 커밋에 반영하기

$ git add .

 

- 메시지와 함께 커밋하기

$ git commit -m "commit message"

 

- 모든 변경 사항을 반영하면서 커밋하기

$ git commit -a

 

- 마지막 커밋 수정하기(published commit에는 하지 말 것!)

$ git commit --amend

 


5. 취소하기

- 작업 디렉토리에 모든 변경 버리기

$ git reset --hard HEAD

 

- 커밋 되돌아가기

$ git revert <commit>


6. 동기화하기

- 원격 저장소의 변경사항 가져오기

$ git fetch <remote>

 

- 원격 저장소의 변경사항을 가져오고 머지하기

$ git pull <remote> <branch>

 

- 원격 저장소의 변경사항을 가져오고 리베이스하기

$ git pull --rebase

 

- 원격 저장소에 변경사항 발행하기

$ git push

 

- 원격 저장소에 태그 발행하기

$ git push --tags


7. 병합하기와 리베이스하기

- 병합하기

$ git merge <branch>

 

- 리베이스하기

$ git rebase <branch>


8. 변경사항 저장하고 복원하기

- 임시로 변경사항 저장하기

$ git stash

 

- 임시 변경사항 복원하기

$ git stash pop

 

- 임시 변경사항 보기

$ git stash list


출처

https://seamless.tistory.com/43

 

Git 명령어 요약 정리 (Cheat sheet)

git은 오늘날 가장 많이 사용하는 버전 관리 시스템 중 하나입니다. 오늘은 git의 자주 사용하는 명령어에 대해서 살펴보고자 합니다. 먼저 각 명령어에 따른 흐름이 어떻게 변하는지 다음 그림을

seamless.tistory.com

 

git branch명령은 branch 생성및 제거, 확인등의 기능을 하는 명령어로 주요 명령어만 요약하였습니다.
자세한 내용은 git-scm Git-브랜치-브랜치-관리에서 확인하세요.


1. git branch [-l]

로컬 branch 정보를 보여줍니다. (-l 옵션은 생략가능)

$ git branch
* master
  work1

2. git branch -v

로컬 branch의 정보를 마지막 커밋 내역과 함께 보여줍니다.

$ git branch -v
* master   4bbc62f commit message 'm1'
  work1    fe7f049 commit message 'w1'
  work_new 4bbc62f commit message 'work_new1'

3. git branch -r

리모트 저장소의 branch 정보를 보여줍니다.

$ git branch -r
  origin/master
  origin/work1

4. git branch -a

로컬/리모트 저장소의 모든 branch 정보를 보여줍니다.

$ git branch -a
* master
  work1
  remotes/origin/master
  remotes/origin/work1

5. git branch (이름)

로컬에 새로운 branch를 생성합니다.

$ git branch work_new
$ git branch -a
* master
  work1
  work_new
  remotes/origin/master
  remotes/origin/work1

※ 생성과 동시에 해당 branch로 이동하려면 아래 명령어를 사용합니다.

$ git checkout -b work2

※ 원격에 있는 branch를 가져오려면 아래 명령어를 사용합니다.

[특정이름 지정시]
$ git checkout -b work2 origin/work2      
[원격 branch 이름을 그대로 사용할 경우]
$ git checkout -t origin/work2

6. git branch (–merged | –no-merged)

--merged는 이미 merge된 branch를 표시해주고 --no-merged는 아직 merge가 되지 않은 branch만 표시합니다.
--merged에 branch 목록 이미 merge되었기 때문에 *가 표시되지 않은 branch는 삭제 가능합니다.

$ git branch --merged
* master
  work_new
  work_old

$ git branch --no-merged
  work1
  work2

7. git branch -d (branch 이름)

branch를 삭제합니다.

아직 merge하지 않은 커밋을 담고 있는 경우 삭제되지 않습니다.

(강제종료 옵션 -D으로만 삭제 가능)

$ git branch -d work3
error: The branch 'work3' is not fully merged.
If you are sure you want to delete it, run 'git branch -D work3'.

$ git branch -d work_new
Deleted branch work_new (was 4bbc62f).

8. git branch -m (변경할 branch이름) (변경될 branch이름)

A 브랜치를 B 브랜치로 변경합니다.

$ git branch -v
* master   4bbc62f m1
  work2    c728ddc w2
  work_old 4bbc62f m1

$ git branch -m work2 work3

$ git branch -v
* master   4bbc62f m1
  work3    c728ddc w2
  work_old 4bbc62f m1

※ -M 옵션을 사용할 경우 기존에 동일한 이름의 branch가 있더라도 덮어씁니다.


출처

https://jistol.github.io/vcs/2017/01/27/git-branch/

 

Git branch 주요 명령어 정리

 

jistol.github.io

 

1. 들어가며

# (수정) 추후 설명하는 그림을 첨부할 예정입니다.

# 공부하며 쓰는 글이라 제일 좋은 방법이 아닐 것이며, 때로 부정확한 정보가 있을 수 있습니다.

 

지난 시간에는 DRF에서 가장 중요한 개념인 시리얼라이저를 이해하고 어떻게 와 작성하는지를 보았습니다.

이번 글에서는 DRF로 토큰 기반 회원 인증 / 가입 및 로그인을 구현해보도록 하겠습니다.

 

진행에 앞서 토큰 기반 인증에 대해 알 필요가 있습니다.

해당 개념에 대해서는 다음 블로그 링크를 첨부하는 것으로 넘어가겠습니다.

쉽게 알아보는 서버 인증 1편 - 이호연 블로그

 

또한, 요즘 서비스에서 소셜 인증을 안 쓸 수 없기 때문에

추후에 소셜 인증으로 해당 포스트를 그대로 다시 작성해보겠습니다.


2. django-rest-knox?

우리가 앞서 설치한 패키지인 django-rest-knox는

사용자 당 여러 개의 토큰을 지원하고 여러 보안 기술을 제공합니다.

 

앞서 설정했던 것처럼 settings.py의 INSTALLED_APPS에 knox를 넣어주셨다면,

아래 내용을 settings.py에 추가해야 합니다.

# mysite/settings.py
...
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ("knox.auth.TokenAuthentication",),
}
...

3. 시리얼라이저 작성

다음 단계는 시리얼라이저 작성입니다.

# api/serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from django.contrib.auth import authenticate

# 회원가입
class CreateUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id", "username", "password")
        extra_kwargs = {"password": {"write_only": True}}

    def create(self, validated_data):
        user = User.objects.create_user(
            validated_data["username"], None, validated_data["password"]
        )
        return user


# 접속 유지중인지 확인
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id", "username")


# 로그인
class LoginUserSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def validate(self, data):
        user = authenticate(**data)
        if user and user.is_active:
            return user
        raise serializers.ValidationError("Unable to log in with provided credentials.")

다른 시리얼라이저는 다 ModelSerializer로 작성하여 간단하게 처리하였고,

로그인의 경우 연결되는 모델이 없기 때문에 그냥 Serializer로 작성하였습니다.


4. 뷰 작성

다음은 views.py를 작성하겠습니다.

# api/views.py
from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view
from knox.models import AuthToken
from .serializers import CreateUserSerializer, UserSerializer, LoginUserSerializer

# Create your views here.
@api_view(["GET"])
def HelloAPI(request):
    return Response("hello world!")


class RegistrationAPI(generics.GenericAPIView):
    serializer_class = CreateUserSerializer

    def post(self, request, *args, **kwargs):
        if len(request.data["username"]) < 6 or len(request.data["password"]) < 4:
            body = {"message": "short field"}
            return Response(body, status=status.HTTP_400_BAD_REQUEST)
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        return Response(
            {
                "user": UserSerializer(
                    user, context=self.get_serializer_context()
                ).data,
                "token": AuthToken.objects.create(user),
            }
        )


class LoginAPI(generics.GenericAPIView):
    serializer_class = LoginUserSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data
        return Response(
            {
                "user": UserSerializer(
                    user, context=self.get_serializer_context()
                ).data,
                "token": AuthToken.objects.create(user)[1],
            }
        )


class UserAPI(generics.RetrieveAPIView):
    permission_classes = [permissions.IsAuthenticated]
    serializer_class = UserSerializer

    def get_object(self):
        return self.request.user

이번 뷰는 generic 기반 클래스 뷰로 작성되었습니다.

우선 함수 기반 뷰에서 클래스 기반 뷰로 바뀌면서 수정된 점은

앞서 데코레이터로 미리 http 메소드를 정의해주었던 것을 클래스 안으로 넣었다는 것이 있습니다.

 

또한 제네릭 뷰의 경우에는

기본적인 기능을 모두 포함하는 뷰로, 자세한 설명은 추후에 첨부하겠습니다.


5. url 설정

마지막으로 url을 설정하면 완료됩니다.

# api/urls.py
from django.urls import path, include
from .views import HelloAPI, RegistrationAPI, LoginAPI, UserAPI

urlpatterns = [
    path("hello/", HelloAPI),
    path("auth/register/", RegistrationAPI.as_view()),
    path("auth/login/", LoginAPI.as_view()),
    path("auth/user/", UserAPI.as_view()),
]
```

```python
# mysite/urls.py
from django.urls import path, include
from django.contrib import admin

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include("api.urls")),
    path("api/auth", include("knox.urls")),
]

6. 여태까지 내용 확인해보기

백엔드 API 테스트를 위해 Insomnia 라는 툴을 다운 받아 사용할 수 있습니다.

여러가지 편한 기능이 있으니 사용하시면 좋고, 아니면 그냥 기본 제공 웹 페이지에서 진행해도 됩니다.

회원 가입이나 로그인의 경우 POST 요청으로 보내면 되며 다음과 같이 요청을 보내면 됩니다.

 

- 회원가입

- 로그인


7. Django 기본 유저 모델 확장하기

여기까지 진행한 회원 모델은 Django에서 기본적으로 제공하는 유저 모델입니다.

하지만 실제로 사용하고 싶은 유저 모델은 포인트나 주소 등 여러 부가적인 정보가 들어갈 수 있는 유저이어야 합니다.

따라서 기본 유저 모델을 확장해야 하는데, 확장하는 방법에는 여러가지가 있습니다.

Django의 사용자 모델을 수정해보자!

 

그중에서 제일 간단한 방법인

"프로필 모델 생성 후 one-to-one 연결" 방법을 사용하겠습니다.

(1) Profile 모델 생성

모델은 다음과 같이 정의하겠습니다.

내용 외 필요한 컬럼이 있다면 추가하시면 됩니다.

# api/models.py
from django.db import models

# Create your models here.
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    user_pk = models.IntegerField(blank=True)
    email = models.EmailField(max_length=500, blank=True)
    nickname = models.CharField(max_length=200, blank=True)
    point = models.IntegerField(default=0)
    like = models.CharField(max_length=200, blank=True)
    phone = models.CharField(max_length=200, blank=True)


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance, user_pk=instance.id)


@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

여기서 @receiver로 작성된 함수들은 User 모델로부터 post_save라는 신호,

 

즉 User 모델 인스턴스 생성에 맞춰 Profile 모델 인스턴스 또한 함께 생성하라는 것입니다.

이로 인해 여러분이 처음 유저를 생성하고 username과 password만 입력해도 해당 사용자에 대한 Profile 인스턴스가 함께 생성 됩니다.

 

(2) Profile Serializer 생성

다음은 Profile 모델에 대한 시리얼라이저를 만들어보겠습니다.

기본적으로 프로필 정보 조회에 필요한 프로필 ModelSerializer가 필요합니다.

# api/models.py
from django.db import models

# Create your models here.
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    user_pk = models.IntegerField(blank=True)
    email = models.EmailField(max_length=500, blank=True)
    nickname = models.CharField(max_length=200, blank=True)
    point = models.IntegerField(default=0)
    like = models.CharField(max_length=200, blank=True)
    phone = models.CharField(max_length=200, blank=True)


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance, user_pk=instance.id)


@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

 

(3) Profile View 생성

시리얼라이저를 만들었으니 뷰를 생성할 때입니다.

이번 뷰는 앞선 회원 관련 뷰와 동일하게 제네릭 뷰의 UpdateAPIView를 사용하며,

이로 인해 별도의 로직 작성 없이 간편하게 Update 기능을 구현할 수 있습니다.

# api/views.py
class ProfileUpdateAPI(generics.UpdateAPIView):
    lookup_field = "user_pk"
    queryset = Profile.objects.all()
    serializer_class = ProfileSerializer

 

(4) Profile url 설정

언제나 마지막 단계는 urls.py입니다.

프로필 업데이트에 대한 url을 제공하면 되며, 다음과 같이 작성할 수 있습니다.

# api/urls.py
from django.urls import path, include
from .views import HelloAPI, RegistrationAPI, LoginAPI, UserAPI, ProfileUpdateAPI

urlpatterns = [
    path("hello/", HelloAPI),
    path("auth/register/", RegistrationAPI.as_view()),
    path("auth/login/", LoginAPI.as_view()),
    path("auth/user/", UserAPI.as_view()),
    path("auth/profile/<int:user_pk>/update/", ProfileUpdateAPI.as_view()),
]

 

(5) 번외 admin에 Profile 등록

Django의 어드민 페이지에서 Profile 모델의 데이터를 보고 싶다면 admin.py에 등록해야 합니다.

또한 Profile 모델은 User 모델에 종속되어 있는 상황으로 User 인스턴스에 Profile 인스턴스가 포함되어 있는 형태로 보기 위해 다음과 같이 코드를 작성하여 등록합니다.

# api/admin.py
from django.contrib import admin

# Register your models here.
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from .models import Profile


class ProfileInline(admin.StackedInline):
    model = Profile
    can_delete = False
    verbose_name_plural = "profile"


class UserAdmin(BaseUserAdmin):
    inlines = (ProfileInline,)


admin.site.unregister(User)
admin.site.register(User, UserAdmin)

 

(6) 결과 확인하기

이제 모든 개발을 마쳤으니 Profile 기능을 확인할 차례입니다.

Insomnia를 활용하여 테스트 해보겠습니다.

Django에서 update 기능을 사용할 때는 PUT 메소드로 요청을 보내야 합니다.

- User 생성 요청

- Profile 수정 요청

이제 어드민 페이지에서 확인해보겠습니다.

위처럼 superuser 외에 생성한 testuser1이 보이며,

이렇게 나오며 프로필 수정 또한 잘 된 것을 확인할 수 있습니다.


8. 마치며

여태까지 회원 가입 / 로그인 관련 API를 작성하였습니다.

 

사실 이렇게 하면 백엔드에서 할 역할은 얼추 끝이지만,

실제 웹 사이트를 만들어보지 않으면 이게 어떻게 되는지 이해할 수 없기 때문에

다음 시간에는 번외편으로 아주아주 간단한 프론트를 만들어서

실제로 회원가입하고 로그인하는 것을 구현해보겠습니다.


출처

https://devkor.tistory.com/entry/03-Django-Rest-Framework-%ED%9A%8C%EC%9B%90-%EC%9D%B8%EC%A6%9D-%EC%9C%A0%EC%A0%80-%EB%AA%A8%EB%8D%B8-%ED%99%95%EC%9E%A5%ED%95%98%EA%B8%B0

 

03. Django Rest Framework 회원 인증, 유저 모델 확장하기

들어가며 # (수정) 추후 설명하는 그림을 첨부할 예정입니다. # 공부하며 쓰는 글이라 제일 좋은 방법이 아닐 것이며, 때로 부정확한 정보가 있을 수 있습니다. 지난 시간에는 DRF에서 가장 중요한

devkor.tistory.com

 

1. Hello World 코드 설명

# (수정) 추후 설명하는 그림을 첨부할 예정입니다.

GET 127.0.0.1:8000/api/hello/

 

지난 시간에 해당 주소로 요청을 보내면 hello world를 print하는 api를 만들었습니다.

각 단계를 다시 따라가보며 세부적인 내용을 살펴보겠습니다.

 

첫번째 단계는 views.py를 작성하는 것입니다.

현재 사용하고 있는 view의 구조는 함수 기반 뷰, 이른바 FBV 입니다.

# api/views.py

from rest_framework.response import Response
from rest_framework.decorators import api_view

# Create your views here.
@api_view(['GET']) # 해당 함수 view에서 처리할 http 메소드
def HelloAPI(request):
    return Response("hello world!") # http response 형태로 return

 

views.py에서 다룬 내용은 그냥 Django에서 다룬 것과 크게 다를 것 없이,

요청을 받아 해당 요청에 대한 응답을 제공하는 기능을 합니다.

오늘 배우게 될 serializer와 함께 하면 어떻게 할 수 있는지 이따 알아보겠습니다.

 

그 다음은 api/urls.py 입니다.

# api/urls.py

from django.urls import path, include
from .views import HelloAPI

urlpatterns = [
    path("hello/", HelloAPI),
]

크게 특별할 것 없이 view에 있는 HelloAPI를 가져와 url로 할당시켰습니다.

path 사용을 권장한다는 것을 지난 시간에 말씀드렸습니다.


2. Serializer?

Serializer는 Django Rest Framework에서 나온 새로운 요소입니다.

사전적 의미는 직렬화 하는 무언가 정도로 볼 수 있습니다.

 

직렬화라는 말이 와닿지 않는데,

이는 간단하게 파이썬 데이터를 JSON 타입의 데이터로 변환해준다 정도로 생각하시면 됩니다.

 

기본적으로 웹에서 통신을 할 때, 즉 데이터를 주고 받을 때는 어느 정도 정해진 포맷이 있습니다.

대표적인 타입이 JSON이나 XML인데, 대부분의 REST API에서는 JSON으로 주고 받기 때문에

우리는 그냥 JSON만 잘 알고 있으면 됩니다:)

 

정확한 의미의 직렬화는 Django 프로젝트에서 내가 만든 모델로부터 뽑은 queryset,

즉 모델 인스턴스를 JSON 타입으로 바꾸는 것입니다.

 

기존 일반적인 Django에서의 폼과 같은 개념이라고 비유하는 경우가 있는데,

저는 그냥 Django 모델을 JSON으로 변환하기 위한 모양 틀 정도로 이해하고 있고 그게 제일 깔끔한 것 같습니다!

간단한 코드로 예를 들어보겠습니다.(게시판 프로젝트 코드가 아닙니다)

# test/models.py
from django.db import models
# 설명만을 위한 모델로, 상당히 대충 작성 되었습니다:)
class Person(models.Model):
    id = models.IntegerField()
    name = models.CharField()
    phone = models.CharField()
    addr = models.CharField()
    email = models.CharField()
# test/serializers.py
from rest_framework import serializers
from .models import Person

# ModelSerializer 뒤에서 설명합니다.
class BasePersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = ('id', 'name', 'phone', 'addr')

위에서 보시면 Person이라는 모델을 models.py에 만들고,

BasePersonSerializer라는 시리얼라이저를 serializers.py에 만들었습니다.(앱 내에 파일을 생성하셔야 합니다)

 

이게 어떤 뜻이냐면,

"내가 만든 Person이라는 모델에서 데이터를 뽑아서 응답으로 보낼텐데,

응답의 형태 중 하나인 Base 형태를 BasePersonSerializer라고 정의할게!" 라는 뜻입니다.

 

자세히 보시면 모델에는 email도 정의되어있는데 Base의 fields에는 email이 없습니다.

이는 개발자가 정의한 Base 형태에는 email이 없다는 뜻이죠.

그러면 사람의 이메일 정보를 요청할 때 쓸 수 있는 시리얼라이저는 어떻게 만들 수 있을까요?

# test/serializers.py
from rest_framework import serializers
from .models import Person

# ModelSerializer 뒤에서 설명합니다.
class EmailPersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = ('id', 'email')

이렇게 만들어 볼 수 있습니다.

 

이제 우리는 한 가지 깨달음을 얻을 수 있습니다.

시리얼라이저는 응답으로 보낼 데이터의 형태를 정해주는 하나의 틀과 같구나!


3. Serializer - View 연결하기

여기까지 잘 이해되셨다면 이후 단계에서는 더 잘 이해될 것입니다.

이제는 그럼 views.py에 실제로 시리얼라이저가 어떻게 사용되는지 알려드리겠습니다.

# test/views.py
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import Person
from .serializers import BasePersonSerializer, EmailPersonSerializer

@api_view(['GET'])
def PersonAPI(request, id):
    now_person = Person.object.get(id=id)
    serializer = BasePersonSerializer(now_person)
    return Response(serializer.data)
# => id=1에 대해 리턴된 Response: {'id': 1, 'name': '태뽕', 'phone': '01012345678', 'addr': '주소주소'}

@api_view(['GET'])
def EmailAPI(request, id):
    now_person = Person.object.get(id=id)
    serializer = EmailPersonSerializer(now_person)
    return Response(serializer.data)
# => id=1에 대해 리턴된 Response: {'id': 1, 'email': 'email@email.com'}

어떠신가요 바로 이해가 되셨나요?

제가 시리얼라이저를 변환기, 모양 틀로 설명드린게 이런 맥락이었습니다.

 

각 view에서 무언가 데이터를 요청할 때,

지금 예시에서는 PersonAPI는 사람에 대한 데이터, EmailAPI에서는 이메일에 대한 데이터를 요청할 때 각각 원하는 형태로 응답해줘야 합니다. 하지만 모델은 하나니 필요한 데이터만 골라서 보내줘야겠죠?

이 역할을 해주는게 시리얼라이저라고 이해하시면 됩니다.

 

내가 보낼 데이터(now_person(즉, 모델 인스턴스))를 시리얼라이저에 넣어 변환시키고

그 데이터를 응답으로 보내주는 것이 시리얼라이저 - 뷰 연동 개념입니다.


4. ModelSerializer

앞서 진행한 코드에서 한가지 설명 안하고 그냥 넘어간 코드가 있습니다.

바로 시리얼라이저를 선언할 때 사용한 serializers.ModelSerializer입니다.

# test/serializers.py
# ModelSerializer 뒤에서 설명합니다.
class BasePersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = ('id', 'name', 'phone', 'addr')

원래 가장 기본적인 형태의 시리얼라이저 선언 방법은 serializers.Serializer를 상속 받는 것입니다.

이 형태로 선언하면 다음과 같이 코드를 작성할 수 있습니다.

# test/serializers.py
class BasePersonSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField()
    phone = serializers.CharField()
    addr = serializers.CharField()

    def create(self, validated_data):
        """
        검증한 데이터로 새 `Person` 인스턴스를 생성하여 리턴합니다.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        검증한 데이터로 기존 `Person` 인스턴스를 업데이트한 후 리턴합니다.
        """
        ...
        ...
        return instance

위에서 작성한 시리얼라이저보다 훨씬 길고 복잡해졌죠?

원래 시리얼라이저를 선언할 때는

1. 데이터 형태에 대한 필드(앞선 방법에서는 fields, 지금은 일일이 선언),

2. 그리고 그 데이터를 어떻게 처리할건지에 대한 메소드가 필요합니다.

 

이 메소드는 create나 update와 같이 이미 선언되어있는 메소드를 재작성하는 것이며,

views.py에서 직접 create나 update를 해야 할 것을 시리얼라이저에서 대신 해주는 것이라고 보면 됩니다.

대략 그런 역할을 선언해주고 필드에 대한 상세적인 내용을 직접 정의해야 했다면

 

serializers.ModelSerializer는 이 내용들을 알아서 해준 형태의 시리얼라이저라고 보시면 됩니다.

따라서 웬만하면 저희는 간편한 ModelSerializer의 형태로 선언하겠습니다. ★★


5. 마치며

여태까지 Django Rest Framework에서 가장 중요한 개념인 시리얼라이저에 대해 배웠습니다.

코드 작성 없이 보면서 따라가느라 조금 지루하셨을텐데,

다음 포스트에서는 이렇게 열심히 공부한 개념을 실제 게시판 프로젝트에 적용하겠습니다.

가장 첫 단계인 회원 인증부터 시작합니다:)


출처

https://devkor.tistory.com/entry/03-Django-Rest-Framework-Serializer-View-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0?category=734691 

 

02. Django Rest Framework, Serializer, View 개념 익히기

Hello World 코드 설명 # (수정) 추후 설명하는 그림을 첨부할 예정입니다. GET 127.0.0.1:8000/api/hello/ 지난 시간에 해당 주소로 요청을 보내면 hello world를 print하는 api를 만들었습니다. 각 단계를 다시..

devkor.tistory.com

 

1. 시작하며

Python: 3.6.3
IDE: VS Code

본 포스트는 Django 튜토리얼은 해보았다는 가정 하에 작성되었습니다.
만약 Django가 아예 처음이라면 장고걸스튜토리얼을 먼저 해보시길 바랍니다.

 


2. Django 개발 환경 세팅

원활한 Django 개발을 위해서는 파이썬 가상환경 세팅이 필요합니다.

다음과 같은 명령어로 venv 생성 및 패키지 설치, 초기 세팅을 진행합니다.

$ mkdir Rest-CRUD
$ cd Rest-CRUD
$ python3 -m venv venv
$ source venv/bin/activate
$ (venv) pip install django
$ (venv) django-admin startproject mysite .
$ (venv) python manage.py startapp api

 

여기까지 진행했을 때 디렉토리 구조는 다음과 같습니다.

- Rest-CRUD
    - api
    - manage.py
    - mysite
    - venv

 

여기에 추가로 djangorestframework 패키지django-rest-knox를 설치하겠습니다.

djangorestframework는 당연하게도 Django Rest Framework를 위함이며,

django-rest-knox는 추후에 진행할 회원가입/인증에 사용될 패키지입니다.

$ (venv) pip install djangorestframework
$ (venv) pip install django-rest-knox

 

당장 쓸 패키지가 아닌데도 미리 설치한 이유는

settings.py를 한번에 건드리기 위함입니다(귀찮을테니ㅎㅎ).

# mysite/settings.py

ALLOWED_HOSTS = ['127.0.0.1']

INSTALLED_APPS = [
    ...
    'api',
    'rest_framework',
    'knox',
]

TIME_ZONE = 'Asia/Seoul'

 

위와 같이 앱을 등록하고 각종 설정을 마치면 migration 단계가 남았습니다.

$ (venv) python manage.py makemigrations
$ (venv) python manage.py migrate

 

여기까지 세팅이 완료되었다면 실행해봅시다!

$ (venv) python manage.py runserver

 

실행하여 로컬호스트 주소로 들어가면 잘 아는 Django 기본 화면이 나오게 됩니다.

여기까지는 그냥 Django 프로젝트랑 다를 바 없기 때문에 그래도 아주 간단한 api 하나 만들고 마치도록 하겠습니다.

GET 127.0.0.1:8000/api/hello/

 

해당 주소로 요청을 보내면

hello world를 print하는 api를 만들겠습니다.

 

첫번째 단계는 views.py를 작성하는 것입니다.

view를 작성하는 방법에는 여러가지가 있고 추후에 정리하겠으며, 지금은 그냥 하셔도 됩니다.

# api/views.py

from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view

# Create your views here.
@api_view(['GET'])
def HelloAPI(request):
    return Response("hello world!")

 

그 다음 api/urls.py 파일을 만들겠습니다.

# api/urls.py

from django.urls import path, include
from .views import HelloAPI

urlpatterns = [
    path("hello/", HelloAPI),
]

마지막으로는 mysite/urls.py에 url을 등록하겠습니다.

(참고)

urls.py에서 urlpatterns를 작성하는 방법

최신버전부터 기존의 정규표현식으로 작성되던 url 기반에서 path 기반으로 변경되었으며

되도록 이 방식을 따르길 권장합니다.

# mysite/urls.py

from django.urls import path, include
from django.contrib import admin

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include("api.urls"))
    ]

이제 실행시키고, 127.0.0.1:8000/api/hello/ 로 접속하시면 다음과 같은 화면을 볼 수 있습니다.

와 처음 보는 형태의 화면이죠?

Django Rest Framework에서 기본적으로 제공하는 이 화면은

api 형태의 요청에 대한 응답을 다음과 같이 표시해줍니다.

api 테스트 툴 대신 충분히 사용할 만한 기능입니다.

 

여기까지 아주 간단하게 Django Rest Framework에 대한 감 잡기 및 설정까지 마쳤습니다.

다음 포스트에서는 위의 코드에 대한 조금 더 자세한 설명과 view를 작성하는 여러 방법에 대해 다뤄보겠습니다.


출처

https://devkor.tistory.com/entry/02-Django-Rest-Framework-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85?category=734691

1. 프론트엔드와 백엔드

요즘 웹 개발의 흐름은 react.js, vue.js의 JS 기반 프론트엔드 프레임워크가 지배하고 있다고 느껴집니다.

웹 개발을 할 때 react.js나 vue.js 없이 개발하는 사례가 거의 없는거 같아요. 

(프론트를 거의 안해봐서 잘 모르겠지만)

워낙 쉽게 모든 걸 할 수 있고, 기존 jquery, js로 구현하기 어려웠거나 불편했던 것의 해결, 유지 보수 등이 좋아 인기가 많은 것 같습니다.

이러다 보니 웹을 만드는 프로젝트를 할 때(비즈니스 레벨 혹은 학교 팀 프로젝트 레벨이더라도)

프론트엔드 프레임워크를 담당하는 사람(들)이 꼭 있습니다.

짧은 식견으로는 라우팅이나 기존 새로고침을 통해 제공해야 했던 데이터의 변경을 프론트엔드에게 넘겨주고,

엔드에서는 그저 DB에 접근해 요청에 맞는 데이터를 제공하는 API 서버를 개발하도록 된 것으로 생각됩니다.

이렇게 개발하게 되면 얻을 수 있는 장점은 프론트엔드 개발자와 백엔드 개발자 간 협업이 상당히 쉬워진다는 것입니다. 프론트엔드 개발자는 UI나 로직을 구현하면 되고, 필요한 데이터가 발생한다면 백엔드 개발자가 만들어놓은 API를 가져다 쓰면 됩니다.

 

혼자 개발을 한다면 이 둘을 굳이 분리할 필요가 없겠지만,

개발자 둘이 한 몸이 되지 않는 이상 코드 공유부터 수정, 테스트까지 정말 불편한 일들이 많을 것입니다.

 

특히 백엔드의 API 서버는 단지 웹에만 적용되지 않고,

앱이나 여러 http 프로토콜로 통신하는 프로젝트에 그대로 적용될 수 있어 활용성이 정말 좋습니다. 

REST API가 API 개발에서 제일 대표적인 방법이며, 이에 대한 설명은 다른 분들이 더 잘해주셔서 이를 첨부하겠습니다.


2. 왜 Django Rest Framework??

REST API를 개발하기 위한 프레임워크는 정말 다양합니다.

최근 정말 인기 있는 node.js의 express, python의 flask, java의 spring boot까지.

정말 다양한 언어로 개발된 프레임워크들이 있고

모든 분야가 그렇듯 다 경험 해보고 본인이 쓰기 편한 것으로 개발하시면 됩니다.

저 또한 express, flask, 그리고 Django Rest Framework 이렇게 세 종류를 경험해보았고

이 중에서 Django Rest Framework를 주 활용 스택으로 선택한 이유는 다음과 같습니다.

 

(1) Django!

: Django의 가장 큰 장점은 "Django스럽다" 라고 말할 수 있습니다. 이게 무슨 뜻이냐 하면, 개발해야하는 여러 대표적인 기능들이 거의 모두 이미 개발되었으니 그냥 가져다 쓰면 된다는 것입니다. 처음엔 오히려 이것저것 복잡해서 낯설고 친해지는데 오래 걸릴 수 있지만, 나중에는 이미 구현된 이 기능 하나하나에 정말 큰 고마움을 느낄 것입니다.

 

(2) Django의 강력한 ORM

: Django를 사용하다보면 내가 데이터베이스를 쓰고 있던가 싶을 정도로 개발자가 쓰기 좋게 잘 추상화 되어있습니다. 상당히 python 스럽고 직관적인 방법으로 데이터에 접근, 필터링할 수 있으며, 다른 프레임워크로는 얻을 수 없는 경험이라고 생각됩니다.


3. 프로젝트 소개

본 프로젝트에서는 Django Rest Framework를 활용하여 게시판을 만들어보겠습니다.

게시판을 만들면서 당연히 CRUD를 구현하게 될 것이고, 이 과정을 시작하면서 최대한 다양한 방법을 제시하고 그 중에서 한 가지 방법으로 통일하여 쭉 작업할 예정입니다.

 

또한 항상 프로젝트의 시작이 되면서 은근 귀찮고 어려운 회원 가입 / 로그인 인증도 개발할 예정입니다.

당연히 코드도 깃헙을 통해 공유드립니다.

 

기왕이면 해당 프로젝트 하나만 쭉 따라하면서 공부해도 

Django Rest Framework를 여러분들이 마스터 할 수 있기를 바랍니다:)


출처

https://devkor.tistory.com/entry/01-%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0-Django-Rest-Framework%EB%A1%9C-%EA%B2%8C%EC%8B%9C%ED%8C%90-%EB%A7%8C%EB%93%A4%EA%B8%B0?category=734691

메타 데이터는 다른 데이터에 대한 정보를 제공하는 특정 데이터 집합을 나타냅니다.

Django에서는 Django 모델을 사용하여 데이터베이스의 테이블과 해당 필드를 설계합니다.

 

모델 자체에 대한 데이터를 추가해야하는 경우Meta클래스를 사용합니다.

이 기사에서 Django 모델의Meta클래스에 대해 자세히 알아보십시오.


Django의 Meta클래스

Meta클래스는 inner 클래스입니다.

즉, 다음과 같이 모델 내부에 정의됩니다.

from django.db import models

class MyModel(models.Model):
    ...
    class Meta:
        ...

Meta클래스는 권한, 데이터베이스 이름, 단 복수 이름, 추상화, 순서 지정 등과 같은

모델에 대한 다양한 사항을 정의하는 데 사용할 수 있습니다.

 

Django 모델에Meta클래스를 추가하는 것은 전적으로 선택 사항입니다.

이 클래스에는 구성 할 수있는 많은 옵션도 제공됩니다.

다음은 일반적으로 사용되는 몇 가지 메타 옵션입니다. 여기에서 모든 메타 옵션을 탐색 할 수 있습니다.


1. Django 메타 옵션 - Abstract

이 옵션은 모델이 추상적인지 여부를 정의하는 데 사용됩니다. 추상 클래스와 동일하게 작동합니다.

 

추상 클래스는 인스턴스화 할 수없고 확장 또는 상속 만 가능한 클래스입니다.

추상으로 설정된 모델은 상속만 가능합니다.

공통 필드가있는 여러 모델이있는 경우이 옵션을 사용할 수 있습니다.

from django.db import models

class Human(models.Model):
    genders = (
        ("M", "Male"),
        ("F", "Female"),
        ("NB", "Non-binary"),
        ("T", "Transgender"),
        ("I", "Intersex"),
        ("O", "Other"),
        ("PNTS", "Prefer not to say")
    )

    name = models.CharField(max_length = 200)
    age = models.IntegerField(default = 0)
    gender = models.CharField(max_length = 50, choices = genders)

    class Meta:
        abstract = True # Important

class Teacher(Human):
    subject = models.CharField(max_length = 200)
    

class Student(Human):
    grade = models.IntegerField(default = 0)
    

여기에서 선생님 및 학생 모델은 Human 모델 내부에 모든 필드가 있습니다.

데이터베이스 내에는Teacher 및 Student 모델 만 생성됩니다.


2. Django 메타 옵션 - db_table

이 옵션은 데이터베이스 내에서

테이블을 식별하는 데 사용해야하는 이름을 설정하는 데 사용됩니다.

예를 들어 다음과 같은 작업을 수행하면 데이터베이스에서 모델 이름이 job이됩니다.

from django.db import models

class JobPosting(models.Model):
    
    
    class Meta:
        db_table = "job"

 


3. Django 메타 옵션 - Ordering

이 옵션은 모델 필드 인 문자열 값 목록을 사용합니다. 모델 객체의 순서를 정의하는 데 사용됩니다.

이 모델의 개체가 검색되면이 순서대로 표시됩니다.

from django.db import models

class JobPosting(models.Model):
    dateTimeOfPosting = models.DateTimeField(auto_now_add = True)
    
    
    class Meta:
        ordering = ["-dateTimeOfPosting"]

위의 예에서 검색된 개체는dateTimeOfPosting필드를 기준으로 내림차순으로 정렬됩니다.

(-접두사는 내림차순을 정의하는 데 사용됩니다.)


4. Django 메타 옵션 - verbose_name

이 옵션은 사람이 읽을 수있는 모델의 단일 이름을 정의하는 데 사용되며

Django의 기본 명명 규칙을 덮어 씁니다.

이 이름은 관리자 패널 (/admin/)에도 반영됩니다.

from django.db import models

class JobPosting(models.Model):
    
    
    class Meta:
        verbose_name = "Job Posting"

 

5. Django 메타 옵션 - Verbose name plural

이 옵션은 모델에 대해 사람이 읽을 수있는 복수형 이름을 정의하는 데 사용되며

다시 Django의 기본 명명 규칙을 덮어 씁니다. 이 이름은 관리자 패널 (/admin/)에도 반영됩니다.

from django.db import models

class JobPosting(models.Model):
    
    
    class Meta:
        verbose_name_plural = "Job Postings"

 


출처

https://www.delftstack.com/howto/django/class-meta-in-django/

 

ForeignKeyManyToManyField 그리고 OneToOneField 들을 제외한,

각 필드 타입은 선택적으로 첫번째 위치인자로, verbose name 을 받습니다.

 

verbose name 이 주어지지 않았을때는,

Django 는 자동으로 field 의 속성명의 언더스코어를 공백으로 전환하여 verbose name 을 생성합니다.

 

verbose name 은

사용자가 사용하는 자세한 이름을 의미합니다

 

verbose name 을 지정하지 않으면,

어드민에 표시되는 이름이 이상하다는것을 느낄수 있을겁니다.


아래 예시에서, verbose name 은 “person’s first name” 입니다

first_name = models.CharField("person's first name", max_length=30)​
 
 
아래 예시에서는, verbose name 이 “first_name” 입니다.

필드 타입의 첫번째 인자로, verbose name 이 명시되지 않았기 때문에,

필드명 first_name 에서, 언더스코어를 공백으로 전환하여 verbose name 을 표시합니다.

first_name = models.CharField(max_length=30)​
 
verbose_name 을 정의한 모델의 예시입니다.
class Members(models.Model):
    name = models.CharField(max_length=50, verbose_name='이름')
    nick_name = models.CharField(max_length=50, verbose_name="닉네임")
    birth_date = models.DateField(verbose_name="생년월일", null=True)

 

위 Members 모델에서는, name, nick_name, birth_date 이

각각 한글로 표시된 verbose_name 들을 어드민에 표시할것입니다.


1. 외래키 모델들

아직 살펴보지는 않았지만, ForeignKeyManyToManyField 그리고 OneToOneField 들은

첫번째 인자로 모델 클래스가 옵니다.

 

따라서, verbose_name을 키워드 인자로 사용해줍니다.

poll = models.ForeignKey(Poll, on_delete=models.CASCADE, verbose_name="the related poll")
# poll 은 ForeignKey (외래키) 로 Poll 이라는 클래스를 첫번째 인자로 받음 
# verbose_name 은 키워드 인자로 넣어줌 

sites = models.ManyToManyField(Site, verbose_name="list of sites")
# sites 는 ManyToManyField 로 Site 라는 클래스를 첫번째 인자로 받음 
# verbose_name 은 키워드 인자로 넣어줌 

place = models.OneToOneField(Place, on_delete=models.CASCADE, verbose_name='related place')
# place 는 OneToOneField 로 Place 라는 클래스를 첫번째 인자로 받음 
# verbose_name 은 키워드 인자로 넣어줌​

 

위의 예시에서는, 단지 ForeignKeyManyToManyField 그리고 OneToOneField 를 사용할때에,

verbose_name 이 어떻게 키워드 인자로 들어가는지에 대한 코딩 스타일만 보여주고 있습니다.

 

관례적으로,

verbose_name 의 첫번째 글자는 대문자로 써주지 않습니다.

Django 가 필요할때 자동으로 첫번째 글자를 대문자화 해줍니다.


2. 마치며..

verbose_name 에 대해서 알아보았습니다.
verbose_name 을 사용하면, 어떤것이 달라지는지 완벽한 예시가 있으면 좋았을텐데,

현재까지는, verbose_name 을 사용할경우,

해당 필드가 어드민에 표시되는 바가 달라지게 된다는점을 알고 넘어가면 될것 같습니다.

 

지난 포스팅에서 Runner 모델을 작성했었는데,

아래와 같이, name 필드에 verbose_name 을 추가해주었습니다. “러너 이름”

 

class Runner(models.Model):
    MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE')
    name = models.CharField("러너이름", max_length=60)
    medal = models.CharField(blank=True, choices=MedalType.choices, max_length=10)
    
    def __str__(self):
        return self.name + " " + self.medal​

 

위와같이 verbose_name 을 모델 필드에 추가하면,

래처럼 어드민에 “러너이름” 이라고 표시가 됩니다.

 


1. 정참조와 역참조 객체 서로 호출하기

데이터베이스에서 두 테이블이 참조 관계에 있는 경우를 생각해보자.

예를 들어, User 테이블과 사용자의 직업인 Occupation 테이블이 있다.

 

두 테이블은 N:1 관계에 있으며, User 객체가 Occupation 객체를 참조하고 있다. 

User 가 Occupation 을 선택하여 입사 원서를 작성한다고 가정해보자.

class User(models.Model):
    name	= models.CharField(max_length = 50)
    job		= models.ForeignKey('Occupation', on_delete = models.CASCADE)
    created_at	= models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

 

User 객체는 Occupation 객체를 정참조 하고 있으므로,

속성 이름으로 바로 접근 할 수 있다. User1을 선택하여, 그 사람의 job을 찾아보자.

user1 = User.objects.get(id = 1)
user1.job.name
>>> 'Developer'

 

그러나 Occupation 객체는 User 객체를 역참조 하고 있으므로 바로 접근이 불가능하다. 

developer 이라는 Occupation을 가지고 있는 유저를 모두 찾아보자.

job1   = Occupation.objects.get(name = 'developer')
people = job1.user.all() # 이게 될까?

 

❌ 안 됨 ❌

Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Occupation' object has no attribute 'user'

 

그렇다고 절대로 사용하지 못하는 것은 절대 아니니 걱정하지말자.

역참조 관계에 있을 때는 [classname]_set 이라는 속성을 사용하여 접근해야한다.

job1   = Occupation.objects.get(id = 1)
people = job1.user_set.all()
>>> <QuerySet[<Object User Object(1)>, <Object User Object(2)>]>

이 때, user_set 대신 사용할 수 있는 것이 related_name이다. 역참조 대상인 user 객체를 부를 이름.

 

즉, User 클래스를 정의할 때,

정참조 하고 있는 Occupation 클래스의 인스턴스에서 어떤 명칭으로 거꾸로 호출당할 지 정해주는 이름인 것이다.


2. What related names do

앞의 예시를 다시 보자.

아무래도 Occupation의 입장에서는 입사 지원자들을 appliers라고 부르는 것이 더 직관적이고 편할 것 같다.

class User(models.Model):
    name = models.CharField(max_length = 50)
    job	 = models.ForeignKey( 
            'Occupation',
            on_delete    = models.CASCADE,
            related_name = 'appliers' ------------------------- [Key Point !]
        )
    created_at	= models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

 

[Key Point] 를 눈여겨 보자.
User객체를 정의할 때, job이라는 속성에 Occupation객체가 연결되어 정참조하고 있다.

 

Occupation객체의 인스턴스와 연결되어 있는 User 객체를 거꾸로 불러올 때, 

appliers 라는 이름으로 부르기 위해 job 속성에 related_names = 'appliers'를 함께 지정해주었다.

job1   = Occupation.objects.get(id = 1)
people = job1.appliers.all()
>>> <QuerySet[<Object User Object(1)>, <Object User Object(2)>]>

 

잘 동작하는 것을 알 수 있다.

모든 Foreign Key에 related_name을 붙여줄 필요는 없다.

때에 따라, 참조하고 있는 객체 이름에 _set을 붙이는 것이 더 직관적인 경우가 굉장히 많기 때문이다.


3. Related name이 필수인 경우가 있다.

바로 한 클래스에서 서로 다른 두 컬럼(속성)이 같은 테이블(클래스)를 참조하는 경우이다.
앞서 설명한 상황에서, 지원자가 필수로 신청한 occupation외에, 2지망인 occupation도 받는다고 가정해보자.

class User(models.Model):
    name       = models.CharField(max_length = 50)
    job	       = models.ForeignKey('Occupation', on_delete = models.CASCADE)
[*] choice_2nd = models.ForeignKey('Occupation', on_delete = models.CASCADE, null = True)
    created_at = models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

 

참고로 위와 같은 선언은 애초에 마이그레이션이 되지 않는다.

related_name 지정하라는 문구만 뜸.

 

User객체에서 Occupation객체를 정참조 하는 속성이 두 개이다.

다시 말해 developer이라는 Occupations객체의 인스턴스를 1지망으로 선택한 지원자와 2지망으로 선택한 지원자가

따로 구별되어있다는 뜻이 된다. 아래 두 인스턴스를 보자.

user1 = User.objects.create(name = 'Nick', job_id = 1) #developer
user2 = User.objects.create(name = 'Sue', job_id = 2, choice_2nd_id = 1)

user1은 1지망은 job으로 id가 1번인 developer이다.
user2의 1지망은 2번 job이고, 2지망이 developer이다.

job1 = Occupation.objects.get(id = 1)
job1.user_set.all()

의 결과가 생성될 수 있을까?
❌ 안 됨 ❌

 

Occupation객체를 정참조 하고 있는 컬럼이 job과choice_2nd두 개이므로,

그저 user_set이라는 속성만으로는 자신을 바라보고 있는 두 User 객체 가운데

어떤 속성에 접근해야할 지 알 수가 없기 때문이다.

 

즉, developer을

1지망으로 고른 사람들의 목록(Nick)을 가져와야할 지,

2지망으로 고른 사람들의 목록(Sue)을 가져와야할 지 알 수가 없기 때문이다.

class User(models.Model):
    name = models.CharField(max_length = 50)
    job	 = models.ForeignKey( 
            'Occupation',
            on_delete    = models.CASCADE,
            related_name = 'appliers' ------------------------- [Key Point !]
        )
    choice_2nd  = models.ForeignKey(
            'Occupation',
            on_delete    = models.CASCADE,
            null         = True
            related_name = 'second_appliers'
        )
    created_at	= models.DateTimeField(auto_now_add = True)

 

이제는 developer을 1지망으로 지원한 Nick과 2지망으로 지원한 Sue를 구분하여 호출할 수 있다.

job1 = Occupation.objects.get(id = 1)

job1.appliers.all()
>>> <QuerySet[<Object User Object(1)>]> # ---> Nick

job1.second_appliers.all()
>>> <QuerySet[<Object User Object(2)>]> # ---> Sue

ManyToMany 관계에 있을 때에도 related_name은 같은 원리로 동작한다. 헷갈리지 않게 주의하자.

 


4. 마치며

웹의 구조나 서비스가 복잡해질 수록, 클래스 사이의 참조가 많아진다.

일대다는 물론이고, 다대다 관계도 계속 늘어난다.

그럴 때일 수록 related name이 중요하다고 생각한다.

 

어떻게든 migration만 되면 되지. 라는 생각으로 related_name을 마음대로 설정하다 보면,

나중엔 변수 이름을 아무렇게나 정했을 때만큼이나 의미를 알수 없는 코드를 양산하게 되기 때문이다.

오늘도 많이 배웠다.

 


출처

https://djangojeng-e.github.io/2020/08/02/Django-Models-6%ED%8E%B8-Fields-verbose-field-names/

 

Django Models 6편 - Fields (verbose field names)

Models - Fields (verbose field names) ForeignKey, ManyToManyField 그리고 OneToOneField 들을 제외한, 각 필드 타입은 선택적으로 첫번째 위치인자로, verbose name 을 받습니다. verbose name 이 주어지지 않았을때는, Django

djangojeng-e.github.io

https://velog.io/@brighten_the_way/Django%EC%99%80-Reverse-relations%EA%B3%BC-Relatedname

 

Django와 Reverse relations과 Related_name

그래서 결국, 너의 이름은

velog.io

 

+ Recent posts