Laboratory work 4
Рейнгеверц В.А. - K33401
Frontend
Desktop
Главная страница
- Боковое меню с навигацией, авторизацией и сменой светлой/темной темы
Пошаговая регистрация
- Client-side & server-side валидация на каждом шагу
- Динамически подгружаются варианты выбора для choice полей модели
- Динамически подгружаются варианты выбора для foreign key полей модели
Выбор библиотеки
- У каждой библиотеки свои читательские залы
Авторизация
- По djoser токену, хранящемся в Local Storage/Session Storage
Уведомления
- Об ошибках, успешных действиях
Кнопка "выйти"
- Инвалидизирует djoser токен
Читательские залы библиотеки
- Динамически подгружаются
Зарезервированные пользователем книги
Mobile & dark/light mode
Django backend + React frontend интеграция
index.html
{% load static %}
{% load env_utils %}
{% load django_vite %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
<title>Librarian</title>
{% comment %} https://vitejs.dev/guide/backend-integration.html {% endcomment %}
<script type="module">
import RefreshRuntime from 'http://{% get_env "host" "localhost" %}:{% get_env "frontend_port" 3000 %}/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
{% comment %} https://github.com/MrBin99/django-vite#template-tags {% endcomment %}
{% vite_hmr_client %}
{% vite_asset 'src/core/index.jsx' %}
<style type="text/css">
#root {
overflow: hidden;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>
vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { resolve } from "path";
const backendPort = process.env.backend_port ?? 8000;
const host = process.env.host ?? "localhost";
// https://vitejs.dev/config/
// https://github.com/MrBin99/django-vite-example/blob/master/vite.config.js
export default defineConfig({
plugins: [react()],
root: resolve("./static"),
base: "/static/",
server: {
strictPort: true,
port: 3000,
open: false,
watch: {
usePolling: true,
disableGlobbing: false,
},
origin: `http://${host}:${backendPort}`,
},
resolve: {
extensions: [".js", ".jsx", ".json"],
alias: {
"~": resolve("./static/src"),
},
},
build: {
outDir: resolve("./static/dist"),
assetsDir: "",
manifest: true,
emptyOutDir: true,
target: "es2015",
rollupOptions: {
input: {
main: resolve("./static/src/core/index.js"),
},
output: {
chunkFileNames: undefined,
},
},
},
define: {
backendPort: JSON.stringify(backendPort),
},
});
index.js
import "vite/modulepreload-polyfill";
import React from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import App from "~/core/App";
const queryClient = new QueryClient();
const root = createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App queryClient={queryClient} />
</BrowserRouter>
</QueryClientProvider>
</React.StrictMode>
);
Reference
Общение frontend'a с backend'ом по REST API
Для коммуникации по REST API были созданы две функции - fetchFromBackendApi
и pushToBackendApi
.
Функция fetchFromBackendApi
отправляет GET запросы на бекэнд прилагая csrf токен и, опционально, djoser токен.
Функция pushToBackendApi
может оправлять POST, PATCH, DELETE запросы, прилагая помимо токенов еще и тело запроса.
utils/BackendAPI.js
import { getCookie, getToken } from "~/utils/Token";
export const fetchFromBackendApi = async (pathSegments = [], dropToken = false) => {
const path = pathSegments.join("/");
const token = dropToken ? null : getToken();
const res = await fetch(`${window.location.origin}/${path}`, {
method: "GET",
mode: "cors",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
// For djago session auth
"X-CSRFToken": getCookie("csrftoken"),
// For djago token auth
...(token && { Authorization: `Token ${token}` }),
},
});
if (!res.ok) throw new Error(res.statusText);
return { json: await res.json(), ok: res.ok };
};
export const pushToBackendApi = async (
pathSegments = [],
method = "POST",
body = {},
dropToken = false
) => {
let path = pathSegments.join("/");
// Account for a bug in django backend's urls.py
if (method === "POST" && pathSegments.length <= 2) path += "/";
const token = dropToken ? null : getToken();
const res = await fetch(`${window.location.origin}/${path}`, {
method,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
// For djago session auth
"X-CSRFToken": getCookie("csrftoken"),
// For djago token auth
...(token && { Authorization: `Token ${token}` }),
},
credentials: "same-origin",
body: JSON.stringify(body),
});
// Handle empty responses
if (res.status === 204) return { json: {}, ok: res.ok };
return { json: await res.json(), ok: res.ok };
};
// <...>
export const fetchLibrariesPublic = async () =>
await fetchFromBackendApi(["api", "libraries-public"], true);
// <...>
Далее ответ принимается в хуках от библиотеки React Query (он был вынес в отдельный, кастомный хук), где, в зависимости от ответа, данные обрабатываются и возвращаются как state.
hooks/index.js
import { useQuery } from "@tanstack/react-query";
import backendApi from "~/utils/BackendApi";
export const useGetLibrariesPublic = () => {
return useQuery(["libraries"], backendApi.fetchLibrariesPublic, {
select: (data) => {
const libraries = data.json["Library"];
return libraries;
},
});
};
// <...>
core/App.js
import React, { useState } from "react";
// <...>
import { useGetLibrariesPublic } from "~/hooks";
const App = (/* <...> */) => {
// <...>
const { data: libraries, status: librariesStatus } = useGetLibrariesPublic();
// <...>
return /* <...> */
}
export default App
Running
Makes migrations, migrates, runs django backend server and react frontend server
- Default backend port: 8000 (can be changed in
run.sh
) - Default frontend port: 3000 (can be changed in
run.sh
)
Passes any command to the django's manage.py
E.g.
Passes any command to the npm
E.g.
Passes any command to the pip
E.g.
Требования к представлениям
Пользователь может:
- Зайти
- Найти книгу
- Выбрать книгу
- Получить книгу
- Сдать книгу
Nice-to-haves:
- Личный кабинет с книгами и фильтром по дате
- Представление для библиотекаря
Backend
ER Diagram
Backend urls
Description
Задание 2
Создать программную систему, предназначенную для работников библиотеки. Такая система должна обеспечивать хранение сведений об имеющихся в библиотеке книгах, о читателях библиотеки и читальных залах.
Для каждой книги в БД должны храниться следующие сведения: название книги, автор (ы), издательство, год издания, раздел, число экземпляров этой книги в каждом зале библиотеки, а также шифр книги и дата закрепления книги за читателем. Книги могут перерегистрироваться в другом зале.
Сведения о читателях библиотеки должны включать номер читательского билета, ФИО читателя, номер паспорта, дату рождения, адрес, номер телефона, образование, наличие ученой степени.
Читатели закрепляются за определенным залом, могут переписаться в другой зал и могут записываться и выписываться из библиотеки.
Библиотека имеет несколько читальных залов, которые характеризуются номером, названием и вместимостью, то есть количеством людей, которые могут одновременно работать в зале.
Библиотека может получать новые книги и списывать старые. Шифр книги может измениться в результате переклассификации, а номер читательского билета в результате перерегистрации.
Библиотекарю могут потребоваться следующие сведения о текущем состоянии библиотеки:
- Какие книги закреплены за заданным читателем?
- Кто из читателей взял книгу более месяца тому назад?
- За кем из читателей закреплены книги, количество экземпляров которых в библиотеке не превышает 2?
- Сколько в библиотеке читателей младше 20 лет?
- Сколько читателей в процентном отношении имеют начальное образование, среднее, высшее, ученую степень?
Библиотекарь может выполнять следующие операции:
- Записать в библиотеку нового читателя.
- Исключить из списка читателей людей, записавшихся в библиотеку более года назад и не прошедших - перерегистрацию.
- Списать старую или потерянную книгу.
- Принять книгу в фонд библиотеки.
Необходимо предусмотреть возможность выдачи отчета о работе библиотеки в течение месяца. Отчет должен включать в себя следующую информацию:
- Количество книг и читателей на каждый день в каждом из залов и в библиотеке в целом
- Количество читателей, записавшихся в библиотеку в каждый зал и в библиотеку за отчетный месяц.
Python requirements
- django-phonenumber-field[phonenumberslite]
- djoser
- djangorestframework_simplejwt
- drf_yasg
- django-cors-headers
- django-vite