본문 바로가기
소프트웨어 마에스트로/BackEnd(Django)

[백엔드] serializer validation 추가하기 ( Fat model)

by alpakaka 2024. 8. 14.

view 에 모든 걸 넣어서 해결했었는데 오늘은 이 view 에 있는 validation 들을 serializer 로 이동시켜줄 예정이다.

 

일단 Todo 부터 진행해본다.

 

Validation 이 필요한 사항

user_id : 유효 여부

category_id : 유효 여부

date : start date 가 end_date 보다 앞서거나 같은지

order : order 가 lexorank 를 따르는가?

 

일단 위의 3개의 validation 은 간단했다.

그런데 order 에서 문제가 발생했는데

def validate_order(self, data):
        request = self.contect['request'].method
        print("request", request)
        if request and request.method == 'PATCH':
            order_data = request.data.get('order')
            prev_id = order_data.get('prev_id')
            next_id = order_data.get('next_id')
            updated_order = order_data.get('updated_order')
            prev = None
            next = None
            if prev_id:
                prev = Todo.objects.filter(id=prev_id, deleted_at__isnull=True).first().order
            if next_id:
                next = Todo.objects.filter(id=next_id, deleted_at__isnull=True).first().order
            if validate_lexo_order(prev=prev, next=next, updated=updated_order) is False:
                raise serializers.ValidationError("Order is invalid")

        elif request.method == 'POST':
            last_todo = Todo.objects.filter(user_id=data['user_id'], deleted_at__isnull=True).order_by('-order').first()
            if last_todo is not None:
                last_order = last_todo.order
                if validate_lexo_order(prev=last_order, next=None, updated=data['order']) is False:  
                        raise serializers.ValidationError("Order is invalid")

이런식으로 patch 냐 post 냐 에 따라 로직이 좀 변화한다.

그래서 patch 인지 post 인지 확인하고 조건에 따라 다른 로직을 실행시키도록 해야하는데 이게 좀 까다로웠다.

 

혹시나 내가 validation 의 범위를 너무 크게 잡고 하고 있나 생각해서 validate 가 아닌 create, update 함수에 로직을 넣을 까 했었다. 그런데 이미 validation 이 완료된 상태에서 저장 중인데 중간에 이 값은 '유효하지 않음.' 이라고 저장하는 도중에 멈춰버리면 머리가 아플 것 같아 최대한 validation 에서 할 수 있도록 작성할 예정이다..

 

lab

https://stackoverflow.com/questions/69275520/django-rest-framework-during-validation-check-the-request-method-type

 

Django Rest-Framework During Validation, Check the Request Method Type

I am working on API Validation Errors to be called. I have to make sure 2 dates don't overlap when making new "POST" calls, which is working fine. I am doing a model.objects.Filter() quer...

stackoverflow.com

문제사항 - self 에서 request 항목자체가 보이지 않음 (print 로 확인)

 

chatgpt 의 답변

self.context.get('request') 를 해라! -> 위의 이유와 같다. 안된다!

그러면 serializer 를 부를 때 context = request 항목을 추가해봐라! -> 안됨

 

그래서 그냥 chatgpt 에게 검색어를 추천해달라고 했다.

 

의외로 공식문서에도 있는 방법이라 다시 한번 view 코드를 찾아봤는데..

response 에 넣고 있었다.. ;) 

serializer 에 넣으니까 간단히 해결되었다... ^^

def post(self, request):
        '''
        - 이 함수는 todo를 생성하는 함수입니다.
        - 입력 : user_id, start_date, deadline, content, category, parent_id
        - content 는 암호화 되어야 합니다.
        - deadline 은 항상 start_date 와 같은 날이거나 그 이후여야합니다
        - category_id 는 category에 존재해야합니다.
        - content는 1자 이상 50자 이하여야합니다.
        - user_id 는 user 테이블에 존재해야합니다.
        - parent_id는 todo 테이블에 이미 존재해야합니다.
        - parent_id가 없는 경우 null로 처리합니다.
        - parent_id는 자기 자신을 참조할 수 없습니다.

        구현해야할 내용
        - order 순서 정리
        - 암호화
        '''
        data = request.data
        # category_id validation
        serializer = TodoSerializer(context={'request': request}, data=data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

Todoserializer(context={'request': request} , ...) 부분을 추가해줬다.

그다음에 serializer 를 다음과 같이 수정해줬다.

def validate_order(self, data):
        request = self.context['request']
        if request.method == 'PATCH':
            order_data = request.data.get('order')
            prev_id = order_data.get('prev_id')
            next_id = order_data.get('next_id')
            updated_order = order_data.get('updated_order')
            prev = None
            next = None
            if prev_id:
                prev = Todo.objects.filter(id=prev_id, deleted_at__isnull=True).first().order
            if next_id:
                next = Todo.objects.filter(id=next_id, deleted_at__isnull=True).first().order
            if validate_lexo_order(prev=prev, next=next, updated=updated_order) is False:
                raise serializers.ValidationError("Order is invalid")
        return data

 

해결!

 

https://hyeo-noo.tistory.com/324

 

[Django] Serializer와 validate (TIP!)

POST method로 새로운 글을 만들 때는 제목이 중복되면 안된다. 중복되면 안되는 필드가 있다고 생각하면 된다. 하지만 해당 글을 수정하고 저장할 때의 제목 중복여부는 수정 이전의 제목과 같을

hyeo-noo.tistory.com

일단 여기 블로그가 정말 잘 정리되어있었는데 save 함수를 호출할 때 하는방법을 소개하고 있다.

근데 일단 나의 고집상으로 validation 에서 진행하고 싶었으므로 일단 잠시 묻어놨다.

 

문제상황2
serializer 는 다음과 같다.

class TodoSerializer(serializers.ModelSerializer):
    content = serializers.CharField(max_length=255)
    category_id = serializers.PrimaryKeyRelatedField(
        queryset=Category.objects.all(), required=True
    )
    user_id = serializers.PrimaryKeyRelatedField(
        queryset=User.objects.all(), required=True
    )
    start_date = serializers.DateField(allow_null=True, required=False)
    end_date = serializers.DateField(allow_null=True, required=False)
    order = serializers.CharField(max_length=255, required=False)
    is_completed = serializers.BooleanField(default=False, required=False)

request를 다음과 같이 받는다.

{
 id : 1,
 order : {
 		prev_id: 2,
        next_id: 3,
        updated_order : "asdf"
        }
        
}

patch 시에 이런식으로 받는데 이런식으로 받으니 문제가 생겼다.

validation 상 맞는 order 임에도 str 타입이 아니라고 계속 400에러를 주는 문제였다.

 

chatgpt 에게 물어본 결과는 post, patch 에 따라 order 의 serializer 를 바꾸라는 내용이 있었다.

일단 SerializerMethodField() 이키워드로 serializer 를 변경할 수 있는 모양이라 찾아봤다.

https://www.django-rest-framework.org/api-guide/fields/ 

 

Serializer fields - Django REST framework

 

www.django-rest-framework.org

 

여기를 참고한 결과 read-only 였다.

그리고 생각해보니 저장하는 것 자체는 또 string 만 저장한다. 

좀 더 생각을 해봐야 할 것 같다.

 

여러가지 사항을 고민해봤는데 아직 내가 해결하기 어렵다고 결론지었다.

따라서 patch order 를 제외한 부분을 모두 serializer 에 validation 으로 넣었다.

 

patch order 는 따로 멘토님께 질문 드려야 할 것 같다.

일단 그래서 완성되었다.

 

더보기
# todos/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi

 

from todos.models import Todo, SubTodo, Category
from todos.serializers import TodoSerializer, GetTodoSerializer, SubTodoSerializer, CategorySerializer
from todos.swagger_serializers import SwaggerTodoPatchSerializer, SwaggerSubTodoPatchSerializer, SwaggerCategoryPatchSerializer
from todos.utils import validate_lexo_order

 

from rest_framework.permissions import IsAuthenticated, AllowAny
from onestep_be.settings import client
import json

 

from copy import deepcopy

 

class TodoView(APIView):
permission_classes = [AllowAny]
queryset = Todo.objects.all()

 

@swagger_auto_schema(tags=['Todo'], request_body=TodoSerializer, operation_summary='Create a todo', responses={201: TodoSerializer})
def post(self, request):
'''
- 이 함수는 todo를 생성하는 함수입니다.
- 입력 : user_id, start_date, deadline, content, category, parent_id
- content 는 암호화 되어야 합니다.
- deadline 은 항상 start_date 와 같은 날이거나 그 이후여야합니다
- category_id 는 category에 존재해야합니다.
- content는 1자 이상 50자 이하여야합니다.
- user_id 는 user 테이블에 존재해야합니다.
- parent_id는 todo 테이블에 이미 존재해야합니다.
- parent_id가 없는 경우 null로 처리합니다.
- parent_id는 자기 자신을 참조할 수 없습니다.

 

구현해야할 내용
- order 순서 정리
- 암호화
'''
data = request.data
# category_id validation
serializer = TodoSerializer(context={'request': request}, data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
 
@swagger_auto_schema(tags=['Todo'],manual_parameters=[
openapi.Parameter('user_id', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, description='user_id', required=True),
openapi.Parameter('start_date', openapi.IN_QUERY, type=openapi.TYPE_STRING, format=openapi.FORMAT_DATE, description='start_date', required=False),
openapi.Parameter('end_date', openapi.IN_QUERY, type=openapi.TYPE_STRING, format=openapi.FORMAT_DATE, description='end_date', required=False)
],operation_summary='Get a todo', responses={200: GetTodoSerializer})
def get(self, request):
'''
- 이 함수는 daily todo list를 불러오는 함수입니다.
- 입력 : user_id(필수), start_date, end_date
- start_date와 end_date가 없는 경우 user_id에 해당하는 모든 todo를 불러옵니다.
- start_date와 end_date가 있는 경우 user_id에 해당하는 todo 중 start_date와 end_date 사이에 있는 todo를 불러옵니다.
- order 의 순서로 정렬합니다.
'''
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
user_id = request.GET.get('user_id')
if user_id is None:
return Response({"error": "user_id must be provided"}, status=status.HTTP_400_BAD_REQUEST)
try:
if start_date is not None and end_date is not None: # start_date and end_date are not None
todos = Todo.objects.get_daily_with_date(user_id=user_id, start_date=start_date, end_date=end_date)
else: # start_date and end_date are None
todos = Todo.objects.get_with_user_id(user_id=user_id).order_by('order')
except Todo.DoesNotExist:
return Response({"error": "Todo not found"}, status=status.HTTP_404_NOT_FOUND)
serializer = GetTodoSerializer(todos, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
 
@swagger_auto_schema(tags=['Todo'], request_body=SwaggerTodoPatchSerializer, operation_summary='Update a todo', responses={200: TodoSerializer})
def patch(self, request):
'''
- 이 함수는 todo를 수정하는 함수입니다.
- 입력 : todo_id, 수정 내용
- 수정 내용은 content, category, start_date, end_date 중 하나 이상이어야 합니다.
- order 의 경우 아래와 같이 제시된다.
"order" : {
"prev_id" : 1,
"next_id" : 3,
"updated_order" : "0|asdf:"
}
'''
request_data = deepcopy(request.data)
todo_id = request.data.get('todo_id')
try:
todo = Todo.objects.get(id=todo_id, deleted_at__isnull=True)
except Todo.DoesNotExist:
return Response({"error": "Todo not found"}, status=status.HTTP_404_NOT_FOUND)
# validate order
if request.data.get('order'):
order_data = request.data.get('order')
prev_id = order_data.get('prev_id')
next_id = order_data.get('next_id')
updated_order = order_data.get('updated_order')
prev = None
next = None
if prev_id :
prev = Todo.objects.get_with_id(id=prev_id).order
if next_id :
next = Todo.objects.get_with_id(id=next_id).order
if validate_lexo_order(prev=prev, next=next, updated=updated_order) is False:
return Response({"error": "Invalid order"}, status=status.HTTP_400_BAD_REQUEST)
else:
request_data['order'] = updated_order
 
serializer = TodoSerializer(context={'request' : request}, instance=todo, data=request_data, partial=True)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
 
return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)



@swagger_auto_schema(tags=['Todo'], request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'todo_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='todo_id'),
}), operation_summary='Delete a todo', responses={200: TodoSerializer})
def delete(self, request):
'''
- 이 함수는 todo를 삭제하는 함수입니다.
- 입력 : todo_id
- todo_id에 해당하는 todo의 deleted_at 필드를 현재 시간으로 업데이트합니다.
- deleted_at 필드가 null이 아닌 경우 이미 삭제된 todo입니다.
- 해당 todo 에 속한 subtodo 도 전부 delete 를 해야함
'''
todo_id = request.data.get('todo_id')

 

try:
todo = Todo.objects.get_with_id(id=todo_id)
subtodos = SubTodo.objects.get_subtodos(todo.id)
SubTodo.objects.delete_many(subtodos)
Todo.objects.delete_instance(todo)
except Todo.DoesNotExist:
return Response({"error": "Todo not found"}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
 
return Response({"todo_id": todo.id, "message": "Todo deleted successfully"}, status=status.HTTP_200_OK)
 

 

class SubTodoView(APIView):
permission_classes = [AllowAny]
@swagger_auto_schema(tags=['SubTodo'], request_body=SubTodoSerializer(many=True), operation_summary='Create a subtodo', responses={201: SubTodoSerializer})
def post(self, request):
'''
- 이 함수는 sub todo를 생성하는 함수입니다.
- 입력 : todo, date, content, order
- subtodo 는 리스트에 여러 객체가 들어간 형태를 가집니다.
- content 는 암호화 되어야 합니다(// 미정)
- date 는 parent의 start_date와 end_date의 사이여야 합니다.
'''
data = request.data
serializer = SubTodoSerializer(context={'request': request}, data=data, many=True)

 

if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
 
@swagger_auto_schema(tags=['SubTodo'],manual_parameters=[
openapi.Parameter('todo_id', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, description='todo_id', required=True)
],operation_summary='Get a subtodo', responses={200: SubTodoSerializer})
def get(self, request):
'''
- 이 함수는 sub todo list를 불러오는 함수입니다.
- 입력 : todo_id
- parent_id에 해당하는 sub todo list를 불러옵니다.
'''
todo_id = request.GET.get('todo_id')
try:
sub_todos = SubTodo.objects.get_subtodos(todo_id= todo_id)
except SubTodo.DoesNotExist:
return Response({"error": "SubTodo not found"}, status=status.HTTP_404_NOT_FOUND)
 
serializer = SubTodoSerializer(sub_todos, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
 

 

@swagger_auto_schema(tags=['SubTodo'], request_body=SwaggerSubTodoPatchSerializer, operation_summary='Update a subtodo', responses={200: SubTodoSerializer})
def patch(self, request):
'''
- 이 함수는 sub todo를 수정하는 함수입니다.
- 입력 : subtodo_id, 수정 내용
- 수정 내용은 content, date, parent_id 중 하나 이상이어야 합니다.
- order 의 경우 아래와 같이 수신됨
"order" : {
"prev_id" : 1,
"next_id" : 3,
"updated_order" : "0|asdf:"
}
'''
subtodo_id = request.data.get('subtodo_id')
request_data = deepcopy(request.data)
try:
sub_todo = SubTodo.objects.get(id=subtodo_id, deleted_at__isnull=True)
except SubTodo.DoesNotExist:
return Response({"error": "SubTodo not found"}, status=status.HTTP_404_NOT_FOUND)
 
# validate order
if request.data.get('order'):
order_data = request.data.get('order')
prev_id = order_data.get('prev_id')
next_id = order_data.get('next_id')
updated_order = order_data.get('updated_order')
prev = None
next = None
if prev_id:
prev = SubTodo.objects.filter(id=prev_id, deleted_at__isnull=True).first().order
if next_id:
next = SubTodo.objects.filter(id=next_id, deleted_at__isnull=True).first().order
if validate_lexo_order(prev=prev, next=next, updated=updated_order) is False:
return Response({"error": "Invalid order"}, status=status.HTTP_400_BAD_REQUEST)
else:
request_data['order'] = updated_order

 

serializer = SubTodoSerializer(context={'request': request}, instance=sub_todo, data=request_data, partial=True)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
 
return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

 

@swagger_auto_schema(tags=['SubTodo'],request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'subtodo_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='subtodo_id'),
}),operation_summary='Delete a subtodo', responses={200: SubTodoSerializer})
def delete(self, request):
'''
- 이 함수는 sub todo를 삭제하는 함수입니다.
- 입력 : subtodo_id
- subtodo_id에 해당하는 sub todo의 deleted_at 필드를 현재 시간으로 업데이트합니다.
- deleted_at 필드가 null이 아닌 경우 이미 삭제된 sub todo입니다.
'''
subtodo_id = request.data.get('subtodo_id')
print(subtodo_id)
try:
sub_todo = SubTodo.objects.get_with_id(id=subtodo_id)
SubTodo.objects.delete_instance(sub_todo)
except SubTodo.DoesNotExist:
return Response({"error": "SubTodo not found"}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)

 

return Response({"subtodo_id": sub_todo.id, "message": "SubTodo deleted successfully"}, status=status.HTTP_200_OK)
 
class CategoryView(APIView):
permission_classes = [AllowAny]
@swagger_auto_schema(tags=['Category'], request_body=CategorySerializer, operation_summary='Create a category', responses={201: CategorySerializer})
def post(self, request):
'''
- 이 함수는 category를 생성하는 함수입니다.
- 입력 : user_id, title, color
- title은 1자 이상 50자 이하여야합니다.
- color는 7자여야합니다.
'''
data = request.data

 

serializer = CategorySerializer(context={'request': request}, data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
 
@swagger_auto_schema(tags=['Category'], request_body=SwaggerCategoryPatchSerializer, operation_summary='Update a category', responses={200: CategorySerializer})
def patch(self, request):
'''
- 이 함수는 category를 수정하는 함수입니다.
- 입력 : category_id, 수정 내용
- 수정 내용은 title, color 중 하나 이상이어야 합니다.
- order 의 경우 아래와 같이 수신됨
"order" : {
"prev_id" : 1,
"next_id" : 3,
"updated_order" : "0|asdf:"
}
'''
category_id = request.data.get('category_id')
request_data = deepcopy(request.data)
if 'user_id' in request.data:
return Response({"error": "user_id cannot be updated"}, status=status.HTTP_400_BAD_REQUEST)
try:
category = Category.objects.get(id=category_id, deleted_at__isnull=True)
except Category.DoesNotExist:
return Response({"error": "Category not found"}, status=status.HTTP_404_NOT_FOUND)
 
# validate order
if request.data.get('order'):
order_data = request.data.get('order')
prev_id = order_data.get('prev_id')
next_id = order_data.get('next_id')
updated_order = order_data.get('updated_order')
prev = None
next = None
if prev_id:
prev = Category.objects.filter(id=prev_id, deleted_at__isnull=True).first().order
if next_id:
next = Category.objects.filter(id=next_id, deleted_at__isnull=True).first().order
if validate_lexo_order(prev=prev, next=next, updated=updated_order) is False:
return Response({"error": "Invalid order"}, status=status.HTTP_400_BAD_REQUEST)
else:
request_data['order'] = updated_order
 
serializer = CategorySerializer(context={'request':request}, instance=category, data=request_data, partial=True)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
 
return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
 
@swagger_auto_schema(tags=['Category'],manual_parameters=[
openapi.Parameter('user_id', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, description='user_id', required=True)
],operation_summary='Get a category', responses={200: CategorySerializer})
def get(self, request):
'''
- 이 함수는 category list를 불러오는 함수입니다.
- 입력 : user_id(필수)
- user_id에 해당하는 category list를 불러옵니다.
'''
user_id = request.GET.get('user_id')
if user_id is None:
return Response({"error": "user_id must be provided"}, status=status.HTTP_400_BAD_REQUEST)
try:
categories = Category.objects.get_with_user_id(user_id=user_id)
except Category.DoesNotExist:
return Response({"error": "Category not found"}, status=status.HTTP_404_NOT_FOUND)
serializer = CategorySerializer(categories, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
 
@swagger_auto_schema(tags=['Category'], request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'category_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='Category_id'),
}),operation_summary='Delete a category', responses={200: CategorySerializer})
def delete(self, request):
'''
- 이 함수는 category를 삭제하는 함수입니다.
- 입력 : category_id
- category_id에 해당하는 category의 deleted_at 필드를 현재 시간으로 업데이트합니다.
- deleted_at 필드가 null이 아닌 경우 이미 삭제된 category입니다.
'''
category_id = request.data.get('category_id')
try:
category = Category.objects.get_with_id(id=category_id)
Category.objects.delete_instance(category)
return Response({"category_id": category.id, "message": "Category deleted successfully"}, status=status.HTTP_200_OK)
except Category.DoesNotExist:
return Response({"error": "Category not found"}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
 
class InboxView(APIView):
permission_classes = [AllowAny]
@swagger_auto_schema(tags=['InboxTodo'],manual_parameters=[
openapi.Parameter('user_id', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, description='user_id', required=True),
],operation_summary='Get Inbox todo', responses={200: GetTodoSerializer})
def get(self, request):
'''
- 이 함수는 daily todo list를 불러오는 함수입니다.
- 입력 : user_id(필수)
- order 의 순서로 정렬합니다.
'''
user_id = request.GET.get('user_id')

 

if user_id is None:
return Response({"error": "user_id must be provided"}, status=status.HTTP_400_BAD_REQUEST)
try:
todos = Todo.objects.get_inbox(user_id=user_id)
except Todo.DoesNotExist:
return Response({"error": "Inbox is Empty"}, status=status.HTTP_404_NOT_FOUND)
serializer = GetTodoSerializer(todos, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
 
class RecommendSubTodo(APIView):
permission_classes = [AllowAny]
@swagger_auto_schema(tags=['RecommendSubTodo'],manual_parameters=[
openapi.Parameter('todo_id', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, description='todo_id', required=True),
],operation_summary='Recommend subtodo', responses={200: SubTodoSerializer})
def get(self, request):
'''
- 이 함수는 sub todo를 추천하는 함수입니다.
- 입력 : todo_id, recommend_category
- todo_id에 해당하는 todo_id 의 Contents 를 바탕으로 sub todo를 추천합니다.
- 커스텀의 경우 사용자의 이전 기록들을 바탕으로 추천합니다.
- 추천할 때의 subtodo 는 약 1시간의 작업으로 openAI 의 api를 통해 추천합니다.
'''
 
todo_id = request.GET.get('todo_id')
try:
todo = Todo.objects.get_with_id(id=todo_id)
completion = client.chat.completions.create(
model = "gpt-4o-mini",
messages = [
{"role": "system",
"content": """너는 퍼스널 매니저야.
너가 하는 일은 이 사람이 할 이야기를 듣고 약 1시간 정도면 끝낼 수 있도록 작업을 나눠주는 식으로 진행할 거야.
아래는 너가 나눠줄 작업 형식이야.
{ id : 1, content: "3학년 2학기 운영체제 중간고사 준비", start_date="2024-09-01", end_date="2024-09-24"}
이런 형식으로 작성된 작업을 받았을 때 너는 이 작업을 어떻게 나눠줄 것인지를 알려주면 돼.
Output a JSON object structured like:
{id, content, start_date, end_date, category_id, order, is_completed, children : [
{content, date, todo(parent todo id)}, ... ,{content, date, todo(parent todo id)}]}
[조건]
- date 는 부모의 start_date를 따를 것
- 작업은 한 서브투두를 해결하는데 1시간 정도로 이루어지도록 제시할 것
- 언어는 주어진 todo content의 언어에 따를 것
"""},
{"role": "user", "content": f"id: {todo.id}, content: {todo.content}, start_date: {todo.start_date}, end_date: {todo.end_date}, category_id: {todo.category_id}, order: {todo.order}, is_completed: {todo.is_completed}"}
],
response_format={"type": "json_object"}
)

 

except Todo.DoesNotExist:
return Response({"error": "Todo not found"}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)

 

return Response(json.loads(completion.choices[0].message.content), status=status.HTTP_200_OK)
 

일단 여기서 맘에 안드는 건 deepcopy... 

patch 의 validation 이 잘 끝난다면.. 없어져도 되는 친구라 일단 내버려둬본다..