WeniVooks

검색

Django 베이스캠프

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
    })
7.6 books 앱 구현7.8 Reservations 관련 로직 구현