본문 바로가기

books 앱 구현

books 앱에서는 도서 CRUD 기능을 구현하고, BookForm을 통해 ISBN 유효성 검사와 수량 관리를 처리하며, 사서 권한 데코레이터로 접근을 제어합니다.

이번 시간에는 도서관 관리 시스템(Library Management System, LMS)의 books 앱을 구현해보겠습니다.

먼저, books 앱의 CRUD(Create, Read, Update, Delete) 기능을 구현해보겠습니다.

1. 도서 관련 뷰 구현

1.1 도서 목록 조회

def book_list(request):
    """도서 목록 및 검색"""
    query = request.GET.get('query', '') # 검색어
    if query:
        books = Book.objects.filter(
            models.Q(title__icontains=query) | # 제목 검색
            models.Q(author__icontains=query) | # 저자 검색
            models.Q(isbn__icontains=query) # ISBN 검색
        )
    else:
        books = Book.objects.all() # 전체 도서 목록
    return render(request, 'books/book_list.html', {'books': books, 'query': query}) # 도서 목록 템플릿 렌더링

1.2 도서 상세 조회

def book_detail(request, pk):
    """도서 상세 정보""" 
    book = get_object_or_404(Book, pk=pk) # 도서 조회
    return render(request, 'books/book_detail.html', {'book': book}) # 도서 상세 정보 템플릿 렌더링

1.3 도서 등록

@librarian_required # 사서 권한 데코레이터
def book_create(request):
    """도서 등록 (사서 전용)"""
    if request.method == 'POST': # POST 요청인 경우
        form = BookForm(request.POST) # 폼 데이터 바인딩
        if form.is_valid(): # 폼 데이터 유효성 검사
            book = form.save(commit=False) # 도서 정보 저장
            book.available_quantity = book.total_quantity # 대출 가능 수량 초기화
            book.save() # 도서 정보 저장
            messages.success(request, '도서가 등록되었습니다.') # 성공 메시지
            return redirect('books:book-detail', pk=book.pk) # 도서 상세 페이지로 이동
    else:
        form = BookForm() # 폼 초기화
    return render(request, 'books/book_form.html', {'form': form, 'title': '도서 등록'}) # 도서 등록 폼 렌더링

1.4 도서 수정

@librarian_required # 사서 권한 데코레이터
def book_update(request, pk): 
    """도서 수정 (사서 전용)"""
    book = get_object_or_404(Book, pk=pk) # 도서 조회
    if request.method == 'POST': # POST 요청인 경우
        form = BookForm(request.POST, instance=book) # 폼 데이터 바인딩
        if form.is_valid(): # 폼 데이터 유효성 검사
            book = form.save() # 도서 정보 저장
            messages.success(request, '도서가 수정되었습니다.') # 성공 메시지
            return redirect('books:book-detail', pk=book.pk) # 도서 상세 페이지로 이동
    else: 
        form = BookForm(instance=book) # 폼 초기화
    return render(request, 'books/book_form.html', {'form': form, 'title': '도서 수정'}) # 도서 수정 폼 렌더링

1.5 도서 삭제

@librarian_required # 사서 권한 데코레이터
def book_delete(request, pk):
    """도서 삭제 (사서 전용)"""
    book = get_object_or_404(Book, pk=pk) # 도서 조회
    if request.method == 'POST': # POST 요청인 경우
        book.delete() # 도서 삭제
        messages.success(request, '도서가 삭제되었습니다.')
        return redirect('books:book-list') # 도서 목록 페이지로 이동
    return render(request, 'books/book_confirm_delete.html', {'book': book}) # 도서 삭제 확인 페이지 렌더링

2.도서 폼 구현

# books/forms.py
from django import forms 
from .models import Book

class BookForm(forms.ModelForm):
    class Meta:
        model = Book
        fields = ['title', 'author', 'isbn', 'publisher', 'total_quantity']
        
        # 각 필드별 위젯 설정
        widgets = {
            'title': forms.TextInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '도서명을 입력하세요'
                }
            ),
            'author': forms.TextInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '저자명을 입력하세요'
                }
            ),
            'isbn': forms.TextInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': 'ISBN을 입력하세요'
                }
            ),
            'publisher': forms.TextInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '출판사명을 입력하세요'
                }
            ),
            'total_quantity': forms.NumberInput(
                attrs={
                    'class': 'form-control',
                    'min': '1',  # 최소값 설정
                }
            )
        }
        
        # 각 필드별 레이블 설정
        labels = {
            'title': '도서명',
            'author': '저자',
            'isbn': 'ISBN',
            'publisher': '출판사',
            'total_quantity': '총 수량'
        }
        
        # 각 필드별 도움말 설정
        help_texts = {
            'isbn': 'ISBN 13자리를 입력하세요',
            'total_quantity': '최소 1권 이상 입력하세요'
        }
    
    def clean_isbn(self):
        """ISBN 유효성 검사"""
        isbn = self.cleaned_data.get('isbn')
        if len(isbn) != 13:  # ISBN은 13자리여야 함
            raise forms.ValidationError('ISBN은 13자리여야 합니다.')
            
        # ISBN이 이미 존재하는지 확인 (수정 시에는 자기 자신은 제외)
        exists = Book.objects.filter(isbn=isbn)
        if self.instance:  # 수정 시
            exists = exists.exclude(pk=self.instance.pk)
        if exists.exists():
            raise forms.ValidationError('이미 등록된 ISBN입니다.')
            
        return isbn
    
    def clean_total_quantity(self):
        """수량 유효성 검사"""
        quantity = self.cleaned_data.get('total_quantity')
        if quantity < 1:  # 최소 1권
            raise forms.ValidationError('수량은 1권 이상이어야 합니다.')
        return quantity
    
    def save(self, commit=True):
        """저장 시 available_quantity도 함께 설정"""
        instance = super().save(commit=False)
        if not self.instance.pk:  # 새로운 도서 등록 시
            instance.available_quantity = instance.total_quantity
        elif instance.total_quantity < self.instance.available_quantity:  # 수정 시 총 수량을 줄이는 경우
            instance.available_quantity = instance.total_quantity
        if commit:
            instance.save()
        return instance
books 앱 구현 - django 베이스캠프 | 위니버시티