Loans 관련 로직 구현
이번 시간부터는 Loans 관련 로직을 구현해보겠습니다.
1. LoansForm 작성
먼저, books/forms.py
파일에 LoansForm
을 작성합니다.
# books/forms.py
class LoanForm(forms.ModelForm):
class Meta:
model = Loan
fields = [] # user와 book은 view에서 처리할 것이므로 비워둡니다.
def clean(self):
cleaned_data = super().clean() # 기존 clean 메서드를 실행합니다.
user = self.user # view에서 전달받은 user
book = self.book # view에서 전달받은 book
# 대출 가능 여부 검증
if not user.can_borrow():
raise forms.ValidationError('최대 대출 가능 권수(3권)를 초과했습니다.')
if user.has_overdue_books():
raise forms.ValidationError('연체된 도서가 있어 대출이 불가능합니다.')
if not book.is_available():
raise forms.ValidationError('현재 대출 가능한 도서가 없습니다.')
return cleaned_data # cleaned_data를 반환합니다.
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None) # user를 kwargs에서 추출합니다.
self.book = kwargs.pop('book', None) # book을 kwargs에서 추출합니다.
super().__init__(*args, **kwargs) # 기존 __init__ 메서드를 실행합니다.
# books/forms.py
class LoanForm(forms.ModelForm):
class Meta:
model = Loan
fields = [] # user와 book은 view에서 처리할 것이므로 비워둡니다.
def clean(self):
cleaned_data = super().clean() # 기존 clean 메서드를 실행합니다.
user = self.user # view에서 전달받은 user
book = self.book # view에서 전달받은 book
# 대출 가능 여부 검증
if not user.can_borrow():
raise forms.ValidationError('최대 대출 가능 권수(3권)를 초과했습니다.')
if user.has_overdue_books():
raise forms.ValidationError('연체된 도서가 있어 대출이 불가능합니다.')
if not book.is_available():
raise forms.ValidationError('현재 대출 가능한 도서가 없습니다.')
return cleaned_data # cleaned_data를 반환합니다.
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None) # user를 kwargs에서 추출합니다.
self.book = kwargs.pop('book', None) # book을 kwargs에서 추출합니다.
super().__init__(*args, **kwargs) # 기존 __init__ 메서드를 실행합니다.
2. LoansView 작성
다음으로, books/views.py
파일에 다음과 같은 내용을 추가합니다.
2.1 Loans 생성 (도서 대출)
# books/views.py
@login_required # 로그인 여부 확인
def loan_create(request, book_id):
"""도서 대출 처리"""
book = get_object_or_404(Book, pk=book_id)
if request.method == 'POST':
form = LoanForm(request.POST, user=request.user, book=book) # 폼 데이터 바인딩
if form.is_valid():
loan = form.save(commit=False) # 대출 정보 저장
loan.user = request.user
loan.book = book
loan.due_date = timezone.now() + timedelta(days=14) # 14일 대출
loan.save()
# 도서 수량 감소
book.decrease_quantity()
messages.success(request, f'{book.title} 도서가 대출되었습니다. 반납일은 {loan.due_date.date()}입니다.')
return redirect('books:loan-list') # 대출 목록 페이지로 이동
else:
form = LoanForm(user=request.user, book=book) # 폼 초기화
return render(request, 'books/loan_form.html', {
'form': form,
'book': book
}) # 대출 폼 렌더링
# books/views.py
@login_required # 로그인 여부 확인
def loan_create(request, book_id):
"""도서 대출 처리"""
book = get_object_or_404(Book, pk=book_id)
if request.method == 'POST':
form = LoanForm(request.POST, user=request.user, book=book) # 폼 데이터 바인딩
if form.is_valid():
loan = form.save(commit=False) # 대출 정보 저장
loan.user = request.user
loan.book = book
loan.due_date = timezone.now() + timedelta(days=14) # 14일 대출
loan.save()
# 도서 수량 감소
book.decrease_quantity()
messages.success(request, f'{book.title} 도서가 대출되었습니다. 반납일은 {loan.due_date.date()}입니다.')
return redirect('books:loan-list') # 대출 목록 페이지로 이동
else:
form = LoanForm(user=request.user, book=book) # 폼 초기화
return render(request, 'books/loan_form.html', {
'form': form,
'book': book
}) # 대출 폼 렌더링
2.2 Loans 목록 조회
@login_required
def loan_list(request):
"""대출 목록 조회"""
if request.user.is_librarian(): # 사서인 경우
loans = Loan.objects.all().order_by('-loan_date') # 모든 대출 목록 조회
else: # 일반 사용자인 경우
loans = Loan.objects.filter(user=request.user).order_by('-loan_date') # 사용자의 대출 목록 조회
return render(request, 'books/loan_list.html', {
'loans': loans
})
@login_required
def loan_list(request):
"""대출 목록 조회"""
if request.user.is_librarian(): # 사서인 경우
loans = Loan.objects.all().order_by('-loan_date') # 모든 대출 목록 조회
else: # 일반 사용자인 경우
loans = Loan.objects.filter(user=request.user).order_by('-loan_date') # 사용자의 대출 목록 조회
return render(request, 'books/loan_list.html', {
'loans': loans
})
2.3 Loans 반납
@login_required
def loan_return(request, loan_id):
"""도서 반납 처리"""
loan = get_object_or_404(Loan, pk=loan_id)
# 본인의 대출이거나 사서만 반납 가능
if request.user != loan.user and not request.user.is_librarian():
messages.error(request, '권한이 없습니다.')
return redirect('books:loan-list')
if request.method == 'POST':
if loan.status == 'ACTIVE':
loan.status = 'RETURNED'
loan.returned_date = timezone.now()
loan.save()
# 도서 수량 증가
loan.book.increase_quantity()
# 예약자 확인 및 처리
waiting_reservation = loan.book.reservation_set.filter(
status='WAITING'
).order_by('reserved_date').first()
if waiting_reservation:
waiting_reservation.status = 'AVAILABLE'
waiting_reservation.save()
messages.success(
request,
'도서가 반납되었습니다. 예약자가 있어 예약자에게 우선권이 부여됩니다.'
)
else:
messages.success(request, '도서가 반납되었습니다.')
return redirect('books:loan-list')
else:
messages.error(request, '잘못된 접근입니다.')
return redirect('books:loan-list')
@login_required
def loan_return(request, loan_id):
"""도서 반납 처리"""
loan = get_object_or_404(Loan, pk=loan_id)
# 본인의 대출이거나 사서만 반납 가능
if request.user != loan.user and not request.user.is_librarian():
messages.error(request, '권한이 없습니다.')
return redirect('books:loan-list')
if request.method == 'POST':
if loan.status == 'ACTIVE':
loan.status = 'RETURNED'
loan.returned_date = timezone.now()
loan.save()
# 도서 수량 증가
loan.book.increase_quantity()
# 예약자 확인 및 처리
waiting_reservation = loan.book.reservation_set.filter(
status='WAITING'
).order_by('reserved_date').first()
if waiting_reservation:
waiting_reservation.status = 'AVAILABLE'
waiting_reservation.save()
messages.success(
request,
'도서가 반납되었습니다. 예약자가 있어 예약자에게 우선권이 부여됩니다.'
)
else:
messages.success(request, '도서가 반납되었습니다.')
return redirect('books:loan-list')
else:
messages.error(request, '잘못된 접근입니다.')
return redirect('books:loan-list')
2.4 Loans 연체 목록
@librarian_required
def loan_overdue_list(request):
"""연체 도서 목록 (사서용)"""
overdue_loans = Loan.objects.filter(
status='ACTIVE', # 대출 중인 도서
due_date__lt=timezone.now() # 현재 시간보다 이전
).order_by('due_date') # 반납일 기준으로 정렬
return render(request, 'books/loan_list.html', {
'loans': overdue_loans,
'show_overdue': True
})
@librarian_required
def loan_overdue_list(request):
"""연체 도서 목록 (사서용)"""
overdue_loans = Loan.objects.filter(
status='ACTIVE', # 대출 중인 도서
due_date__lt=timezone.now() # 현재 시간보다 이전
).order_by('due_date') # 반납일 기준으로 정렬
return render(request, 'books/loan_list.html', {
'loans': overdue_loans,
'show_overdue': True
})