템플릿 구현
이전 시간에는 도서관 관리 시스템(Library Management System, LMS)의 URL 라우터 및 모델을 구현했습니다.
이번 시간에는 요구사항에 맞게 템플릿을 구현해보겠습니다.
1. 핵심 기능별 필요 템플릿
1.1 공통 템플릿
templates/
├── base.html # 기본 레이아웃
└── navbar.html # 공통 네비게이션
templates/
├── base.html # 기본 레이아웃
└── navbar.html # 공통 네비게이션
1.2 사용자 관리 템플릿
accounts/templates/accounts/
├── login.html # 로그인
├── register.html # 회원가입
└── profile.html # 프로필(대출/예약 현황)
accounts/templates/accounts/
├── login.html # 로그인
├── register.html # 회원가입
└── profile.html # 프로필(대출/예약 현황)
1.3 도서 관리 템플릿
books/templates/books/
├── book_list.html # 도서 목록
├── book_detail.html # 도서 상세
├── book_confirm_delete.html # 도서 삭제 확인
├── book_form.html # 도서 등록/수정 (사서용)
├── loan_list.html # 대출 현황
├── loan_form.html # 대출 신청
├── reservation_form.html # 도서 예약
└── reservation_list.html # 예약 현황
books/templates/books/
├── book_list.html # 도서 목록
├── book_detail.html # 도서 상세
├── book_confirm_delete.html # 도서 삭제 확인
├── book_form.html # 도서 등록/수정 (사서용)
├── loan_list.html # 대출 현황
├── loan_form.html # 대출 신청
├── reservation_form.html # 도서 예약
└── reservation_list.html # 예약 현황
2. 기본 템플릿 구조 예시
2.1 base.html
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>도서관 관리 시스템</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
{% include 'navbar.html' %}
<div class="container mt-4">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}
{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>도서관 관리 시스템</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
{% include 'navbar.html' %}
<div class="container mt-4">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}
{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
2.1.1 base.html 핵심 요소
{% block content %}
: 다른 템플릿들이 내용을 채워넣을 영역 정의{% include 'navbar.html' %}
: 네비게이션 바 포함{% if messages %}
: Django messages framework로 전달된 알림 표시- Bootstrap 5 CSS/JS 포함
2.2 navbar.html
<!-- templates/navbar.html -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">도서관</a>
<div class="navbar-nav">
{% if user.is_authenticated %}
{% if user.role == 'LIBRARIAN' %}
<a class="nav-link" href="{% url 'books:book-create' %}">도서등록</a>
<a class="nav-link" href="{% url 'books:loan-list' %}">대출관리</a>
{% endif %}
<a class="nav-link" href="{% url 'accounts:profile' %}">내정보</a>
<form class="d-inline" method="post" action="{% url 'accounts:logout' %}">
{% csrf_token %}
<button class="btn btn-link nav-link">로그아웃</button>
</form>
{% else %}
<a class="nav-link" href="{% url 'accounts:login' %}">로그인</a>
<a class="nav-link" href="{% url 'accounts:register' %}">회원가입</a>
{% endif %}
</div>
</div>
</nav>
<!-- templates/navbar.html -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">도서관</a>
<div class="navbar-nav">
{% if user.is_authenticated %}
{% if user.role == 'LIBRARIAN' %}
<a class="nav-link" href="{% url 'books:book-create' %}">도서등록</a>
<a class="nav-link" href="{% url 'books:loan-list' %}">대출관리</a>
{% endif %}
<a class="nav-link" href="{% url 'accounts:profile' %}">내정보</a>
<form class="d-inline" method="post" action="{% url 'accounts:logout' %}">
{% csrf_token %}
<button class="btn btn-link nav-link">로그아웃</button>
</form>
{% else %}
<a class="nav-link" href="{% url 'accounts:login' %}">로그인</a>
<a class="nav-link" href="{% url 'accounts:register' %}">회원가입</a>
{% endif %}
</div>
</div>
</nav>
2.2.1 navbar.html 핵심 기능
- 사용자 인증 상태에 따른 메뉴 분기(
{% if user.is_authenticated %}
) - 사서 권한 확인 및 관리자 메뉴 표시(
{% if user.role == 'LIBRARIAN' %}
) - 로그아웃은 POST 방식으로 처리(CSRF 보호)
3. 도서 관리 템플릿 예시
3.1 book_list.html
<!-- books/templates/books/book_list.html -->
{% extends 'base.html' %}
{% block content %}
<h3>도서 목록</h3>
<table class="table">
<thead>
<tr>
<th>도서명</th>
<th>저자</th>
<th>ISBN</th>
<th>출판사</th>
<th>잔여/전체</th>
<th>액션</th>
</tr>
</thead>
<tbody>
{% for book in books %}
<tr>
<td><a href="{% url 'books:book-detail' book.pk %}">{{ book.title }}</a></td>
<td>{{ book.author }}</td>
<td>{{ book.isbn }}</td>
<td>{{ book.publisher }}</td>
<td>{{ book.available_quantity }}/{{ book.total_quantity }}</td>
<td>
{% if user.is_authenticated %}
{% if book.available_quantity > 0 %}
<form method="post" action="{% url 'books:loan-create' book.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-primary">대출</button>
</form>
{% else %}
<form method="post" action="{% url 'books:reservation-create' book.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-warning">예약</button>
</form>
{% endif %}
{% if user.role == 'LIBRARIAN' %}
<a href="{% url 'books:book-update' book.pk %}" class="btn btn-sm btn-secondary">수정</a>
<a href="{% url 'books:book-delete' book.pk %}" class="btn btn-sm btn-danger">삭제</a>
{% endif %}
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">도서가 없습니다.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
<!-- books/templates/books/book_list.html -->
{% extends 'base.html' %}
{% block content %}
<h3>도서 목록</h3>
<table class="table">
<thead>
<tr>
<th>도서명</th>
<th>저자</th>
<th>ISBN</th>
<th>출판사</th>
<th>잔여/전체</th>
<th>액션</th>
</tr>
</thead>
<tbody>
{% for book in books %}
<tr>
<td><a href="{% url 'books:book-detail' book.pk %}">{{ book.title }}</a></td>
<td>{{ book.author }}</td>
<td>{{ book.isbn }}</td>
<td>{{ book.publisher }}</td>
<td>{{ book.available_quantity }}/{{ book.total_quantity }}</td>
<td>
{% if user.is_authenticated %}
{% if book.available_quantity > 0 %}
<form method="post" action="{% url 'books:loan-create' book.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-primary">대출</button>
</form>
{% else %}
<form method="post" action="{% url 'books:reservation-create' book.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-warning">예약</button>
</form>
{% endif %}
{% if user.role == 'LIBRARIAN' %}
<a href="{% url 'books:book-update' book.pk %}" class="btn btn-sm btn-secondary">수정</a>
<a href="{% url 'books:book-delete' book.pk %}" class="btn btn-sm btn-danger">삭제</a>
{% endif %}
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">도서가 없습니다.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
3.1.1 book_list.html 핵심 기능
- 도서 목록을 테이블로 표시
- 각 도서의 상세 페이지 링크(
{% url 'books:book-detail' book.pk %}
) - 대출 가능 여부에 따른 버튼 분기:
- 재고 있음: 대출 버튼
- 재고 없음: 예약 버튼
- 사서 권한자만 수정/삭제 버튼 표시
3.2 book_detail.html
<!-- books/templates/books/book_detail.html -->
{% extends 'base.html' %}
{% block content %}
<div class="card">
<div class="card-body">
<h3>{{ book.title }}</h3>
<p>저자: {{ book.author }}</p>
<p>ISBN: {{ book.isbn }}</p>
<p>출판사: {{ book.publisher }}</p>
<p>잔여 수량: {{ book.available_quantity }}/{{ book.total_quantity }}</p>
{% if user.is_authenticated and book.available_quantity > 0 %}
<form method="post" action="{% url 'books:loan-create' book.id %}">
{% csrf_token %}
<button class="btn btn-primary">대출신청</button>
</form>
{% endif %}
</div>
</div>
{% endblock %}
<!-- books/templates/books/book_detail.html -->
{% extends 'base.html' %}
{% block content %}
<div class="card">
<div class="card-body">
<h3>{{ book.title }}</h3>
<p>저자: {{ book.author }}</p>
<p>ISBN: {{ book.isbn }}</p>
<p>출판사: {{ book.publisher }}</p>
<p>잔여 수량: {{ book.available_quantity }}/{{ book.total_quantity }}</p>
{% if user.is_authenticated and book.available_quantity > 0 %}
<form method="post" action="{% url 'books:loan-create' book.id %}">
{% csrf_token %}
<button class="btn btn-primary">대출신청</button>
</form>
{% endif %}
</div>
</div>
{% endblock %}
3.2.1 book_detail.html 핵심 기능
- 도서 정보(제목, 저자, ISBN 등) 표시
- 재고 상태 표시
- 인증된 사용자 + 재고 있을 때만 대출 버튼 표시
3.3 book_form.html
<!-- books/templates/books/book_form.html -->
{% extends 'base.html' %}
{% block content %}
<div class="card">
<div class="card-body">
<h3>{% if book %}도서 수정{% else %}도서 등록{% endif %}</h3>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="title" class="form-label">도서명</label>
<input type="text" class="form-control" id="title" name="title" value="{{ book.title|default:'' }}" required>
</div>
<div class="mb-3">
<label for="author" class="form-label">저자</label>
<input type="text" class="form-control" id="author" name="author" value="{{ book.author|default:'' }}" required>
</div>
<div class="mb-3">
<label for="isbn" class="form-label">ISBN</label>
<input type="text" class="form-control" id="isbn" name="isbn" value="{{ book.isbn|default:'' }}" required>
</div>
<div class="mb-3">
<label for="publisher" class="form-label">출판사</label>
<input type="text" class="form-control" id="publisher" name="publisher" value="{{ book.publisher|default:'' }}" required>
</div>
<div class="mb-3">
<label for="total_quantity" class="form-label">총 수량</label>
<input type="number" class="form-control" id="total_quantity" name="total_quantity" value="{{ book.total_quantity|default:'1' }}" required>
</div>
<button type="submit" class="btn btn-primary">저장</button>
<a href="{% url 'books:book-list' %}" class="btn btn-secondary">취소</a>
</form>
</div>
</div>
{% endblock %}
<!-- books/templates/books/book_form.html -->
{% extends 'base.html' %}
{% block content %}
<div class="card">
<div class="card-body">
<h3>{% if book %}도서 수정{% else %}도서 등록{% endif %}</h3>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="title" class="form-label">도서명</label>
<input type="text" class="form-control" id="title" name="title" value="{{ book.title|default:'' }}" required>
</div>
<div class="mb-3">
<label for="author" class="form-label">저자</label>
<input type="text" class="form-control" id="author" name="author" value="{{ book.author|default:'' }}" required>
</div>
<div class="mb-3">
<label for="isbn" class="form-label">ISBN</label>
<input type="text" class="form-control" id="isbn" name="isbn" value="{{ book.isbn|default:'' }}" required>
</div>
<div class="mb-3">
<label for="publisher" class="form-label">출판사</label>
<input type="text" class="form-control" id="publisher" name="publisher" value="{{ book.publisher|default:'' }}" required>
</div>
<div class="mb-3">
<label for="total_quantity" class="form-label">총 수량</label>
<input type="number" class="form-control" id="total_quantity" name="total_quantity" value="{{ book.total_quantity|default:'1' }}" required>
</div>
<button type="submit" class="btn btn-primary">저장</button>
<a href="{% url 'books:book-list' %}" class="btn btn-secondary">취소</a>
</form>
</div>
</div>
{% endblock %}
3.3.1 book_form.html 핵심 기능
- 등록/수정 구분(
{% if book %}
) - 필수 입력 필드 구성
- 기존 데이터 표시(
{{ book.field|default:'' }}
)
3.4 book_confirm_delete.html
{% extends 'base.html' %}
{% block content %}
<div class="card">
<div class="card-body">
<h3 class="card-title">도서 삭제</h3>
<p>정말 "{{ book.title }}" 도서를 삭제하시겠습니까?</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">삭제</button>
<a href="{% url 'books:book-detail' book.pk %}" class="btn btn-secondary">취소</a>
</form>
</div>
</div>
{% endblock %}
{% extends 'base.html' %}
{% block content %}
<div class="card">
<div class="card-body">
<h3 class="card-title">도서 삭제</h3>
<p>정말 "{{ book.title }}" 도서를 삭제하시겠습니까?</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">삭제</button>
<a href="{% url 'books:book-detail' book.pk %}" class="btn btn-secondary">취소</a>
</form>
</div>
</div>
{% endblock %}
3.4.1 book_confirm_delete.html 핵심 기능
- 삭제 확인 메시지
- 취소 시 상세 페이지로 복귀
- POST 방식으로 삭제 처리
3.5 loan_list.html
<<!-- books/templates/books/loan_list.html -->
{% extends 'base.html' %}
{% block content %}
<h3>대출 현황</h3>
<table class="table">
<thead>
<tr>
<th>도서명</th>
<th>대출자</th>
<th>대출일</th>
<th>반납예정일</th>
<th>상태</th>
<th>액션</th>
</tr>
</thead>
<tbody>
{% for loan in loans %}
<tr>
<td>{{ loan.book.title }}</td>
<td>{{ loan.user.username }}</td>
<td>{{ loan.loan_date|date:"Y-m-d" }}</td>
<td>{{ loan.due_date|date:"Y-m-d" }}</td>
<td>
{% if loan.status == 'RETURNED' %}
<span class="badge bg-success">반납완료</span>
{% elif loan.is_overdue %}
<span class="badge bg-danger">연체</span>
{% else %}
<span class="badge bg-primary">대출중</span>
{% endif %}
</td>
<td>
{% if user.role == 'LIBRARIAN' or user == loan.user %}
<form method="post" action="{% url 'books:loan-return' loan.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-success">반납</button>
</form>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">대출 내역이 없습니다.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
<<!-- books/templates/books/loan_list.html -->
{% extends 'base.html' %}
{% block content %}
<h3>대출 현황</h3>
<table class="table">
<thead>
<tr>
<th>도서명</th>
<th>대출자</th>
<th>대출일</th>
<th>반납예정일</th>
<th>상태</th>
<th>액션</th>
</tr>
</thead>
<tbody>
{% for loan in loans %}
<tr>
<td>{{ loan.book.title }}</td>
<td>{{ loan.user.username }}</td>
<td>{{ loan.loan_date|date:"Y-m-d" }}</td>
<td>{{ loan.due_date|date:"Y-m-d" }}</td>
<td>
{% if loan.status == 'RETURNED' %}
<span class="badge bg-success">반납완료</span>
{% elif loan.is_overdue %}
<span class="badge bg-danger">연체</span>
{% else %}
<span class="badge bg-primary">대출중</span>
{% endif %}
</td>
<td>
{% if user.role == 'LIBRARIAN' or user == loan.user %}
<form method="post" action="{% url 'books:loan-return' loan.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-success">반납</button>
</form>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">대출 내역이 없습니다.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
3.5.1 loan_list.html (대출 현황) 핵심 기능
-
대출 상태 표시 시스템
RETURNED
: 녹색 배지로 "반납완료" 표시is_overdue
: 빨간색 배지로 "연체" 표시- 그 외: 파란색 배지로 "대출중" 표시
-
권한 기반 반납 기능
- 사서 또는 해당 대출자만 반납 버튼 표시
{% if user.role == 'LIBRARIAN' or user == loan.user %}
{% if user.role == 'LIBRARIAN' or user == loan.user %}
-
날짜 포맷팅
|date:"Y-m-d"
필터로 날짜 형식 통일
3.6 loan_form.html
<!-- books/templates/books/loan_form.html -->
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="mb-0">도서 대출</h3>
</div>
<div class="card-body">
<h5 class="card-title">{{ book.title }}</h5>
<p class="card-text">
<strong>저자:</strong> {{ book.author }}<br>
<strong>출판사:</strong> {{ book.publisher }}<br>
<strong>ISBN:</strong> {{ book.isbn }}<br>
<strong>잔여 수량:</strong> {{ book.available_quantity }}/{{ book.total_quantity }}
</p>
{% if form.errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">대출하기</button>
<a href="{% url 'books:book-detail' book.pk %}" class="btn btn-secondary">취소</a>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
<!-- books/templates/books/loan_form.html -->
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="mb-0">도서 대출</h3>
</div>
<div class="card-body">
<h5 class="card-title">{{ book.title }}</h5>
<p class="card-text">
<strong>저자:</strong> {{ book.author }}<br>
<strong>출판사:</strong> {{ book.publisher }}<br>
<strong>ISBN:</strong> {{ book.isbn }}<br>
<strong>잔여 수량:</strong> {{ book.available_quantity }}/{{ book.total_quantity }}
</p>
{% if form.errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">대출하기</button>
<a href="{% url 'books:book-detail' book.pk %}" class="btn btn-secondary">취소</a>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
3.6.1 loan_form.html (대출 신청) 주요 구성
-
도서 정보 표시 섹션
- 제목, 저자, 출판사, ISBN
- 현재 재고 상태 표시
-
폼 오류 처리
{% if form.errors %} {% for error in form.non_field_errors %}
{% if form.errors %} {% for error in form.non_field_errors %}
-
폼 레이아웃
- Bootstrap 그리드 시스템 활용
- 반응형 디자인(col-md-8)
3.7 reservation_list.html
<!-- books/templates/books/reservation_list.html -->
{% extends 'base.html' %}
{% block content %}
<h3>예약 현황</h3>
<table class="table">
<thead>
<tr>
<th>도서명</th>
<th>예약자</th>
<th>예약일</th>
<th>만료일</th>
<th>상태</th>
<th>액션</th>
</tr>
</thead>
<tbody>
{% for reservation in reservations %}
<tr>
<td>{{ reservation.book.title }}</td>
<td>{{ reservation.user.username }}</td>
<td>{{ reservation.reserved_date|date:"Y-m-d" }}</td>
<td>{{ reservation.expiry_date|date:"Y-m-d" }}</td>
<td>
{% if reservation.status == 'WAITING' %}
<span class="badge bg-warning">대기중</span>
{% elif reservation.status == 'AVAILABLE' %}
<span class="badge bg-success">대출가능</span>
{% else %}
<span class="badge bg-secondary">취소됨</span>
{% endif %}
</td>
<td>
{% if reservation.status == 'WAITING' %}
<form method="post" action="{% url 'books:reservation-cancel' reservation.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-danger">취소</button>
</form>
{% elif reservation.status == 'AVAILABLE' %}
<form method="post" action="{% url 'books:loan-create' reservation.book.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-primary">대출</button>
</form>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">예약 내역이 없습니다.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
<!-- books/templates/books/reservation_list.html -->
{% extends 'base.html' %}
{% block content %}
<h3>예약 현황</h3>
<table class="table">
<thead>
<tr>
<th>도서명</th>
<th>예약자</th>
<th>예약일</th>
<th>만료일</th>
<th>상태</th>
<th>액션</th>
</tr>
</thead>
<tbody>
{% for reservation in reservations %}
<tr>
<td>{{ reservation.book.title }}</td>
<td>{{ reservation.user.username }}</td>
<td>{{ reservation.reserved_date|date:"Y-m-d" }}</td>
<td>{{ reservation.expiry_date|date:"Y-m-d" }}</td>
<td>
{% if reservation.status == 'WAITING' %}
<span class="badge bg-warning">대기중</span>
{% elif reservation.status == 'AVAILABLE' %}
<span class="badge bg-success">대출가능</span>
{% else %}
<span class="badge bg-secondary">취소됨</span>
{% endif %}
</td>
<td>
{% if reservation.status == 'WAITING' %}
<form method="post" action="{% url 'books:reservation-cancel' reservation.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-danger">취소</button>
</form>
{% elif reservation.status == 'AVAILABLE' %}
<form method="post" action="{% url 'books:loan-create' reservation.book.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-primary">대출</button>
</form>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">예약 내역이 없습니다.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
3.7.1 reservation_list.html (예약 현황) 핵심 요소
-
예약 상태 시스템
WAITING
: 노란색 배지AVAILABLE
: 녹색 배지CANCELLED
: 회색 배지
-
상태별 액션 버튼
- 대기중: 예약 취소 버튼
- 대출가능: 대출 신청 버튼
- 취소됨: 액션 없음
-
날짜 정보
- 예약일(reserved_date)
- 만료일(expiry_date)
3.8 reservation_form.html
<!-- books/templates/books/reservation_form.html -->
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="mb-0">도서 예약</h3>
</div>
<div class="card-body">
<h5 class="card-title">{{ book.title }}</h5>
<p class="card-text">
<strong>저자:</strong> {{ book.author }}<br>
<strong>출판사:</strong> {{ book.publisher }}<br>
<strong>ISBN:</strong> {{ book.isbn }}<br>
<strong>잔여 수량:</strong> {{ book.available_quantity }}/{{ book.total_quantity }}
</p>
{% if form.errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">예약하기</button>
<a href="{% url 'books:book-detail' book.pk %}" class="btn btn-secondary">취소</a>
</form>
<div class="mt-3">
<small class="text-muted">
* 예약은 1일간 유효하며, 도서가 반납되면 우선적으로 대출하실 수 있습니다.
</small>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
<!-- books/templates/books/reservation_form.html -->
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="mb-0">도서 예약</h3>
</div>
<div class="card-body">
<h5 class="card-title">{{ book.title }}</h5>
<p class="card-text">
<strong>저자:</strong> {{ book.author }}<br>
<strong>출판사:</strong> {{ book.publisher }}<br>
<strong>ISBN:</strong> {{ book.isbn }}<br>
<strong>잔여 수량:</strong> {{ book.available_quantity }}/{{ book.total_quantity }}
</p>
{% if form.errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">예약하기</button>
<a href="{% url 'books:book-detail' book.pk %}" class="btn btn-secondary">취소</a>
</form>
<div class="mt-3">
<small class="text-muted">
* 예약은 1일간 유효하며, 도서가 반납되면 우선적으로 대출하실 수 있습니다.
</small>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
3.9.1 reservation_form.html (예약 신청) 상세 구성
-
도서 상세 정보 섹션
- 기본 정보 표시
- 현재 재고 상태
-
폼 검증
- 서버 측 오류 메시지 표시
- 필드별 오류 처리
-
추가 정보
- 예약 유효 기간 안내
- 우선 대출 권한 설명
-
레이아웃
- Bootstrap 카드 컴포넌트
- 반응형 그리드(justify-content-center)
-
네비게이션
- 취소 시 도서 상세 페이지로 복귀
- 폼 제출은 POST 방식
4. 사용자 관리 템플릿 예시
4.1 login.html
<!-- accounts/templates/accounts/login.html -->
{% extends 'base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h3 class="card-title text-center mb-4">로그인</h3>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="username" class="form-label">아이디</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">비밀번호</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">로그인</button>
</form>
<p class="text-center mt-3">
계정이 없으신가요? <a href="{% url 'accounts:register' %}">회원가입</a>
</p>
</div>
</div>
</div>
</div>
{% endblock %}
<!-- accounts/templates/accounts/login.html -->
{% extends 'base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h3 class="card-title text-center mb-4">로그인</h3>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="username" class="form-label">아이디</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">비밀번호</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">로그인</button>
</form>
<p class="text-center mt-3">
계정이 없으신가요? <a href="{% url 'accounts:register' %}">회원가입</a>
</p>
</div>
</div>
</div>
</div>
{% endblock %}
4.1.1 login.html (로그인) 핵심 구성
-
폼 구조
- 아이디/비밀번호 필드
- CSRF 토큰 보호
- Bootstrap form-control 클래스 적용
-
레이아웃
- 중앙 정렬(col-md-6)
- 카드 컴포넌트 활용
- 회원가입 링크 포함
4.2 register.html
<!-- accounts/templates/accounts/register.html -->
{% extends 'base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h3 class="card-title text-center mb-4">회원가입</h3>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="username" class="form-label">아이디</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">이메일</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password1" class="form-label">비밀번호</label>
<input type="password" class="form-control" id="password1" name="password1" required>
</div>
<div class="mb-3">
<label for="password2" class="form-label">비밀번호 확인</label>
<input type="password" class="form-control" id="password2" name="password2" required>
</div>
<button type="submit" class="btn btn-primary w-100">회원가입</button>
</form>
<p class="text-center mt-3">
이미 계정이 있으신가요? <a href="{% url 'accounts:login' %}">로그인</a>
</p>
</div>
</div>
</div>
</div>
{% endblock %}
<!-- accounts/templates/accounts/register.html -->
{% extends 'base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h3 class="card-title text-center mb-4">회원가입</h3>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="username" class="form-label">아이디</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">이메일</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password1" class="form-label">비밀번호</label>
<input type="password" class="form-control" id="password1" name="password1" required>
</div>
<div class="mb-3">
<label for="password2" class="form-label">비밀번호 확인</label>
<input type="password" class="form-control" id="password2" name="password2" required>
</div>
<button type="submit" class="btn btn-primary w-100">회원가입</button>
</form>
<p class="text-center mt-3">
이미 계정이 있으신가요? <a href="{% url 'accounts:login' %}">로그인</a>
</p>
</div>
</div>
</div>
</div>
{% endblock %}
4.2.1 register.html (회원가입) 주요 요소
-
필수 입력 필드
- 아이디
- 이메일
- 비밀번호
- 비밀번호 확인
-
보안 기능
- CSRF 보호
- 비밀번호 이중 입력 검증
- required 속성으로 필수 입력 강제
-
네비게이션
- 로그인 페이지 링크 제공
- 폼 레이아웃은 로그인과 동일
4.3 profile.html
<!-- accounts/templates/accounts/profile.html -->
{% extends 'base.html' %}
{% block content %}
<div class="card mb-4">
<div class="card-body">
<h3 class="card-title">내 정보</h3>
<p><strong>아이디:</strong> {{ user.username }}</p>
<p><strong>이메일:</strong> {{ user.email }}</p>
<p><strong>권한:</strong> {{ user.get_role_display }}</p>
</div>
</div>
<div class="card mb-4">
<div class="card-body">
<h4>현재 대출 현황</h4>
<table class="table">
<thead>
<tr>
<th>도서명</th>
<th>대출일</th>
<th>반납예정일</th>
<th>상태</th>
</tr>
</thead>
<tbody>
{% for loan in user.loan_set.all %}
<tr>
<td>{{ loan.book.title }}</td>
<td>{{ loan.loan_date|date:"Y-m-d" }}</td>
<td>{{ loan.due_date|date:"Y-m-d" }}</td>
<td>
{% if loan.is_overdue %}
<span class="badge bg-danger">연체</span>
{% else %}
<span class="badge bg-primary">대출중</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center">대출 중인 도서가 없습니다.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-body">
<h4>예약 현황</h4>
<table class="table">
<thead>
<tr>
<th>도서명</th>
<th>예약일</th>
<th>상태</th>
<th>액션</th>
</tr>
</thead>
<tbody>
{% for reservation in user.reservation_set.all %}
<tr>
<td>{{ reservation.book.title }}</td>
<td>{{ reservation.reserved_date|date:"Y-m-d" }}</td>
<td>
{% if reservation.status == 'WAITING' %}
<span class="badge bg-warning">대기중</span>
{% elif reservation.status == 'AVAILABLE' %}
<span class="badge bg-success">대출가능</span>
{% endif %}
</td>
<td>
{% if reservation.status == 'WAITING' %}
<form method="post" action="{% url 'books:reservation-cancel' reservation.id %}" class="d-inline">
{% csrf_token %}
<button class="btn btn-sm btn-danger">취소</button>
</form>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center">예약 중인 도서가 없습니다.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
<!-- accounts/templates/accounts/profile.html -->
{% extends 'base.html' %}
{% block content %}
<div class="card mb-4">
<div class="card-body">
<h3 class="card-title">내 정보</h3>
<p><strong>아이디:</strong> {{ user.username }}</p>
<p><strong>이메일:</strong> {{ user.email }}</p>
<p><strong>권한:</strong> {{ user.get_role_display }}</p>
</div>
</div>
<div class="card mb-4">
<div class="card-body">
<h4>현재 대출 현황</h4>
<table class="table">
<thead>
<tr>
<th>도서명</th>
<th>대출일</th>
<th>반납예정일</th>
<th>상태</th>
</tr>
</thead>
<tbody>
{% for loan in user.loan_set.all %}
<tr>
<td>{{ loan.book.title }}</td>
<td>{{ loan.loan_date|date:"Y-m-d" }}</td>
<td>{{ loan.due_date|date:"Y-m-d" }}</td>
<td>
{% if loan.is_overdue %}
<span class="badge bg-danger">연체</span>
{% else %}
<span class="badge bg-primary">대출중</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center">대출 중인 도서가 없습니다.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-body">
<h4>예약 현황</h4>
<table class="table">
<thead>
<tr>
<th>도서명</th>
<th>예약일</th>
<th>상태</th>
<th>액션</th>
</tr>
</thead>
<tbody>
{% for reservation in user.reservation_set.all %}
<tr>
<td>{{ reservation.book.title }}</td>
<td>{{ reservation.reserved_date|date:"Y-m-d" }}</td>
<td>
{% if reservation.status == 'WAITING' %}
<span class="badge bg-warning">대기중</span>
{% elif reservation.status == 'AVAILABLE' %}
<span class="badge bg-success">대출가능</span>
{% endif %}
</td>
<td>
{% if reservation.status == 'WAITING' %}
<form method="post" action="{% url 'books:reservation-cancel' reservation.id %}" class="d-inline">
{% csrf_token %}
<button class="btn btn-sm btn-danger">취소</button>
</form>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center">예약 중인 도서가 없습니다.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
4.3.1 profile.html (프로필) 상세 구조
-
사용자 정보 섹션
- 아이디/이메일 표시
- 사용자 권한 표시(
get_role_display
)
-
대출 현황 테이블
- 현재 대출 도서 목록
- 연체 상태 표시(badge 사용)
- 반납 예정일 정보
-
예약 현황 섹션
- 예약 상태별 표시(대기중/대출가능)
- 예약 취소 기능(WAITING 상태만)
- 날짜 포맷팅 적용
-
데이터 연결
user.loan_set.all
로 대출 정보 접근user.reservation_set.all
로 예약 정보 접근
-
빈 데이터 처리
{% empty %}
태그로 데이터 없음 메시지- 테이블 전체 너비 사용(colspan)