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