Добавление в проект Bootstrap 5
Подключим Bootstrap 5 и добавим его классы в шаблон templates\base.html:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}Books{% endblock title %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'base.css' %}">
</head>
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
<div class="container">
<div class="row">
<div class="col">
<!-- Форма для добавления новой книги -->
{% include "partial_create_book_form.html" %}
<!-- Таблица книг -->
<form class="form-inline">
<table class="table table-bordered table-sm mt-5">
<thead>
<tr>
<!-- Заголовки столбцов таблицы -->
<!-- Заголовок номера книги -->
<th scope="col" style="width: 10%" class="text-center">
<small>
No.
<a href=""
hx-get="{% url 'book_list_sort' filter='id' direction='ascend' %}"
hx-target="#book-list"
hx-swap="innerHTML">
⇓
</a>
<a href=""
hx-get="{% url 'book_list_sort' filter='id' direction='descend' %}"
hx-target="#book-list"
hx-swap="innerHTML">
⇑
</a>
</small>
</th>
<!-- Заголовок названия книги -->
<th scope="col" style="width: 26%" class="text-center">
<small>
Title
<a href=""
hx-get="{% url 'book_list_sort' filter='title' direction='ascend' %}"
hx-target="#book-list"
hx-swap="innerHTML">
⇓
</a>
<a href=""
hx-get="{% url 'book_list_sort' filter='title' direction='descend' %}"
hx-target="#book-list"
hx-swap="innerHTML">
⇑
</a>
</small>
</th>
<!-- Заголовок автора книги -->
<th scope="col" style="width: 26%" class="text-center">
<small>
Author
<a href=""
hx-get="{% url 'book_list_sort' filter='author' direction='ascend' %}"
hx-target="#book-list"
hx-swap="innerHTML">
⇓
</a>
<a href=""
hx-get="{% url 'book_list_sort' filter='author' direction='descend' %}"
hx-target="#book-list"
hx-swap="innerHTML">
⇑
</a>
</small>
</th>
<!-- Заголовок цены книги -->
<th scope="col" style="width: 11%" class="text-center">
<small>
Price ($)
<a href=""
hx-get="{% url 'book_list_sort' filter='price' direction='ascend' %}"
hx-target="#book-list"
hx-swap="innerHTML">
⇓
</a>
<a href=""
hx-get="{% url 'book_list_sort' filter='price' direction='descend' %}"
hx-target="#book-list"
hx-swap="innerHTML">
⇑
</a>
</small>
</th>
<!-- Заголовок статуса книги -->
<th scope="col" style="width: 11%" class="text-center">
<small>
Status
<a href=""
hx-get="{% url 'book_list_sort' filter='read' direction='descend' %}"
hx-target="#book-list"
hx-swap="innerHTML">
⇓
</a>
<a href=""
hx-get="{% url 'book_list_sort' filter='read' direction='ascend' %}"
hx-target="#book-list"
hx-swap="innerHTML">
⇑
</a>
</small>
</th>
<!-- Заголовок кнопок действия -->
<th scope="col" style="width: 16%" class="text-center">
<small>
Actions
</small>
</th>
</tr>
</thead>
<!-- Список книг -->
<tbody id="book-list">
{% include "partial_book_list.html" %}
</tbody>
</table>
</form>
</div>
</div>
</div>
<script src="https://unpkg.com/htmx.org@1.9.4"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3"
crossorigin="anonymous"></script>
<script>
function cngElementsAtr(cls, atr, val) {
var elems = document.getElementsByClassName(cls);
for (var i = 0; i < elems.length; i++) {
elems[i][atr] = val;
}
}
document.body.addEventListener('htmx:afterRequest', (event) => {
path_str = event.detail.pathInfo.requestPath;
if (path_str.includes('create_book')) {
cngElementsAtr('clrtxt', 'value', '');
} else if (path_str.includes('update_book_details')) {
if (event.detail.requestConfig.verb === 'put') {
cngElementsAtr('disbtn', 'disabled', true);
} else {
cngElementsAtr('disbtn', 'disabled', false);
}
} else if (path_str.includes('book_detail')) {
cngElementsAtr('disbtn', 'disabled', false);
}
});
</script>
</body>
</html>
Добавим Bootstrap-классы в шаблон templates\partial_book_detail.html:
<tr class="{% if book.read %}grey-text{% endif %}">
<!-- Ячейка номера книги -->
<td class="align-middle text-center ps-2">
{{ book.id }}
</td>
<!-- Ячейка названия книги -->
<td class="align-middle ps-2">
{{ book.title }}
</td>
<!-- Ячейка автора книги -->
<td class="align-middle ps-2">
{{ book.author }}
</td>
<!-- Ячейка цены книги -->
<td class="align-middle text-center">
{{ book.price }}
</td>
<!-- Ячейка состояния -->
<td class="align-middle text-center">
{% if not book.read %}
<!-- Кнопка "Unread" -->
<button class="disbtn table-button status-button btn btn-primary btn-sm"
hx-put="{% url 'update_book_status' book.id %}"
hx-target="closest tr"
hx-swap="outerHTML">
Unread
</button>
{% else %}
<!-- Кнопка "Read" -->
<button class="disbtn table-button status-button btn btn-secondary btn-sm"
hx-put="{% url 'update_book_status' book.id %}"
hx-target="closest tr"
hx-swap="outerHTML">
Read
</button>
{% endif %}
</td>
<!-- Ячейка кнопок действия -->
<td class="align-middle text-center">
<!-- Кнопка "Edit" -->
<button class="disbtn table-button any-button btn btn-primary btn-sm me-2"
hx-put="{% url 'update_book_details' book.id %}"
hx-target="closest tr"
hx-swap="outerHTML">
Edit
</button>
<!-- Кнопка "Delete" -->
<button class="disbtn table-button any-button btn btn-danger btn-sm"
hx-delete="{% url 'delete_book' book.id %}"
hx-target="closest tr"
hx-swap="outerHTML"
hx-confirm="Are you sure you wish to delete this book?">
Delete
</button>
</td>
</tr>
Добавим Bootstrap-классы в шаблон templates\partial_book_update_form.html:
<tr class="{% if book.read %}grey-text{% endif %}">
<!-- Ячейка номера книги -->
<td class="align-middle text-center ps-2">
{{ book.id }}
</td>
<!-- Ячейка названия книги -->
<td class="align-middle ps-2">
{{ form.title }}
</td>
<!-- Ячейка автора книги -->
<td class="align-middle ps-2">
{{ form.author }}
</td>
<!-- Ячейка цены книги -->
<td class="align-middle text-center">
{{ form.price }}
</td>
<!-- Ячейка состояния -->
<td class="align-middle text-center">
{% if not book.read %}
<!-- Кнопка "Unread" -->
<button class="table-button status-button btn btn-primary btn-sm" disabled>
Unread
</button>
{% else %}
<!-- Кнопка "Read" -->
<button class="table-button status-button btn btn-secondary btn-sm" disabled>
Read
</button>
{% endif %}
</td>
<!-- Ячейка кнопок действия -->
<td class="align-middle text-center">
<!-- Кнопка "Save" -->
<button type="submit"
class="table-button any-button btn btn-success btn-sm me-2"
hx-post="{% url 'update_book_details' book.id %}"
hx-target="closest tr"
hx-swap="outerHTML"
hx-include="closest tr">
Save
</button>
<!-- Кнопка "Cancel" -->
<button class="table-button any-button btn btn-warning btn-sm"
hx-get="{% url 'book_detail' book.id %}"
hx-target="closest tr"
hx-swap="outerHTML">
Cancel
</button>
</td>
</tr>
Добавим Bootstrap-классы в шаблон templates\partial_create_book_form.html:
<form class="form-inline">
<div class="row">
<div class="col">
{{ form.title }}
</div>
<div class="col">
{{ form.author }}
</div>
<div class="col">
{{ form.price }}
</div>
<div class="col">
<!-- Кнопка "Add book" -->
<button type="submit"
class="disbtn btn btn-primary"
hx-post="{% url 'create_book' %}"
hx-target="#book-list"
hx-swap="beforeend">
Add book
</button>
</div>
</div>
</form>
Добавим Bootstrap-класс form-control в файл books\forms.py:
class BookEditForm(BookCreateForm):
title = CharField(required=False, widget=TextInput(attrs={"class":"form-control-sm form-control"}))
author = CharField(required=False, widget=TextInput(attrs={"class":"form-control-sm form-control"}))
price = CharField(required=False, widget=TextInput(attrs={"class":"form-control-sm form-control"}))