Search

DRF General

1. Basic

가. 주요 구성요소

1) ViewSets

복수개의 View를 그룹화한 개념

2) Serializer

DRF에서는 모델의 데이터를 Json 형태로 변환 할 수 있도록 함
장고의 Form은 html 생성 vs DRF의 Serializer는 JSON 생성

3) Router

path 설정 주의사항: urlpatterns의 path 중 중복된 경로가 있으면 가장 많이 중복되는 path는 하위에 위치시킨다
Router 설정은 ViewSet에만 해당되며 APIView, GenericView 등은 일반 django path method 활용하여 routing 설정함
# viewset + router router.register(r'cattles/sales/reports', CattleSaleReportViewSet) # genericview + path path('cattles/sales/form', FileUploadView.as_view(), name='file_upload_view'),
Python
복사

나. Pagination

1) 페이지 설정

# tutorial/settings.py REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10 }
Python
복사

2. ViewSet 상세

가. Views

1) 상속관계

View → APIView
class View:
Python
복사
class APIView(View):
Python
복사

나. Generic views

1) 상속관계

View → APIView → GenericAPIView → GenericViewSet
class GenericAPIView(views.APIView):
Python
복사
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
Python
복사

다. Mixins

1) RetrieveModelMixin, ListModelMixin

이하 다른 ModelMixin과 달리 perform_* 메소드는 정의돼있지 않음
class ListModelMixin: """ List a queryset. """ def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class RetrieveModelMixin: """ Retrieve a model instance. """ def retrieve(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer(instance) return Response(serializer.data)
Python
복사

2) CreateModelMixin

request.data 외에 request의 특정 값을 이용하여 데이터 갱신시 유용하게 사용 가능
perform_create 메소드를 overriding하는 방향 권장
def perform_create(self, serializer): serializer.save(user=self.request.user)
Python
복사
class CreateModelMixin: """ Create a model instance. """ def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def perform_create(self, serializer): serializer.save() def get_success_headers(self, data): try: return {'Location': str(data[api_settings.URL_FIELD_NAME])} except (TypeError, KeyError): return {}
Python
복사

3) UpdateModelMixin

perform_update(self, serializer) 제공
class UpdateModelMixin: """ Update a model instance. """ def update(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) self.perform_update(serializer) if getattr(instance, '_prefetched_objects_cache', None): # If 'prefetch_related' has been applied to a queryset, we need to # forcibly invalidate the prefetch cache on the instance. instance._prefetched_objects_cache = {} return Response(serializer.data) def perform_update(self, serializer): serializer.save() def partial_update(self, request, *args, **kwargs): kwargs['partial'] = True return self.update(request, *args, **kwargs)
Python
복사

4) DestroyModelMixin

perform_destroy(self, instance) 제공
class DestroyModelMixin: """ Destroy a model instance. """ def destroy(self, request, *args, **kwargs): instance = self.get_object() self.perform_destroy(instance) return Response(status=status.HTTP_204_NO_CONTENT) def perform_destroy(self, instance): instance.delete()
Python
복사

라. Viewsets

1) 상속관계

View → APIView → GenericAPIView → GenericViewSet → ModelViewSet
class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet):
Python
복사

3. Serializers 상세

가. Serializer 역할

1) Converting data

QuerySet, 모델 인스턴스를 파이썬 데이터 타입(dictionary 등)으로 변환
→ 결과적으로 JSON, XML 등으로 쉽게 렌더링하는 것이 목적

2) Validating the incoming data

외부로부터 들어오는 데이터에 대해 유효 여부를 확인
→ 유효성이 검증된 데이터에 대해 deserializing

나. Serializer 동작 방식

1) Serializing

모델 인스턴스를 Serializer 클래스의 인스턴스로 전환
→ 모델 데이터를 파이썬 타입 데이터(Dictionary)로 Converting
serializer = CommentSerializer(comment) serializer.data # {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
Python
복사
파이썬 타입 데이터를 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"}'
Python
복사

2) Deserializing

입력 데이터를 JSON 형태로 파싱
import io from rest_framework.parsers import JSONParser stream = io.BytesIO(json) data = JSONParser().parse(stream)
Python
복사
JSON 형태에서 파이썬 데이터 타입으로 복원
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)}
Python
복사

다. 유효성 검사

1) 예외 발생 방식

역직렬화(deserializing) 시 입력 데이터에 접근 또는 입력 데이터 저장 시 is_valid() 호출
→ 입력 데이터가 유효하지 않다면 .errors에 에러 메시지 저장
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'}) serializer.is_valid() # False serializer.errors # {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}
Python
복사
is_valid() 에 raise_exception 플래그를 활성화하면 HTTP 400 Bad Request 반환
# Return a 400 response if the data was invalid. serializer.is_valid(raise_exception=True)
Python
복사

2) field level validation

Serializer 내 각각의 필드에 대해 유효성 검사 정의 시 활용
→ validate_<field_name> 형태로 메소드 정의
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
Python
복사
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]) ...
Python
복사

3) object level validation

Serializer 내 복수의 필드를 활용한 유효성 검사를 정의 시 활용
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 start is before finish. """ if data['start'] > data['finish']: raise serializers.ValidationError("finish must occur after start") return data
Python
복사
QuerySet 내 특정 필드를 대상으로 재사용 가능한 유효성 검사 로직 적용 가능
→ Serializer 내 inner Meta class 부분에 적용 가능
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'] ) ]
Python
복사

4) validators in ModelSerializer

Model 객체에 선언된 제약사항에 따라 ModelSerializer 내 자동으로 관련 유효성 검사 생성
>>> from project.example.serializers import CustomerReportSerializer >>> serializer = CustomerReportSerializer() >>> print(repr(serializer)) CustomerReportSerializer(): id = IntegerField(label='ID', read_only=True) time_raised = DateTimeField(read_only=True) reference = CharField(max_length=20, validators=[<UniqueValidator(queryset=CustomerReportRecord.objects.all())>]) description = CharField(style={'type': 'textarea'}) ### 이하와 같이 선언되면 위와 같이 관련 유효성 검사 자동 생성 class CustomerReportRecord(models.Model): time_raised = models.DateTimeField(default=timezone.now, editable=False) reference = models.CharField(unique=True, max_length=20) description = models.TextField() class CustomerReportSerializer(serializers.ModelSerializer): class Meta: model = CustomerReportRecord
Python
복사
ModelSerializer 내 자동 생성된 유효성 검사를 사용하지 않고 직접 조작하려면 Meta innner class 내 validators에 빈 리스트를 할당
class BillingRecordSerializer(serializers.ModelSerializer): def validate(self, attrs): # Apply custom validation either here, or in the view. class Meta: fields = ['client', 'date', 'amount'] extra_kwargs = {'client': {'required': False}} validators = [] # Remove a default "unique together" constraint.
Python
복사

다. 정방향 관계 vs 역방향 관계

1) 자식 모델 호출 결과 비교

정방향 관계: 왼쪽 예시, Book(1:N=Author:Book)
→ 자식 모델에 정의된 외래키에 부모 모델에 매칭되는 Serializer를 맵핑
역방향 관계: 오른쪽 예시, Address(1:N=Reader:Address)
# Book [ { "id": 1, "author": { "id": 1, "name": "MJ" }, "name": "123", "created_at": "2021-10-22T06:20:32.160442Z" }, { "id": 2, "author": { "id": 1, "name": "MJ" }, "name": "123123", "created_at": "2021-10-22T06:20:35.061096Z" }, { "id": 3, "author": { "id": 1, "name": "MJ" }, "name": "123123123", "created_at": "2021-10-22T06:20:37.859903Z" } ]
Python
복사
# Address [ { "detail": "그곳이 그곳", "city": 1.0 }, { "detail": "저곳이 저곳", "city": 2.0 }, { "detail": "이런곳도 있음", "city": 3.0 } ]
Python
복사

2) 부모 모델 호출 결과 비교

정방향 관계: 왼쪽, Author(1:N=Author:Book)
역방향 관계: 오른쪽, Reader(1:N=Reader:Address)
→ 부모 모델에 매칭되는 Serializer 내부에 Serializer Filed를 정의하고 해당 필드에 자식 모델에 매칭되는 Serializer를 맵핑
# Author [ { "id": 1, "name": "MJ" }, { "id": 2, "name": "배만진" }, { "id": 3, "name": "mj" } ]
Python
복사
# Reader [ { "name": "MJ", "email": "gentlemmj@gmail.com", "addresses": [ { "detail": "그곳이 그곳", "city": 1.0 }, { "detail": "저곳이 저곳", "city": 2.0 }, { "detail": "이런곳도 있음", "city": 3.0 }, { "detail": "정말 가고프다", "city": 123.0 } ] }, { "name": "KK", "email": "123@naver.com", "addresses": [ { "detail": "모르겠다", "city": 4.0 } ] } ]
Python
복사

라. SerailzierMethodFiled

1) 필드 값 수정

Serializer에서 특정 값 수정
→ 특정 값이 None이면 문자열 공백("")으로 반환
class FarmsSerializer(serializers.ModelSerializer): class Meta: model = Farms fields = "__all__" def get_address1(self, obj): return obj.address1 if obj.address1 is not None else "" def get_address2(self, obj): return obj.address2 if obj.address2 is not None else ""
Python
복사
참고) Model of farms
from django.db import models class Farms(models.Model): no = models.IntegerField(null=True) farmname = models.CharField(max_length=100, null=True) owner = models.CharField(max_length=50, null=True) farmUniqueNo = models.CharField(max_length=50, null=True) livestockNo = models.CharField(max_length=50, null=True) address1 = models.TextField(null=True) address2 = models.TextField(null=True) class Meta: managed = True db_table = 'farms'
Python
복사

3. S3 + Excel Data

가. Download from S3

1) Access to S3 Object with Pre-Signed URL

@api_view(['GET']) @transaction.atomic def download_basic_form_view(request): object_key = 'temp/temp.xlsx' try: response = _get_signed_uri(object_key) except ClientError as e: logging.error(e) return Response({"S3 정보가 올바르지 않습니다."}, status.HTTP_400_BAD_REQUEST) return Response({"signature-url": response}, status.HTTP_200_OK) def _get_signed_uri(object_key): s3_client = boto3.client('s3', aws_access_key_id='<AWS ACCESS KEY>', aws_secret_access_key='<AWS SECRET KEY>', region_name='<AWS RIGION>') return s3_client.generate_presigned_url('get_object', Params={'Bucket': '<버킷 이름>', 'Key': object_key}, ExpiresIn=3600)
Python
복사

나. Upload to WAS

1) Upload CSV file

아래와 같이 FileUploadSerialier 활용하여 form-data 내 key 값이 file인 file 객체를 가져올 수 있음
@api_view(['POST']) def file_upload_view(request): serializer = FileUploadSerializer(data=request.data) serializer.is_valid(raise_exception=True) file = serializer.validated_data['file'] decoded_file = file.read().decode() io_string = io.StringIO(decoded_file) reader = csv.reader(io_string) rows = list(reader) for row in rows[1:]: public_id = row[0] date = row[1] weight = row[2] destination = row[3] price = row[4] memo = row[5]
JavaScript
복사
Serializer
class FileUploadSerializer(serializers.Serializer): file = serializers.FileField() class Meta: fields = ('file',)
Python
복사

2) Upload xlsx file

Transaction in atomic
@api_view(['POST']) @transaction.atomic def upload_excel_view(request): serializer = FileUploadSerializer(data=request.data) serializer.is_valid(raise_exception=True) file = serializer.validated_data['file'] decoded_file = get_data(file) rows = decoded_file['시트1'] try: with transaction.atomic(): _save_sales_in_bulk(rows) except IntegrityError: raise exceptions.ParseError("엑셀 데이터 업로드에 이상이 있습니다.") return Response({"status": "success"}, status.HTTP_201_CREATED)
Python
복사
Bulk Creat
def _save_in_bulk(rows): sales = [] rows_length = len(rows[0]) for row in rows[1:]: public_id, date, weight, destination, price, memo = refine_sale_cells(row, rows_length) try: dog = Dog.objects.get(public_id=public_id) except Dog.DoesNotExist: raise exceptions.ParseError("id가 올바르지 않습니다.") cattle.shipment_status = "sale" cattle.save() sales.append(DogSale(cattle_id=cattle.id)) DogSale.objects.bulk_create(sales)
Python
복사

4. Exception Handling

가. Exception Basic

1) Globally Handling in View

핵심 아이디어: 예외는 결국 에러 처리를 용이하게 하는 것이므로 사용자(프런트엔드 개발자)에게 관련 예외 메시지를 효과적으로 전달할 수 있어야 한다
이하 컴포넌트에서 발생한 예외를 View에서 가져와서 Error 메시지로 이어짐
except KeyError: return JsonResponse({"message": "KEY_ERROR"}, status=400) except BlankFieldException as e: return JsonResponse({"message": e.__str__()}, status=400) except User.DoesNotExist: return JsonResponse({"message": "INVALID_USER"}, status=400) except Exception as e: return JsonResponse({"message": "UNKNOWN_ERROR"}, status=400)
Python
복사
code from https://velog.io/@pm1100tm/PythonDjango-1%EB%B2%88%EC%A7%B8-%EC%BD%94%EB%93%9C-%EB%A6%AC%EB%B7%B0

나. Custom Exception

5. know-hows

가. Reverse relationships in serializers

1) 사용 목적

1:N 관계에서 부모 테이블 기준으로 연관된 자식 테이블의 값을 포함해서 전달
→ 자식 테이블에는 부모 테이블의 PK를 FK로 갖지만 부모 테이블에는 자식 테이블과의 접점이 없으므로 역방향 관계를 활용

2) 사용 방법

Serializer에 자식 테이블과 연관된 Serializer에 대해 Serializerㅏiled로 할당
1:N 관계이므로 'many=True' 설정
조회 요청에 따른 응답값으로 추가된 필드이므로 'read_only=True' 설정
결과

3) 활용

큰 주제를 중심으로 하위 주제를 묶은 형태로 Response에 담아서 전달할 때 사용

나. Read-only derived data to the response

1) 사용 목적

모델 내 값을 활용하여 추가적인 데이터를 Response로 전달

2) 사용 방법

Serializer에서 SerializerMethodField 선언
SerializerMethodField의 구현부에서 모델 내 값을 활용하여 필요한 데이터 연산 후 반환
SerializerMethodField 의 구현부에는 매개변수로 self 외 오직 해당 모델에 대한 변수만 허용된다
→ 이하에서 book 외에 author(book의 부모 모델)는 매개변수로 전달할 수 없다
def get_book_display_name(self, book): return book.name.upper()
Python
복사

다. Serializer context

1) 목적

Serializer에서 데이터 조작에 필요한 추가적인 데이터를 view에서 받아오는 방법

2) 사용법

view 내부에 serializer에서 context를 가져갈 수 있도록 관련 메소드를 정의함
serializer에서 context 반환 메소드(view에서 정의)를 활용하여 필요한 데이터 추출
코드 참고

3) 활용

Serializer에 추가한 필드 갱신 시 request 정보를 view로부터 받아서 조작이 필요한 경우
Serializer에서 특정 값의 유효성 검사 시 view나 model에서 추가적인 데이터가 필요한 경우

Reference

SerailzierMethodFileds, https://eunjin3786.tistory.com/268