Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added apps/meal/__init__.py
Empty file.
38 changes: 30 additions & 8 deletions apps/meal/migrations/0001_initial.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

migration 파일은 날리면 안됩니다... 수정 사항이 있으면 migration 파일을 고치지 않고 새로 만들어야 합니다. 즉 0001과 0002가 있는 상황에서 수정 사항이 생기면, 그 수정사항은 0003 파일로 만들어야 합니다.

해결을 위해서 아래와 같이 작업해서 다시 커밋해주시면 되요!

  1. 로컬에서 마이그레이션 롤백하기 python manage.py migrate meal zero
  2. dev브랜치에서 0001과 0002 파일 받아서 로컬에서 작업하던 폴더에 붙여넣기
    1. 상태에서 다시 마이그레이션 만들기 (0003) -> python manage.py makemigrations
  3. 마이그레이션 진행 후 테스트 -> python manage.py migrate

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.23 on 2025-11-06 15:35
# Generated by Django 4.2.23 on 2025-11-22 17:30

import datetime
from django.db import migrations, models
Expand All @@ -24,8 +24,7 @@ class Migration(migrations.Migration):
('menu_name', models.TextField(verbose_name='메뉴명')),
('price', models.IntegerField(blank=True, null=True, verbose_name='가격')),
('date', models.DateField(verbose_name='날짜')),
('day', models.CharField(max_length=16, verbose_name='요일')),
('time', models.CharField(max_length=16, verbose_name='시간대(조식/중식/석식)')),
('meal_time', models.CharField(choices=[('BREAKFAST', 'BREAKFAST'), ('LUNCH', 'LUNCH'), ('DINNER', 'DINNER')], max_length=10, null=True, verbose_name='식사 시간대')),
],
options={
'ordering': ('-created_at',),
Expand All @@ -39,11 +38,10 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='생성 시간')),
('updated_at', models.DateTimeField(auto_now=True, db_index=True, verbose_name='수정 시간')),
('deleted_at', models.DateTimeField(db_index=True, default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.timezone.utc), verbose_name='삭제 시간')),
('restaurant_name', models.CharField(max_length=32, verbose_name='식당 이름')),
('course_name', models.CharField(max_length=32, verbose_name='코스 이름')),
('price', models.PositiveIntegerField(blank=True, null=True, verbose_name='가격')),
('date', models.DateField(verbose_name='날짜')),
('day', models.CharField(max_length=16, verbose_name='요일')),
('time', models.CharField(max_length=16, verbose_name='시간대(조식/중식/석식)')),
('meal_time', models.CharField(choices=[('BREAKFAST', 'BREAKFAST'), ('LUNCH', 'LUNCH'), ('DINNER', 'DINNER')], max_length=10, null=True, verbose_name='식사 시간대')),
],
options={
'ordering': ('-created_at',),
Expand All @@ -65,6 +63,20 @@ class Migration(migrations.Migration):
'abstract': False,
},
),
migrations.CreateModel(
name='Restaurant',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='생성 시간')),
('updated_at', models.DateTimeField(auto_now=True, db_index=True, verbose_name='수정 시간')),
('deleted_at', models.DateTimeField(db_index=True, default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.timezone.utc), verbose_name='삭제 시간')),
('restaurant_name', models.CharField(max_length=32, unique=True, verbose_name='식당 이름')),
],
options={
'ordering': ('-created_at',),
'abstract': False,
},
),
migrations.CreateModel(
name='MenuAllergy',
fields=[
Expand All @@ -73,12 +85,22 @@ class Migration(migrations.Migration):
('updated_at', models.DateTimeField(auto_now=True, db_index=True, verbose_name='수정 시간')),
('deleted_at', models.DateTimeField(db_index=True, default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.timezone.utc), verbose_name='삭제 시간')),
('allergen_code', models.PositiveIntegerField(blank=True, null=True, verbose_name='알러지 번호')),
('cafeteria_menu_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='allergy_set', to='meal.cafeteriamenu', verbose_name='카페테리아 메뉴')),
('menu_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='allergy_set', to='meal.menu', verbose_name='일반 메뉴')),
('cafeteria_menu_id', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='allergy_set', to='meal.cafeteriamenu', verbose_name='카페테리아 메뉴')),
('menu_id', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='allergy_set', to='meal.menu', verbose_name='일반 메뉴')),
],
options={
'ordering': ('-created_at',),
'abstract': False,
},
),
migrations.AddField(
model_name='course',
name='restaurant_id',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='courses', to='meal.restaurant', verbose_name='식당 이름'),
),
migrations.AddField(
model_name='cafeteriamenu',
name='restaurant_id',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cafeteria_menus', to='meal.restaurant', verbose_name='식당 이름'),
),
]

This file was deleted.

7 changes: 4 additions & 3 deletions apps/meal/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .restaurant import Restaurant
from .course import Course
from .cafeteria_menu import CafeteriaMenu, MealType
from .course import Course, MealType
from .menu_allergy import MenuAllergy
from .menu import Menu
from .menu import Menu
from .menu_allergy import MenuAllergy
7 changes: 7 additions & 0 deletions apps/meal/models/cafeteria_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ class MealType(str, Enum):
DINNER = "DINNER" # 석식

class CafeteriaMenu(MetaDataModel):
restaurant_id = models.ForeignKey(
to="meal.Restaurant",
on_delete=models.CASCADE,
related_name="cafeteria_menus",
verbose_name="식당 이름",
db_index=True,
)
menu_name = models.TextField(
verbose_name="메뉴명",
)
Expand Down
18 changes: 10 additions & 8 deletions apps/meal/models/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ class MealType(str, Enum):
LUNCH = "LUNCH" # 중식
DINNER = "DINNER" # 석식

class Course(MetaDataModel):
class MealType(models.IntegerChoices):
BREAKFAST = 0, "조식"
LUNCH = 1, "중식"
DINNER = 2, "석식"

restaurant_name = models.CharField(
class Course(MetaDataModel):
restaurant_id = models.ForeignKey(
to="meal.Restaurant",
on_delete=models.CASCADE,
related_name="courses",
verbose_name="식당 이름",
max_length=32,
db_index=True,
)
course_name = models.CharField(
verbose_name="코스 이름",
max_length=32,
)
price = models.PositiveIntegerField(
verbose_name="가격",
blank=True,
Expand Down
12 changes: 12 additions & 0 deletions apps/meal/models/restaurant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.db import models
from ara.db.models import MetaDataModel

class Restaurant(MetaDataModel):
restaurant_name = models.CharField(
verbose_name="식당 이름",
max_length=32,
unique=True,
)

def __str__(self):
return self.restaurant_name
Empty file.
38 changes: 38 additions & 0 deletions apps/meal/serializers/meal_serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from rest_framework import serializers
from ..models import Course, Menu, CafeteriaMenu

class BaseMenuSerializer(serializers.ModelSerializer):
"""알러지 코드 추출 및 경고 플래그 표시 여부 확인"""
allergy_codes = serializers.SerializerMethodField()
has_user_allergy = serializers.SerializerMethodField()

def get_allergy_codes(self, obj):
return list(obj.allergy_set.values_list('allergen_code', flat=True))

def get_has_user_allergy(self, obj):
user_allergies = self.context.get('user_allergies', [])
if not user_allergies:
return False
menu_allergies = set(obj.allergy_set.values_list('allergen_code', flat=True))
return bool(menu_allergies.intersection(set(user_allergies)))

class MenuSerializer(BaseMenuSerializer):
class Meta:
model = Menu
fields = ('menu_name', 'allergy_codes', 'has_user_allergy')

class CafeteriaMenuSerializer(BaseMenuSerializer):
class Meta:
model = CafeteriaMenu
fields = ('menu_name', 'price', 'allergy_codes', 'has_user_allergy')

class CourseSerializer(serializers.ModelSerializer):
menus = MenuSerializer(source='filtered_menus', many=True, read_only=True)

class Meta:
model = Course
fields = ('course_name', 'price', 'menus')

class DailyMealResponseSerializer(serializers.Serializer):
courses = CourseSerializer(many=True)
cafeteria_menus = CafeteriaMenuSerializer(many=True)
6 changes: 6 additions & 0 deletions apps/meal/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.urls import path, include
from .views.router import router

urlpatterns = [
path("", include(router.urls)),
]
Empty file added apps/meal/views/__init__.py
Empty file.
54 changes: 54 additions & 0 deletions apps/meal/views/meal_viewset.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기
try:
query_date = date_type(int(date_str[:4]), int(date_str[4:6]), int(date_str[6:]))
except (ValueError, TypeError):
return Response({'error': 'Invalid date'}, status=status.HTTP_400_BAD_REQUEST)

이부분에서 잘못된 형식의 데이터에 대해서 에러 메서지를 조금 더 자세히 명시해 주세요

ex) 날짜가 틀린 경우 어떤 형식으로 와야 하는지

+) 테스트 케이스도 만들어 주세요!!

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from rest_framework import viewsets, status
from rest_framework.response import Response
from django.db.models import Prefetch
from datetime import date as date_type
from ..models import Course, Menu, CafeteriaMenu
from ..serializers.meal_serializers import CourseSerializer, CafeteriaMenuSerializer

class MealViewSet(viewsets.ViewSet):

def list(self, request):
date_str = request.query_params.get('date')
restaurant_name = request.query_params.get('restaurant_name')
meal_time = request.query_params.get('meal_time')

if not all([date_str, restaurant_name, meal_time]):
return Response({'error': 'Missing parameters'}, status=status.HTTP_400_BAD_REQUEST)
try:
query_date = date_type(int(date_str[:4]), int(date_str[4:6]), int(date_str[6:]))
except (ValueError, TypeError):
return Response({'error': 'Invalid date'}, status=status.HTTP_400_BAD_REQUEST)

"""알러지 필터 context 생성"""
raw_codes = request.query_params.get('allergy_codes', '')
user_allergies = [int(c.strip()) for c in raw_codes.split(',') if c.strip()]
context = {'user_allergies': user_allergies}

"""일반 코스 메뉴 조회"""
all_menus_qs = Menu.objects.all().prefetch_related('allergy_set')

course_queryset = Course.objects.filter(
restaurant_id__restaurant_name=restaurant_name,
date=query_date,
meal_time=meal_time
).prefetch_related(
Prefetch('menu_set', queryset=all_menus_qs, to_attr='filtered_menus')
)

course_serializer = CourseSerializer(course_queryset, many=True, context=context)

"""카페테리아 메뉴 조회"""
cafeteria_queryset = CafeteriaMenu.objects.filter(
restaurant_id__restaurant_name=restaurant_name,
date=query_date,
meal_time=meal_time,
).prefetch_related('allergy_set')

cafeteria_serializer = CafeteriaMenuSerializer(cafeteria_queryset, many=True, context=context)


return Response({
'restaurant': restaurant_name,
'courses': course_serializer.data,
'cafeteria_menus': cafeteria_serializer.data,
}, status=status.HTTP_200_OK)
10 changes: 10 additions & 0 deletions apps/meal/views/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from rest_framework import routers
from apps.meal.views import meal_viewset

router = routers.DefaultRouter()

router.register(
prefix=r"",
viewset=meal_viewset.MealViewSet,
basename="meal",
)
Loading