본문 바로가기

URL 라우터, 모델 설계 및 구현

이번 장에서는 accounts와 books 앱의 URL 라우터를 설정하고, User/Book/Loan/Reservation 모델을 ForeignKey 관계와 함께 구현합니다.

이전 시간에는 도서관 관리 시스템(Library Management System, LMS)의 요구사항 명세서를 작성하였습니다.

이번 시간에는, URL 라우터를 구현하고, 모델을 설계하고 구현해보겠습니다.

1. URL 라우터 구현

1.1 계정 관련 URL (accounts/urls.py)

  • 로그인/로그아웃
  • 회원가입
  • 프로필 페이지
# accounts/urls.py
from django.urls import path
from . import views

app_name = 'accounts' # 네임스페이스 설정

urlpatterns = [
    path('login/', views.login_view, name='login'),
    path('logout/', views.logout_view, name='logout'),
    path('register/', views.register_view, name='register'),
    path('profile/', views.profile_view, name='profile'),
]

1.2 도서 관련 URL (books/urls.py)

  • 도서 CRUD 작업
  • 대출/반납 관리
  • 예약 시스템
  • API 엔드포인트 (선택사항)
# books/urls.py  
from django.urls import path
from . import views

app_name = 'books'

urlpatterns = [
    # 도서 관련
    path('', views.book_list, name='book-list'),
    path('<int:pk>/', views.book_detail, name='book-detail'),
    path('create/', views.book_create, name='book-create'),
    path('<int:pk>/update/', views.book_update, name='book-update'),
    path('<int:pk>/delete/', views.book_delete, name='book-delete'),
    
    # 대출 관련
    path('loans/', views.loan_list, name='loan-list'),
    path('loans/user/', views.user_loan_list, name='user-loan-list'),
    path('loans/create/<int:book_id>/', views.loan_create, name='loan-create'),
    path('loans/return/<int:loan_id>/', views.loan_return, name='loan-return'),
    path('loans/overdue/', views.loan_overdue_list, name='loan-overdue'),
    
    # 예약 관련
    path('reservations/', views.reservation_list, name='reservation-list'),
    path('reservations/user/', views.user_reservation_list, name='user-reservation-list'),
    path('reservations/create/<int:book_id>/', views.reservation_create, name='reservation-create'),
    path('reservations/cancel/<int:reservation_id>/', views.reservation_cancel, name='reservation-cancel'),
    
    # API 엔드포인트 (선택 사항)
    path('search/', views.book_search, name='book-search'),
    path('status/<int:book_id>/', views.book_status, name='book-status'),
    path('loans/status/<int:loan_id>/', views.loan_status, name='loan-status'),
]

1.3 메인 페이지 URL (config/urls.py)

  • admin 페이지
  • accounts 앱 URL 포함
  • books 앱 URL 포함
# config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('books.urls')),  # 메인 페이지는 도서 목록
    path('accounts/', include('accounts.urls')),
    path('books/', include('books.urls')), 
]

각 패턴은 해당하는 뷰 함수와 연결되며, name 파라미터를 통해 템플릿에서 URL 역참조가 가능합니다. app_name을 지정하여 URL 패턴의 네임스페이스를 구분했습니다.

이제 각각의 view 함수를 구현한 후, 템플릿에서는 다음과 같이 URL을 참조할 수 있습니다.

<a href="{% url 'books:book-list' %}">도서 목록</a>
<a href="{% url 'accounts:login' %}">로그인</a>

2. 모델 설계 및 구현

2.1 사용자 모델 (accounts/models.py)

  • AbstractUser 상속으로 기본 인증 기능 활용
  • 사서/일반 사용자 역할 구분
  • 추가 정보(전화번호) 저장
# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
   """
   사용자 모델: AbstractUser를 상속받아 기본 인증 시스템 활용
   기본 필드: username, password, email, first_name, last_name, is_staff, is_active, date_joined
   """
   ROLE_CHOICES = [
       ('LIBRARIAN', '사서'),
       ('USER', '일반 사용자'),
   ]
   
   role = models.CharField(
       max_length=10, 
       choices=ROLE_CHOICES,
       default='USER',
       verbose_name='역할'
   )
   phone = models.CharField(
       max_length=11,
       blank=True,
       verbose_name='전화번호'
   )

   class Meta: # Meta 클래스를 통해 모델의 메타데이터(옵션) 설정
       verbose_name = '사용자' # verbose_name 필드는 단수형 이름
       verbose_name_plural = '사용자 목록' # verbose_name_plural 필드는 복수형 이름

2.2 도서, 대출, 예약 모델 (books/models.py)

2.2.1 도서 모델 (Book)

  • 기본적인 도서 정보 관리
  • 수량 관리 (전체/가용)
  • 생성/수정 시간 자동 기록
# books/models.py
from django.db import models
from django.conf import settings

class Book(models.Model):
   """
   도서 정보를 저장하는 모델
   """
   title = models.CharField(
       max_length=200, 
       verbose_name='도서명'
   )
   author = models.CharField(
       max_length=100, 
       verbose_name='저자'
   )
   isbn = models.CharField(
       max_length=13,
       unique=True,
       verbose_name='ISBN'
   )
   publisher = models.CharField(
       max_length=100, 
       verbose_name='출판사'
   )
   total_quantity = models.PositiveIntegerField( # PositiveIntegerField: 양수만 저장
       default=1,
       verbose_name='전체 수량'
   )
   available_quantity = models.PositiveIntegerField(
       default=1,
       verbose_name='대출 가능 수량'
   )
   created_at = models.DateTimeField(
       auto_now_add=True, # 생성 시간 자동 기록
       verbose_name='등록일'
   )
   updated_at = models.DateTimeField(
       auto_now=True, # 수정 시간 자동 기록
       verbose_name='수정일'
   )

   class Meta:
       verbose_name = '도서'
       verbose_name_plural = '도서 목록'
       ordering = ['-created_at'] # 기본 정렬 순서

   def __str__(self): # 객체를 문자열로 표현하는 메서드
       return self.title # 도서명으로 표현

   def is_available(self):
       """대출 가능 여부 확인"""
       return self.available_quantity > 0 # 대출 가능 수량이 0보다 크면 True 반환
       
   def decrease_quantity(self):
       """대출 시 수량 감소"""
       if self.is_available():
           self.available_quantity -= 1
           self.save()
           return True
       return False

   def increase_quantity(self):
       """반납 시 수량 증가"""
       if self.available_quantity < self.total_quantity:
           self.available_quantity += 1
           self.save()
           return True
       return False   

2.2.2 대출 모델 (Loan)

  • 대출 상태 관리 (대출중/연체/반납완료)
  • 대출일과 반납예정일 관리
  • 연체 여부 확인 메서드

# ... 위 코드 계속

class Loan(models.Model):
   """
   도서 대출 정보를 저장하는 모델
   """
   STATUS_CHOICES = [
       ('ACTIVE', '대출중'),
       ('OVERDUE', '연체'),
       ('RETURNED', '반납완료'),
   ]

   user = models.ForeignKey( # ForeignKey: 다른 모델과의 관계 설정
       settings.AUTH_USER_MODEL, # settings.py에 지정한 사용자 모델
       on_delete=models.CASCADE, # 연결된 사용자가 삭제되면 대출 정보도 삭제
       verbose_name='사용자' 
   )
   book = models.ForeignKey(
       Book,
       on_delete=models.CASCADE,
       verbose_name='도서'
   )
   status = models.CharField(
       max_length=10,
       choices=STATUS_CHOICES,
       default='ACTIVE',
       verbose_name='상태'
   )
   loan_date = models.DateTimeField(
       auto_now_add=True,
       verbose_name='대출일'
   )
   due_date = models.DateTimeField(
       verbose_name='반납예정일'
   )
   returned_date = models.DateTimeField(
       null=True,
       blank=True,
       verbose_name='반납일'
   )

   class Meta:
       verbose_name = '대출'
       verbose_name_plural = '대출 목록'
       ordering = ['-loan_date'] # 최신 대출 순으로 정렬

   def __str__(self):
       return f"{self.user.username} - {self.book.title}"

   def is_overdue(self):
       """연체 여부 확인"""
       from django.utils import timezone
       return self.status == 'ACTIVE' and timezone.now() > self.due_date # 대출중이고 반납예정일이 지났으면 True 반환

2.2.3 예약 모델 (Reservation)

  • 예약 상태 관리 (대기/가능/취소)
  • 예약 만료일 설정
  • 중복 예약 방지
# ... 위 코드 계속
class Reservation(models.Model):
   """
   도서 예약 정보를 저장하는 모델
   """
   STATUS_CHOICES = [
       ('WAITING', '대기중'),
       ('AVAILABLE', '대출가능'),
       ('CANCELLED', '취소됨'),
   ]

   user = models.ForeignKey(
       settings.AUTH_USER_MODEL,
       on_delete=models.CASCADE,
       verbose_name='사용자'
   )
   book = models.ForeignKey(
       Book,
       on_delete=models.CASCADE,
       verbose_name='도서'
   )
   status = models.CharField(
       max_length=10,
       choices=STATUS_CHOICES,
       default='WAITING',
       verbose_name='상태'
   )
   reserved_date = models.DateTimeField(
       auto_now_add=True,
       verbose_name='예약일'
   )
   expiry_date = models.DateTimeField(
       verbose_name='예약만료일'
   )

   class Meta:
       verbose_name = '예약'
       verbose_name_plural = '예약 목록'
       ordering = ['reserved_date'] # 예약일 순으로 정렬
       # 한 사용자가 같은 도서를 중복 예약할 수 없도록 제약
       unique_together = ['user', 'book'] # 사용자, 도서 같은 경우 중복 예약 방지

   def __str__(self):
       return f"{self.user.username} - {self.book.title}"

   def is_expired(self):
       """예약 만료 여부 확인"""
       from django.utils import timezone
       return timezone.now() > self.expiry_date # 예약 만료일이 지났으면 True 반환

3. 어드민 등록

3.1 사용자 모델 등록 (accounts/admin.py)

# accounts/admin.py
from django.contrib import admin
from .models import User

admin.site.register(User)

3.2 도서, 대출, 예약 모델 등록 (books/admin.py)


# books/admin.py

from django.contrib import admin
from .models import Book, Loan, Reservation

admin.site.register(Book)
admin.site.register(Loan)
admin.site.register(Reservation)