▶️ DRF 

[▶️Back-end] 사용자 입력의 Prompt를 다듬는 기능 구현 1차 

 

▲ 본 게시물은 serializer, url, view 코드를 구현 하는 내용을 담았어요!

 

★ 변경 사항 ★

1. prompt 앱을 생성해서 새로 시작했어요.

2. dev/0.0.3은 DRF이기 때문에 새로 생성한 거예요! (이전은 Django였어요.)

3. API 명세서가 수정될 예정이에요.


▶️ 로직 구현 시작

🗣️ 앱 생성

# bash
python manage.py startapp prompts

🗣️ core/settings.py

1.  DRF 기본 설정

더보기
# DRF 설정
REST_FRAMEWORK = {
    # **API 설정**

    # 1. 기본 인증 방식 (Authentication Classes)
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',  # Django 세션 인증 (웹 브라우저)
        'rest_framework.authentication.TokenAuthentication',    # 토큰 인증 (API 클라이언트)
        # 필요에 따라 JWT 인증 추가 가능:
        # 'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],

    # 2. 기본 권한 설정 (Permission Classes)
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 읽기 권한은 누구나, 쓰기 권한은 인증된 사용자만
        # 'rest_framework.permissions.IsAuthenticated',         # 인증된 사용자만 접근 가능
        # 'rest_framework.permissions.AllowAny',                # 누구나 접근 가능 (개발/테스트 환경)
    ],

    # **페이지네이션 설정**
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # 페이지 번호 기반 페이지네이션
    'PAGE_SIZE': 10,        # 한 페이지당 항목 수

    # **기타 설정**

    # 3. 예외 처리 (Exception Handling)
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', # DRF 기본 예외 처리 사용

    # 4. Content Negotiation 설정 (콘텐츠 협상)
    'DEFAULT_CONTENT_TYPE': 'application/json', # 기본 콘텐츠 타입

    # 5. Renderer 설정 (응답 형식)
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',           # JSON 형식
        'rest_framework.renderers.BrowsableAPIRenderer',    # browsable API (웹 브라우저)
    ],

    # 6. 파서 설정 (요청 형식)
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ],

    # 7. API 버전 관리
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning', # Accept 헤더 기반 버전 관리
    'DEFAULT_VERSION': 'v1',  # 기본 API 버전

    # 8. 스키마 생성 설정
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
1차 게시물에서 DRF의 기본 설정들을 추가해주었어야 했는데, 까먹고 안 했더라고요.
그래서 이번에 추가해 주었어요.
출처 : https://www.django-rest-framework.org/api-guide/settings/

2. prompt 앱 등록

# Application definition
INSTALLED_APPS = [
    ...
    
    # Create app list
    ...
    "prompts", # 유저의 프롬프트를 좀 더 좋게 보완해주는 앱
    
    ...
]
새로 추가해 준 앱도 주석과 함께 등록해 주었어요.

🗣️ core/urls.py

urlpatterns = [
    ...
    path("prompts/", include("prompts.urls")),
]
urls.py에 prompts 앱의 urls을 포함하도록 설정해 주었어요.

🗣️ prompts/urls.py

from django.urls import path
from .views import GenerateMesh

app_name="prompts"

urlpatterns = [
    path("", GenerateMesh.as_view(), name="generate_mesh"),
]

🗣️ prompts/serializers.py

from rest_framework import serializers

class GenerateMeshRequestSerializer(serializers.Serializer):
    prompt = serializers.CharField(
        help_text="생성할 3D 모델에 대한 프롬프트 (예: '귀여운 강아지')."
    )
    art_style = serializers.CharField(
        default="realistic",
        required=False,
        help_text="모델의 스타일 (예: 'realistic', 'cartoon'). 기본값은 'realistic'입니다."
    )

    def validate_art_style(self, value):
        """art_style 유효성 검사."""
        allowed_styles = ["realistic", " artoon", "abstract", "horror", "industrial aesthetic", "game figure"]  # 허용되는 스타일 목록
        if value not in allowed_styles:
            raise serializers.ValidationError(
                f"잘못된 art_style입니다. 다음 중 하나를 선택하세요: {', '.join(allowed_styles)}"
            )
        return value

🗣️ prompts/views.py

프롬프트 보강해 주는 로직

더보기
import logging
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from .models import MeshPromptModel

# utils
from workspace.meshy_utils import call_meshy_api  # Meshy API 호출 함수
from .serializers import GenerateMeshRequestSerializer  # Serializer 임포트

# 로깅 설정
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")

class GenerateMesh(APIView):
    """Mesh 생성 요청 & job_id 반환"""
    permission_classes = [IsAuthenticated]  # 로그인된 사용자만 접근 가능

    def get(self, request):
        """최근 생성된 Mesh 모델 목록 조회"""
        meshes = MeshPromptModel.objects.filter(user=request.user).order_by("-created_at")[:5]  # 최근 5개 조회
        results = [{"job_id": mesh.job_id, "status": mesh.status, "prompt": mesh.create_prompt, "art_style": mesh.art_style} for mesh in meshes]

        return Response({"recent_meshes": results}, status=status.HTTP_200_OK)
    
    def post(self, request):
        serializer = GenerateMeshRequestSerializer(data=request.data)
        if serializer.is_valid():
            prompt = serializer.validated_data['prompt']
            art_style = serializer.validated_data.get('art_style', 'realistic')

            # 프롬프트 보강: 중복 확인 후 "4K, highly detailed" 추가
            if "4K" not in prompt and "highly detailed" not in prompt:
                enhanced_prompt = prompt + ", 4K, highly detailed"
            else:
                enhanced_prompt = prompt  # 이미 포함된 경우 그대로 사용

            response_data = call_meshy_api("/openapi/v2/text-to-3d", "POST", {
                "mode": "preview", "prompt": enhanced_prompt, "art_style": art_style
            })

            if not response_data:
                return Response({"error": "Meshy API 응답 없음"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

            job_id = response_data.get("result")
            if not job_id:
                return Response({"error": "Meshy API에서 job_id를 받지 못했습니다."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

            MeshPromptModel.objects.create(
                user=request.user,
                job_id=job_id,
                status="processing",
                create_prompt=enhanced_prompt,  # 보강된 프롬프트 정보 저장
                art_style=art_style
            )

            return Response({"job_id": job_id, "message": "Mesh 생성 시작!"}, status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

서버

이렇게
"prompt" : "A cute baby dog, 4K, highly detailed"로 보완되도록 DRF로는 구현했어요.
하지만 LLM을 활용해서 이 작업을 수행해야 하기 때문에, 이제는 LLM 공부로 넘어갑시다. 😭

🐾Recent posts