URL 라우터, 모델 설계 및 구현
이전 시간에는 도서관 관리 시스템(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'),
]
# 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'),
]
# 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')),
]
# 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>
<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 필드는 복수형 이름
# 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
# 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 반환
# ... 위 코드 계속
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 반환
# ... 위 코드 계속
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)
# 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)
# 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)