
Django의 model_to_dict는 기본적으로 모델 객체를 딕셔너리로 변환하는 데 유용하지만, 외래키(ForeignKey)나 일대일 관계(OneToOneField)와 같은 복잡한 필드 구조를 직렬화하는 데는 한계가 있다. 이 경우, Django REST Framework(DRF)의 Serializer를 사용한다.
class WeeklyListView(ListAPIView):
def get(self, request, *args, **kwargs):
year = self.request.query_params.get("year", None)
if year:
# 선택한 연도의 출석 데이터 필터링
attendance_dates = (
Attendance.objects.filter(date__year=year)
.values_list("date", flat=True)
.distinct()
)
else:
attendance_dates = Attendance.objects.values_list(
"date", flat=True
).distinct()
sorted_dates = sorted(attendance_dates)
return Response(sorted_dates)
이 코드는 Attendance model을 사용하여 객체를 가져오지만 values_list()`로
날짜 데이터의 원시값만 추출하고 이를 정렬하여 반환한다.
values_list() : 특정 필드만 가져오기 위해 사용하며, flat=True를 설정하면 단일 필드의 값만 리스트 형태로 반환한다.distinct() : 중복된 데이터를 제거한다.['2023-01-01', '2023-01-02']
class GroupAttendanceViewSet(ModelViewSet):
queryset = Attendance.objects.all()
serializer_class = GroupAttendanceSerializer
permission_classes = [IsAuthenticated]
authentication_classes = [JWTCookieAuthentication]
@action(detail=False, methods=["get"], url_path="attendance-by-week")
def attendance_by_week(self, request):
# 쿼리 파라미터에서 날짜 가져오기
week = request.query_params.get("week")
if not week:
return Response({"error": "Week parameter is required"}, status=400)
try:
# 요청된 날짜를 필터링
attendance_data = (
Attendance.objects.filter(date=week) # 요청된 날짜 필터링
.values("name__grade") # 학년 기준 그룹화
.annotate(
attendance_count=Count("id", filter=Q(attendance=True)), # 출석 수
absent_count=Count("id", filter=Q(attendance=False)), # 결석 수
)
.order_by("name__grade") # 학년 기준 정렬
)
return Response(attendance_data) # 결과 반환
serializer_class옵션은 ModelViewSet의 기본 CRUD 동작 및 기본 조회 동작에서 사용된다.
attendance_by_week와 같은 @action 데코레이터로 정의된 커스텀 액션은 기본 CRUD 동작과 별도로 정의되므로 해당메서드에서는 serializer_class가 자동으로 사용되지않는다.
attendance_by_week 메서드에서 반환하는 데이터는 집계된 결과로, Attendance 모델의 인스턴스가 아니기때문에 serializer_class가 사용되지않는다.
[
{"name__grade": "1", "attendance_count": 10, "absent_count": 5},
{"name__grade": "2", "attendance_count": 8, "absent_count": 3},
]
react-apexcharts를 생성할 serise와 option 함수를 생성한다.
const series = getGraphSeries(weeklyAttendanceData);
const lineGraphOptions = getLineGraphOptions();
const groupGrades = groupAttendanceData.map((item) => item.name__grade);
const groupSeries = getGroupGraphSeries(groupAttendanceData);
const barGraphOptions = getBarGraphOptionsForGroups(groupGrades);
series : weeklyAttendanceData를 기반으로 라인 그래프에 사용할 시리즈 데이터를 생성한다.
lineGraphOptions : 라인 그래프의 옵션을 생성한다.
groupGrades : groupAttendanceData에서 각 반(grade)의 이름(name__grade)을 추출하여 배열로 만든다.
groupSeries : groupAttendanceData를 기반으로 막대 그래프를 생성한다.
barGraphOptions : 막대 그래프의 옵션을 생성한다.
weeklyAttendanceData, groupAttendanceData는 두 함수를 통해 생성
// 주간 출석 데이터 가져오기
const fetchData = async () => {
setLoading(true);
try {
const data = await fetchWeeklyAttendanceData(selectedYear);
setWeeklyAttendanceData(data);
} catch (error) {
console.error("Error fetching weekly attendance data:", error);
message.error("출석 데이터를 가져오는 데 실패했습니다.");
} finally {
setLoading(false);
}
};
// 반별 출석 데이터 가져오기
const fetchGroupData = async () => {
// 데이터 호출 코드
};
serise와 option 함수는 graphUtils.js를 생성하여 분리하여 관리한다.
/**
* 그래프 데이터를 생성하는 함수
* @param {Array} attendanceData - 출석 데이터 배열
* @returns {Array} series - 그래프 시리즈 데이터
*/
export const getGraphSeries = (attendanceData) => [
{
name: "출석",
data: attendanceData
.filter((item) => item.type === "출석")
.map((item) => ({
x: new Date(item.date).getTime(), // 날짜를 타임스탬프로 변환
y: item.value,
})),
},
{
name: "결석",
data: attendanceData
.filter((item) => item.type === "결석")
.map((item) => ({
x: new Date(item.date).getTime(), // 날짜를 타임스탬프로 변환
y: item.value,
})),
},
];
export const getLineGraphOptions => [
];
export const getGroupGraphSeries => [
];
export const getBarGraphOptionsForGroups => [
];
각 태그에서 serise와 option을 통해 그래프를 생성한다.
<Chart options={lineGraphOptions} series={series} type="line" height={350} />
<Chart options={barGraphOptions} series={groupSeries} type="bar" height={350} />