문제 요약: Nginx를 통한 보안 강화 작업과 Django 서버에 대한 악성 요청 차단을 위해 Fail2Ban 및 Nginx 설정을 조정하는 과정에서 오류 발생.
오류 메시지:
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 5
[nginx-badbots]
enabled = true
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 3
[nginx-404]
enabled = true
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 10
문제 요약: 출석 데이터를 등록하는 과정에서 같은 날짜의 데이터가 중복되어 저장되지 않도록 예외 처리 로직을 추가
오류 메시지:출석 데이터가 이미 저장되어 있습니다.
if Attendance.objects.filter(date=date).exists():
raise serializers.ValidationError(
{"detail": f"{date}의 출석 데이터가 저장되어있습니다."}
)
if (error.response && error.response.data && error.response.data.detail) {
Modal.error({
title: '출석 데이터 중복',
content: error.response.data.detail,
});
}
raise serializers.ValidationError:
if Attendance.objects.filter(date=date).exists():
raise serializers.ValidationError(
{"detail": f"{date}의 출석 데이터가 저장되어있습니다."}
)
{"detail": "해당 날짜의 출석 데이터가 이미 저장되어 있습니다."}
* 메세지 임의 지정 가능
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
{
"date": ["이 필드는 필수 항목입니다."],
"attendance": ["출석 데이터는 필수입니다."]
}
* 자동 생성된 메세지
즉시 중단 (예외 발생)
if Attendance.objects.filter(date=date).exists():
raise serializers.ValidationError(
{"detail": f"{date}의 출석 데이터가 저장되어있습니다."}
)
# 이 이후의 코드는 실행되지 않는다.
끝까지 실행 후 반환
if Attendance.objects.filter(date=date).exists():
return Response({"error": f"{date}의 출석 데이터가 이미 저장되어있습니다."}, status=status.HTTP_400_BAD_REQUEST)
# 이후 코드도 실행될 수 있음
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
날짜 포맷 문제: React에서 new Date().toISOString().split('T')[0] 방식으로 날짜를 전송했으나, Modal에서 new Date().toLocaleDateString()으로 출력하여 날짜 차이가 발생함.
날짜 처리 통일
API와 Modal 아래와 같이 코드 수정
// API
const getFormattedDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const handleSubmit = async () => {
const attendanceData = students.map(student => ({
id : student.id,
attendance: checkedStudents.includes(student.id)
}));
const currentDate = getFormattedDate(new Date());
try {
const response = await postAttendanceData(currentDate, attendanceData);
console.log('서버 응답:', response);
// Modal
<AttendanceModal
isOpen={isModalOpen}
onClose={closeModal}
students={students}
checkedStudents={checkedStudents}
handleCheck={handleCheck}
handleSubmit={handleSubmit}
getFormattedDate={getFormattedDate(new Date())}
>
<p className="modal-subtitle">등록일: <b>{getFormattedDate}</b></p>
요청 데이터: 모든 필드를 포함하여 전송
예시:
기존 리소스:
{
"name": "김민수",
"grade": "1",
"gender": "남"
}
PUT 요청 시:
{
"name": "김민수",
"grade": "2",
"gender": "남"
}
결과:
name: “김민수” (유지)
grade: “2” (업데이트됨)
gender: “남” (유지)
요청 데이터: 수정하려는 필드만 포함
예시:
기존 리소스:
{
"name": "김민수",
"grade": "1",
"gender": "남"
}
PATCH 요청 시:
{
"grade": "2"
}
결과:
name: “김민수” (유지)
grade: “2” (업데이트됨)
gender: “남” (유지)
다수의 출석 데이터를 한번에 업데이트
@action(detail=False, methods=["patch"], url_path="bulk-update")
def bulk_update(self, request, *args, **kwargs):
serializer = BulkAttendanceSerializer(data=request.data)
if serializer.is_valid():
updated_records = serializer.update(None, serializer.validated_data)
return Response(
self.get_serializer(updated_records, many=True).data,
status=status.HTTP_200_OK,
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action 데코레이터
bulk_update는 @action 데코레이터로 정의되었으며, 이는 DRF에서 ViewSet에 맞춤형 동작을 추가하는 방법. methods=["patch"]를 통해 PATCH 요청을 처리.
URL은 /api/attendance/attendance-records/bulk-update/ 형태로 접근.
Serializer 데이터 검증:
BulkAttendanceSerializer로 요청 데이터를 검증.
요청 데이터 구조
{
"date": "2024-10-24",
"attendance": [
{"id": 54, "attendance": false},
{"id": 55, "attendance": false}
]
}
데이터 검증 및 저장
응답 생성
def update(self, instance, validated_data):
date = validated_data.get("date")
attendance_list = validated_data.get("attendance")
updated_records = []
for attendance_data in attendance_list:
member_id = attendance_data.get("id")
attendance_status = attendance_data.get("attendance")
attendance_record = Attendance.objects.filter(
name_id=member_id, date=date
).first()
if attendance_record:
attendance_record.attendance = attendance_status
attendance_record.save()
updated_records.append(attendance_record)
return updated_records
입력 데이터 분리
출석 데이터 업데이트
업데이트된 출석 기록 저장
전반적인 흐름