diff --git a/!doc.tar.gz b/!doc.tar.gz new file mode 100644 index 0000000..acc292d Binary files /dev/null and b/!doc.tar.gz differ diff --git a/!doc/.~lock.fitnesscenter_report.md# b/!doc/.~lock.fitnesscenter_report.md# new file mode 100644 index 0000000..e6f8306 --- /dev/null +++ b/!doc/.~lock.fitnesscenter_report.md# @@ -0,0 +1 @@ +,q,think,23.03.2026 13:02,file:///home/q/.config/libreoffice/4; \ No newline at end of file diff --git a/!doc/dashboard.png b/!doc/dashboard.png new file mode 100644 index 0000000..2556ce6 Binary files /dev/null and b/!doc/dashboard.png differ diff --git a/!doc/dashboard_auth.html b/!doc/dashboard_auth.html new file mode 100644 index 0000000..4976804 --- /dev/null +++ b/!doc/dashboard_auth.html @@ -0,0 +1,204 @@ + + + + + + + Личный кабинет — FitnessCenter + + + + + + + + + + +
+
+ +
+ +
+
+
+
+
+
+
Е
+
+
Егор Тестов
+
report@example.com
+
+
+ +
Активен до 15.04.2026
+ + Выйти +
+
+
+
+
+
+

Оформить абонемент

+
+ +
+
+
+
+
Старт 1 месяц
+
30 дней
+
+
2490,00 ₽
+
+

Базовый доступ ко всем зонам клуба.

+
+ + +
+
+
+ +
+
+
+
+
Сила 3 месяца
+
90 дней
+
+
6490,00 ₽
+
+

Три месяца + гостевой визит раз в месяц.

+
+ + +
+
+
+ +
+
+
+
+
Премиум 12 месяцев
+
365 дней
+
+
19900,00 ₽
+
+

Год без ограничений, приоритетная запись на тренировки.

+
+ + +
+
+
+ +
+
+
+ +
+
+

История

+
+ + + + + + + + + + + + + + + + + +
ПланПериодСтатус
Старт 1 месяц16.03.2026 — 15.04.2026 + + Активен + +
+
+
+
+
+
+
+ +
+ + + + + + + diff --git a/!doc/embed_docx_images.py b/!doc/embed_docx_images.py new file mode 100644 index 0000000..3f64c0d --- /dev/null +++ b/!doc/embed_docx_images.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import shutil +import tempfile +import zipfile +from pathlib import Path +from urllib.parse import unquote, urlparse +from xml.etree import ElementTree as ET + + +PKG_NS = "http://schemas.openxmlformats.org/package/2006/relationships" +R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" +A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main" +CT_NS = "http://schemas.openxmlformats.org/package/2006/content-types" + +ET.register_namespace("", PKG_NS) +ET.register_namespace("a", A_NS) +ET.register_namespace("r", R_NS) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Embed externally linked local images into a DOCX file." + ) + parser.add_argument("input_docx", type=Path) + parser.add_argument("output_docx", type=Path) + return parser.parse_args() + + +def local_path_from_target(target: str) -> Path | None: + parsed = urlparse(target) + if parsed.scheme != "file": + return None + return Path(unquote(parsed.path)) + + +def ensure_content_type(defaults_root: ET.Element, extension: str) -> None: + ext = extension.lower().lstrip(".") + existing = defaults_root.find(f"{{{CT_NS}}}Default[@Extension='{ext}']") + if existing is not None: + return + + content_types = { + "png": "image/png", + "jpg": "image/jpeg", + "jpeg": "image/jpeg", + "gif": "image/gif", + "bmp": "image/bmp", + "svg": "image/svg+xml", + } + content_type = content_types.get(ext) + if not content_type: + return + + ET.SubElement( + defaults_root, + f"{{{CT_NS}}}Default", + {"Extension": ext, "ContentType": content_type}, + ) + + +def main() -> int: + args = parse_args() + input_docx = args.input_docx.resolve() + output_docx = args.output_docx.resolve() + + with tempfile.TemporaryDirectory() as tmpdir: + workdir = Path(tmpdir) + with zipfile.ZipFile(input_docx) as zin: + zin.extractall(workdir) + + rels_path = workdir / "word" / "_rels" / "document.xml.rels" + doc_path = workdir / "word" / "document.xml" + types_path = workdir / "[Content_Types].xml" + media_dir = workdir / "word" / "media" + media_dir.mkdir(parents=True, exist_ok=True) + + rels_tree = ET.parse(rels_path) + rels_root = rels_tree.getroot() + doc_tree = ET.parse(doc_path) + doc_root = doc_tree.getroot() + types_tree = ET.parse(types_path) + types_root = types_tree.getroot() + + rel_to_target: dict[str, str] = {} + next_image_num = 1 + + existing_media = sorted(media_dir.glob("image*.*")) + if existing_media: + next_image_num = len(existing_media) + 1 + + for rel in rels_root.findall(f"{{{PKG_NS}}}Relationship"): + rel_type = rel.attrib.get("Type", "") + target_mode = rel.attrib.get("TargetMode") + if not rel_type.endswith("/image") or target_mode != "External": + continue + + target = rel.attrib.get("Target", "") + local_path = local_path_from_target(target) + if not local_path or not local_path.exists(): + continue + + image_name = f"image{next_image_num}{local_path.suffix.lower()}" + next_image_num += 1 + embedded_target = f"media/{image_name}" + shutil.copy2(local_path, media_dir / image_name) + + rel.attrib["Target"] = embedded_target + rel.attrib.pop("TargetMode", None) + rel_to_target[rel.attrib["Id"]] = embedded_target + ensure_content_type(types_root, local_path.suffix) + + if not rel_to_target: + raise SystemExit("No external local images found in DOCX relationships.") + + for blip in doc_root.findall(f".//{{{A_NS}}}blip"): + link_key = f"{{{R_NS}}}link" + embed_key = f"{{{R_NS}}}embed" + rel_id = blip.attrib.get(link_key) + if rel_id in rel_to_target: + blip.attrib[embed_key] = rel_id + blip.attrib.pop(link_key, None) + + rels_tree.write(rels_path, encoding="UTF-8", xml_declaration=True) + doc_tree.write(doc_path, encoding="UTF-8", xml_declaration=True) + types_tree.write(types_path, encoding="UTF-8", xml_declaration=True) + + if output_docx.exists(): + output_docx.unlink() + + with zipfile.ZipFile(output_docx, "w", zipfile.ZIP_DEFLATED) as zout: + for path in sorted(workdir.rglob("*")): + if path.is_dir(): + continue + zout.write(path, path.relative_to(workdir).as_posix()) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/!doc/example.docx b/!doc/example.docx new file mode 100644 index 0000000..5b09521 Binary files /dev/null and b/!doc/example.docx differ diff --git a/!doc/fitnesscenter_report.docx b/!doc/fitnesscenter_report.docx new file mode 100644 index 0000000..285e91d Binary files /dev/null and b/!doc/fitnesscenter_report.docx differ diff --git a/!doc/fitnesscenter_report.md b/!doc/fitnesscenter_report.md new file mode 100644 index 0000000..3663be6 --- /dev/null +++ b/!doc/fitnesscenter_report.md @@ -0,0 +1,143 @@ +# Отчёт по проекту FitnessCenter + +Подготовлено на основе технической документации проекта, работающего локального экземпляра сайта и шаблона оформления `example.docx`. + +Город: Ростов-на-Дону +Дата подготовки отчёта: 16.03.2026 + +## Введение + +Проект **FitnessCenter** представляет собой веб-приложение фитнес-клуба с лендингом, регистрацией пользователей, авторизацией, личным кабинетом и оформлением абонементов. Система ориентирована на быстрый запуск в учебной или демонстрационной среде и построена по модели серверного рендеринга HTML без отдельного JSON API. + +Целью работы команды было создание понятного и визуально цельного сайта, который одновременно решает маркетинговую задачу лендинга и прикладную задачу оформления абонементов через личный кабинет. + +## Краткое описание системы + +В ходе анализа технической документации и интерфейса были выделены следующие ключевые возможности проекта: + +- главная страница с презентацией клуба, тарифов, тренеров и филиалов; +- регистрация нового пользователя с проверкой телефона в формате `+7XXXXXXXXXX`; +- вход в систему через форму авторизации; +- личный кабинет с отображением активного абонемента и истории покупок; +- оформление нового абонемента из личного кабинета; +- административное управление данными через стандартную Django-админку; +- запуск в Docker-контуре с публикацией сервиса на порту `1337`. + +## Технологический стек и архитектура + +По технической документации проект реализован на следующем стеке: + +- backend: `Python 3.13`, `Django 5.1.4`; +- frontend: `Bootstrap 5.3`, кастомные стили в `static/css/main.css`, шрифт `Manrope`; +- база данных по умолчанию: `SQLite`, с возможностью переноса на `PostgreSQL`; +- инфраструктура: `Dockerfile`, `docker-compose.yml`, `entrypoint.sh`; +- архитектурный подход: серверный рендеринг HTML-страниц без выделенного REST API. + +Ключевые структурные элементы проекта: + +- `gymclub/` содержит настройки Django и верхнеуровневую маршрутизацию; +- `club/` реализует модели, формы, views и бизнес-логику абонементов; +- `templates/` содержит шаблоны страниц `home`, `register`, `login`, `dashboard`; +- `static/` и `staticfiles/` отвечают за визуальную часть проекта; +- маршруты регистрации, входа, выхода и покупки абонементов сосредоточены в `club/urls.py`. + +## Реализованный функционал + +В итоговой версии системы реализованы следующие пользовательские сценарии: + +### 1. Лендинг фитнес-клуба + +Главная страница знакомит пользователя с клубом, доступными тарифами, тренерами и адресами филиалов. Интерфейс выполнен в тёмной цветовой схеме с акцентным зелёным цветом, что делает сайт визуально единым и современным. + +### 2. Регистрация и авторизация + +Пользователь может создать аккаунт, указав логин, имя, фамилию, телефон, email и пароль. После регистрации доступны вход в систему и переход в личный кабинет. Для телефона реализована нормализация и валидация, что снижает количество некорректных записей. + +### 3. Личный кабинет и покупка абонементов + +После авторизации пользователь видит активный абонемент, историю покупок и перечень доступных тарифов. Покупка выполняется через серверный сценарий, который рассчитывает дату окончания абонемента автоматически. + +### 4. Эксплуатационная готовность + +Проект запускается в контейнере Docker, а настройки окружения вынесены в переменные. Это упрощает перенос между стендами и делает демонстрацию предсказуемой. + +## Команда проекта и распределение работ + +Оценка трудозатрат ниже является проектной и отражает предполагаемый вклад участников на протяжении основного этапа разработки с **10.02.2026 по 14.03.2026**. + +- **Комин Архип** + Роль: тимлид, инфраструктура, DevOps и backend. + Оценка занятости: **64 часа**. + Основные задачи: постановка архитектуры проекта, подготовка Docker-контура, настройка переменных окружения, организация структуры Django-приложения, реализация моделей `MembershipPlan`, `Profile`, `Subscription`, настройка маршрутов и контроль интеграции backend-части с шаблонами. + +- **Токмачев Егор** + Роль: full-stack разработчик. + Оценка занятости: **58 часов**. + Основные задачи: реализация форм регистрации и входа, связка пользовательских сценариев с бизнес-логикой, доработка личного кабинета, интеграция покупки абонементов, отладка переходов между страницами и проверка корректности пользовательских сообщений. + +- **Стрельников Вадим** + Роль: frontend разработчик, дизайнер. + Оценка занятости: **49 часов**. + Основные задачи: разработка визуальной концепции сайта, вёрстка главной страницы и экранов авторизации, настройка тёмной темы, адаптация карточек тарифов и тренеров, подбор визуальных акцентов, доработка читаемости и согласованности интерфейса на разных экранах. + +- **Акуев Магомед** + Роль: тестировщик, дизайнер. + Оценка занятости: **37 часов**. + Основные задачи: ручное тестирование регистрации, входа и кабинета, проверка сценариев покупки абонемента, поиск визуальных и функциональных дефектов, проверка текстов форм и состояний ошибок, участие в финальной полировке элементов интерфейса. + +**Суммарная оценка трудозатрат команды: 208 часов.** + +## Итоговые экраны системы + +Ниже приведены скриншоты реально работающего локального экземпляра проекта. + +### Главная страница + +![Главная страница проекта](home.png) + +На главной странице представлены ключевое позиционирование клуба, тарифы, карточки тренеров, адреса филиалов и информационные блоки. + +### Страница входа + +![Страница входа](login.png) + +Экран входа минималистичен и сфокусирован на быстрой авторизации пользователя. + +### Страница регистрации + +![Страница регистрации](register.png) + +Форма регистрации включает обязательные пользовательские поля и согласие на обработку персональных данных. + +### Личный кабинет пользователя + +![Личный кабинет пользователя](dashboard.png) + +В кабинете отображаются профиль пользователя, срок действия активного абонемента, доступные тарифы и история покупок. + +## Вывод + +По результатам анализа документации и проверки интерфейса можно сделать вывод, что проект **FitnessCenter** доведён до работоспособного состояния и решает основные задачи учебного веб-продукта: демонстрацию услуг фитнес-клуба, регистрацию пользователей, авторизацию и оформление абонементов. Архитектура проекта остаётся достаточно простой для сопровождения, а Docker-окружение делает запуск и демонстрацию удобными. + +Отдельным преимуществом проекта является согласованная работа команды: backend, frontend, дизайн и тестирование разделены по ролям, но при этом объединены в единый пользовательский сценарий. Это делает результат целостным как с технической, так и с визуальной точки зрения. + +## Использованные материалы и литература + +### Локальные материалы проекта + +- техническая документация проекта: `../DOCS_RU.md`; +- локально запущенный сайт: `http://localhost:1337`; +- шаблон оформления и структуры отчёта: `example.docx`. + +### Подобранная литература + +1. Django Software Foundation. **Django documentation** [Электронный ресурс]. URL: `https://docs.djangoproject.com/en/5.1/` (дата обращения: 16.03.2026). +2. Bootstrap Team. **Bootstrap v5.3 Documentation** [Электронный ресурс]. URL: `https://getbootstrap.com/docs/5.3/` (дата обращения: 16.03.2026). +3. Fowler M. **Patterns of Enterprise Application Architecture**. Boston: Addison-Wesley, 2002. +4. Krug S. **Don't Make Me Think, Revisited: A Common Sense Approach to Web Usability**. 3rd ed. Berkeley: New Riders, 2014. +5. Pressman R. S., Maxim B. R. **Software Engineering: A Practitioner's Approach**. 9th ed. New York: McGraw-Hill, 2019. +6. Gamma E., Helm R., Johnson R., Vlissides J. **Design Patterns: Elements of Reusable Object-Oriented Software**. Boston: Addison-Wesley, 1994. +7. Sommerville I. **Software Engineering**. 10th ed. Boston: Pearson, 2015. +8. Нильсен Я. **Веб-дизайн: удобство использования веб-сайтов**. М.: Вильямс, 2010. +9. Купер А., Рейман Р., Кронин Д., Носсел К. **Интерфейс. Основы проектирования взаимодействия**. 4-е изд. СПб.: Питер, 2017. +10. Макконнелл С. **Совершенный код**. 2-е изд. СПб.: Питер, 2019. diff --git a/!doc/fitnesscenter_reporte.docx.xml b/!doc/fitnesscenter_reporte.docx.xml new file mode 100644 index 0000000..8a2c374 --- /dev/null +++ b/!doc/fitnesscenter_reporte.docx.xml @@ -0,0 +1,94 @@ + + + + + + + Отчёт по проекту FitnessCenter + Подготовлено на основе технической документации проекта, работающего локального экземпляра сайта и шаблона оформления example.docx. + Город: Ростов-на-Дону +Дата подготовки отчёта: 16.03.2026 + Введение + Проект FitnessCenter представляет собой веб-приложение фитнес-клуба с лендингом, регистрацией пользователей, авторизацией, личным кабинетом и оформлением абонементов. Система ориентирована на быстрый запуск в учебной или демонстрационной среде и построена по модели серверного рендеринга HTML без отдельного JSON API. + Целью работы команды было создание понятного и визуально цельного сайта, который одновременно решает маркетинговую задачу лендинга и прикладную задачу оформления абонементов через личный кабинет. + Краткое описание системы + В ходе анализа технической документации и интерфейса были выделены следующие ключевые возможности проекта: + главная страница с презентацией клуба, тарифов, тренеров и филиалов; + регистрация нового пользователя с проверкой телефона в формате +7XXXXXXXXXX; + вход в систему через форму авторизации; + личный кабинет с отображением активного абонемента и истории покупок; + оформление нового абонемента из личного кабинета; + административное управление данными через стандартную Django-админку; + запуск в Docker-контуре с публикацией сервиса на порту 1337. + Технологический стек и архитектура + По технической документации проект реализован на следующем стеке: + backend: Python 3.13, Django 5.1.4; + frontend: Bootstrap 5.3, кастомные стили в static/css/main.css, шрифт Manrope; + база данных по умолчанию: SQLite, с возможностью переноса на PostgreSQL; + инфраструктура: Dockerfile, docker-compose.yml, entrypoint.sh; + архитектурный подход: серверный рендеринг HTML-страниц без выделенного REST API. + Ключевые структурные элементы проекта: + gymclub/ содержит настройки Django и верхнеуровневую маршрутизацию; + club/ реализует модели, формы, views и бизнес-логику абонементов; + templates/ содержит шаблоны страниц home, register, login, dashboard; + static/ и staticfiles/ отвечают за визуальную часть проекта; + маршруты регистрации, входа, выхода и покупки абонементов сосредоточены в club/urls.py. + Реализованный функционал + В итоговой версии системы реализованы следующие пользовательские сценарии: + 1. Лендинг фитнес-клуба + Главная страница знакомит пользователя с клубом, доступными тарифами, тренерами и адресами филиалов. Интерфейс выполнен в тёмной цветовой схеме с акцентным зелёным цветом, что делает сайт визуально единым и современным. + 2. Регистрация и авторизация + Пользователь может создать аккаунт, указав логин, имя, фамилию, телефон, email и пароль. После регистрации доступны вход в систему и переход в личный кабинет. Для телефона реализована нормализация и валидация, что снижает количество некорректных записей. + 3. Личный кабинет и покупка абонементов + После авторизации пользователь видит активный абонемент, историю покупок и перечень доступных тарифов. Покупка выполняется через серверный сценарий, который рассчитывает дату окончания абонемента автоматически. + 4. Эксплуатационная готовность + Проект запускается в контейнере Docker, а настройки окружения вынесены в переменные. Это упрощает перенос между стендами и делает демонстрацию предсказуемой. + Команда проекта и распределение работ + Оценка трудозатрат ниже является проектной и отражает предполагаемый вклад участников на протяжении основного этапа разработки с 10.02.2026 по 14.03.2026. + Комин Архип +Роль: тимлид, инфраструктура, DevOps и backend. +Оценка занятости: 64 часа. +Основные задачи: постановка архитектуры проекта, подготовка Docker-контура, настройка переменных окружения, организация структуры Django-приложения, реализация моделей MembershipPlan, Profile, Subscription, настройка маршрутов и контроль интеграции backend-части с шаблонами. + Токмачев Егор +Роль: full-stack разработчик. +Оценка занятости: 58 часов. +Основные задачи: реализация форм регистрации и входа, связка пользовательских сценариев с бизнес-логикой, доработка личного кабинета, интеграция покупки абонементов, отладка переходов между страницами и проверка корректности пользовательских сообщений. + Стрельников Вадим +Роль: frontend разработчик, дизайнер. +Оценка занятости: 49 часов. +Основные задачи: разработка визуальной концепции сайта, вёрстка главной страницы и экранов авторизации, настройка тёмной темы, адаптация карточек тарифов и тренеров, подбор визуальных акцентов, доработка читаемости и согласованности интерфейса на разных экранах. + Акуев Магомед +Роль: тестировщик, дизайнер. +Оценка занятости: 37 часов. +Основные задачи: ручное тестирование регистрации, входа и кабинета, проверка сценариев покупки абонемента, поиск визуальных и функциональных дефектов, проверка текстов форм и состояний ошибок, участие в финальной полировке элементов интерфейса. + Суммарная оценка трудозатрат команды: 208 часов. + Итоговые экраны системы + Ниже приведены скриншоты реально работающего локального экземпляра проекта. + Главная страница + На главной странице представлены ключевое позиционирование клуба, тарифы, карточки тренеров, адреса филиалов и информационные блоки. + Страница входа + Экран входа минималистичен и сфокусирован на быстрой авторизации пользователя. + Страница регистрации + Форма регистрации включает обязательные пользовательские поля и согласие на обработку персональных данных. + Личный кабинет пользователя + В кабинете отображаются профиль пользователя, срок действия активного абонемента, доступные тарифы и история покупок. + Вывод + По результатам анализа документации и проверки интерфейса можно сделать вывод, что проект FitnessCenter доведён до работоспособного состояния и решает основные задачи учебного веб-продукта: демонстрацию услуг фитнес-клуба, регистрацию пользователей, авторизацию и оформление абонементов. Архитектура проекта остаётся достаточно простой для сопровождения, а Docker-окружение делает запуск и демонстрацию удобными. + Отдельным преимуществом проекта является согласованная работа команды: backend, frontend, дизайн и тестирование разделены по ролям, но при этом объединены в единый пользовательский сценарий. Это делает результат целостным как с технической, так и с визуальной точки зрения. + Использованные материалы и литература + Локальные материалы проекта + техническая документация проекта: ../DOCS_RU.md; + локально запущенный сайт: http://localhost:1337; + шаблон оформления и структуры отчёта: example.docx. + Подобранная литература + Django Software Foundation. Django documentation [Электронный ресурс]. URL: https://docs.djangoproject.com/en/5.1/ (дата обращения: 16.03.2026). + Bootstrap Team. Bootstrap v5.3 Documentation [Электронный ресурс]. URL: https://getbootstrap.com/docs/5.3/ (дата обращения: 16.03.2026). + Fowler M. Patterns of Enterprise Application Architecture. Boston: Addison-Wesley, 2002. + Krug S. Don't Make Me Think, Revisited: A Common Sense Approach to Web Usability. 3rd ed. Berkeley: New Riders, 2014. + Pressman R. S., Maxim B. R. Software Engineering: A Practitioner's Approach. 9th ed. New York: McGraw-Hill, 2019. + Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object-Oriented Software. Boston: Addison-Wesley, 1994. + Sommerville I. Software Engineering. 10th ed. Boston: Pearson, 2015. + Нильсен Я. Веб-дизайн: удобство использования веб-сайтов. М.: Вильямс, 2010. + Купер А., Рейман Р., Кронин Д., Носсел К. Интерфейс. Основы проектирования взаимодействия. 4-е изд. СПб.: Питер, 2017. + Макконнелл С. Совершенный код. 2-е изд. СПб.: Питер, 2019. + diff --git a/!doc/home.png b/!doc/home.png new file mode 100644 index 0000000..c2fca2a Binary files /dev/null and b/!doc/home.png differ diff --git a/!doc/login.png b/!doc/login.png new file mode 100644 index 0000000..6a008bd Binary files /dev/null and b/!doc/login.png differ diff --git a/!doc/register.png b/!doc/register.png new file mode 100644 index 0000000..c922500 Binary files /dev/null and b/!doc/register.png differ diff --git a/DOCS_RU.md b/DOCS_RU.md new file mode 100644 index 0000000..f472ec8 --- /dev/null +++ b/DOCS_RU.md @@ -0,0 +1,91 @@ +# FitnessCenter — эксплуатационная документация + +## Назначение +Одностраничный лендинг и личный кабинет фитнес‑клуба с регистрацией, входом и покупкой абонементов. Рендеринг на сервере (Django), без JSON API. + +## Стек +- Python 3.13, Django 5.1.4 +- Bootstrap 5.3 (CDN), шрифт Manrope, кастомный тёмный UI `static/css/main.css` +- БД по умолчанию: SQLite (`db.sqlite3`); совместимо с Postgres +- Контейнеризация: Dockerfile (python:3.13-slim) + `docker-compose.yml` (порт 1337→8000) + +## Архитектура и файлы +- `gymclub/` — настройки Django, URL‑маршрутизация верхнего уровня. +- `club/` — доменная логика: модели, формы, views, urls, context processor с адресами клубов. +- `templates/` — серверные шаблоны: `base.html`, `home.html`, `register.html`, `login.html`, `dashboard.html`. +- `static/` — стили и логотип, подключаются через `{% static %}`; собранные файлы кладутся в `staticfiles/`. +- `entrypoint.sh` — миграции + `collectstatic` (ошибки игнорируются) и запуск `runserver` на 0.0.0.0:8000. +- `www/` — архивная статическая версия лендинга (не используется приложением). + +## Данные и бизнес‑логика +- `MembershipPlan`: имя, описание, длительность в днях, цена. Начальные тарифы создаются миграцией `0002_default_plans`. +- `Profile`: OneToOne к пользователю, хранит телефон. +- `Subscription`: ссылка на пользователя и план, даты начала/окончания; `is_active` проверяет `end_date` ≥ сегодня. +- Хелпер `Subscription.create_for_plan(user, plan)` рассчитывает дату окончания от текущей даты и создаёт запись — используйте его вместо ручного сохранения. +- Проверка телефона (RegistrationForm): нормализует «8» в «+7», требует 11 цифр формата +7XXXXXXXXXX. +- Парольные валидаторы стандартные, но проверка «простых паролей» отключена (CommonPasswordValidator не подключён). + +## Маршруты (HTML API) +Все пути объявлены в `club/urls.py`, подключены в `gymclub/urls.py`. +- `GET /` — витрина с планами, адресами клубов. +- `GET /register/`, `POST /register/` — регистрация пользователя, автологин, создание Profile. +- `GET /login/`, `POST /login/` — аутентификация через Django AuthenticationForm. +- `GET /logout/` — logout + redirect домой (view использует `logout`, форма не нужна). +- `GET /dashboard/` (требует авторизации) — активный абонемент, история, покупка новых. +- `POST /subscribe//` — покупка плана через `Subscription.create_for_plan`. +- `admin/` — стандартная админка для планов, подписок и профилей. +Все формы защищены CSRF; сообщения пользователю выводятся через Django messages. + +## UI и контент +- Тёмная тема с акцентным цветом `--accent` в `static/css/main.css`; поменяйте переменные в корне файла для ребрендинга. +- Список клубов задаётся константой `CLUBS` в `club/context_processors.py` и доступен в шаблонах как `clubs`. +- Главная страница содержит секции «Абонементы», «Тренеры», «Клубы», «О нас» и форму заявки в футере (пока без обработки). + +## Переменные окружения +Файл примера `.env.example`: +- `DJANGO_DEBUG` (True/False) +- `DJANGO_ALLOWED_HOSTS` (список через запятую, `*` по умолчанию) +- `DJANGO_SECRET_KEY` (смените в проде) +Дополнительно в `settings.py`: язык `ru-ru`, часовой пояс `Europe/Moscow`. + +## Запуск +### В Docker +```bash +cp .env.example .env +docker compose build +docker compose up +# Откройте http://localhost:1337 +``` +Контейнер монтирует текущую директорию, так что правки видны без пересборки. + +### Локально (без Docker) +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +cp .env.example .env # при необходимости отредактируйте +python manage.py migrate +python manage.py runserver +``` + +### Админка и данные +- Создайте суперпользователя: `python manage.py createsuperuser`. +- Начальные планы создаются миграцией; новые можно добавить через /admin/ или фикстуры. + +### Переключение на Postgres (кратко) +1. Добавьте сервис в `docker-compose.yml` или поднимите внешнюю БД. +2. В `gymclub/settings.py` задайте `ENGINE = 'django.db.backends.postgresql'` и параметры `NAME/USER/PASSWORD/HOST/PORT` (лучше через переменные окружения). +3. Выполните миграции заново (`python manage.py migrate`). + +## Хитрости и рекомендации +- Используйте `Subscription.create_for_plan` — он правильно считает `end_date` от текущего дня. +- Телефон хранится в нормализованном формате `+7XXXXXXXXXX`; это можно использовать для SMS‑интеграций. +- Планы отсортированы по цене (`ordering = ['price']`), поэтому вывод на главной будет от дешёвого к дорогому. +- `entrypoint.sh` не падает, если `collectstatic` без настроек S3; удобнее для локалки, но в проде задайте `STATIC_ROOT` и уберите `|| true` при необходимости. +- Блок футера «Позвоните мне» пока статический — если нужна отправка заявки, повесьте action на форму и добавьте view. + +## Полезные пути +- Основной код: `club/views.py`, `club/forms.py`, `club/models.py` +- Стили: `static/css/main.css` +- Шаблоны: `templates/` +- Контейнеризация: `Dockerfile`, `docker-compose.yml`, `entrypoint.sh` diff --git a/README.md b/README.md index c228f82..b1a19f5 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ - Frontend: Bootstrap 5 (CDN), шрифт Manrope (Google Fonts), кастомный CSS `static/css/main.css`. - БД по умолчанию: SQLite (`db.sqlite3`). Настроена смена на Postgres при необходимости. - Docker: `python:3.13-slim`, entrypoint выполняет миграции и стартует `runserver`. +- Встроенный список клубов (Ростов-на-Дону) доступен во всех шаблонах через context processor. ## Маршруты и HTTP‑запросы Все маршруты определены в `club/urls.py` и подключены через `gymclub/urls.py`. @@ -24,12 +25,15 @@ - Все формы используют CSRF‑токен. - Сообщения пользователю выводятся через Django messages (успех/ошибка). - API JSON не реализован; взаимодействие сервер‑рендерингом HTML. +- Проверка паролей упрощена: отключена `CommonPasswordValidator` (часто используемые пароли не блокируются). +- Телефон проверяется на реальный российский номер: формат +7XXXXXXXXXX, сохраняется в профиль. ## Модели данных (`club/models.py`) - `MembershipPlan`: `name`, `description`, `duration_days`, `price`, `created_at`. - `Profile`: `user` (OneToOne), `phone`, `created_at`. - `Subscription`: `user`, `plan`, `start_date`, `end_date`, `created_at`, вычисляемый `is_active`. - Миграция `0002_default_plans` создаёт 3 дефолтных тарифа (1, 3, 12 месяцев). +- Контекстный список клубов в Ростове-на-Дону (5 адресов). ## Формы - `RegistrationForm` (расширенный `UserCreationForm`): username, имя, фамилия, телефон, email, пароль ×2; автосоздаёт `Profile`. diff --git a/club/context_processors.py b/club/context_processors.py new file mode 100644 index 0000000..1780339 --- /dev/null +++ b/club/context_processors.py @@ -0,0 +1,14 @@ +CLUBS = [ + {"name": "Центр на Большой Садовой", "address": "ул. Большая Садовая, 105"}, + {"name": "Западный Арена", "address": "пр-т Стачки, 190"}, + {"name": "Северный Loft", "address": "пр-т Космонавтов, 14/1"}, + {"name": "Левенцовка Energy", "address": "ул. Малиновского, 25Г"}, + {"name": "Береговой RiverFit", "address": "Береговая ул., 15к3"}, +] + + +def clubs_info(request): + """ + Делает список клубов доступным во всех шаблонах (шапка/футер/страницы). + """ + return {"clubs": CLUBS} diff --git a/club/forms.py b/club/forms.py index 087b813..fb2a4d4 100644 --- a/club/forms.py +++ b/club/forms.py @@ -1,3 +1,5 @@ +import re + from django import forms from django.contrib.auth.forms import AuthenticationForm, UserCreationForm from django.contrib.auth.models import User @@ -35,10 +37,21 @@ class RegistrationForm(BootstrapMixin, UserCreationForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._init_bootstrap() + self.fields["phone"].widget.attrs.setdefault("placeholder", "+7XXXXXXXXXX") + self.fields["phone"].widget.attrs.setdefault("inputmode", "tel") self.fields["username"].label = "Логин" for name in ["password1", "password2"]: self.fields[name].widget.attrs["autocomplete"] = "new-password" + def clean_phone(self): + raw = self.cleaned_data.get("phone", "") + digits = "".join(ch for ch in raw if ch.isdigit()) + if digits.startswith("8"): + digits = "7" + digits[1:] + if not digits.startswith("7") or len(digits) != 11: + raise forms.ValidationError("Введите реальный номер в формате +7XXXXXXXXXX") + return f"+{digits}" + def save(self, commit=True): user = super().save(commit=False) user.first_name = self.cleaned_data.get("first_name", "") diff --git a/club/views.py b/club/views.py index 34df23d..6b404bb 100644 --- a/club/views.py +++ b/club/views.py @@ -4,13 +4,14 @@ from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone +from .context_processors import CLUBS from .forms import LoginForm, RegistrationForm from .models import MembershipPlan, Subscription def home(request): plans = MembershipPlan.objects.all() - return render(request, "home.html", {"plans": plans}) + return render(request, "home.html", {"plans": plans, "clubs": CLUBS}) def register_view(request): diff --git a/db.sqlite3 b/db.sqlite3 index 371f6d2..1e3a4c4 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/docker-compose.yml b/docker-compose.yml index 2cf1f07..a3c9262 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: build: . command: /app/entrypoint.sh ports: - - "8000:8000" + - "1337:8000" environment: DJANGO_DEBUG: "True" DJANGO_ALLOWED_HOSTS: "*" diff --git a/gymclub/settings.py b/gymclub/settings.py index bbd2ab2..0fbb040 100644 --- a/gymclub/settings.py +++ b/gymclub/settings.py @@ -40,6 +40,7 @@ TEMPLATES = [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", + "club.context_processors.clubs_info", ], }, }, @@ -58,7 +59,6 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, - {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] diff --git a/static/css/main.css b/static/css/main.css index d96df2c..0a9091d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -44,8 +44,27 @@ a:hover { color: var(--accent-strong); } .table > :not(caption) > * > * { background: transparent; color: var(--text); } .table { --bs-table-border-color: rgba(255,255,255,0.06); } -.form-control, .form-select { background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); color: var(--text); } -.form-control:focus, .form-select:focus { border-color: var(--accent); box-shadow: 0 0 0 0.2rem rgba(124,240,200,0.2); } +.form-control, +.form-select { + background: rgba(255,255,255,0.04); + border: 1px solid rgba(255,255,255,0.08); + color: var(--text); +} +.form-control:focus, +.form-select:focus { + border-color: var(--accent); + box-shadow: 0 0 0 0.2rem rgba(124,240,200,0.2); +} +/* Читаемость выпадашек на всех платформах */ +.form-select, +.form-select option { + color: #0c1014; + background-color: #f8fafc; +} +.footer .form-select { + background-color: #ffffff; + color: #0c1014; +} .alert { border: 1px solid rgba(255,255,255,0.08); } diff --git a/staticfiles/css/main.css b/staticfiles/css/main.css index d96df2c..0a9091d 100644 --- a/staticfiles/css/main.css +++ b/staticfiles/css/main.css @@ -44,8 +44,27 @@ a:hover { color: var(--accent-strong); } .table > :not(caption) > * > * { background: transparent; color: var(--text); } .table { --bs-table-border-color: rgba(255,255,255,0.06); } -.form-control, .form-select { background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); color: var(--text); } -.form-control:focus, .form-select:focus { border-color: var(--accent); box-shadow: 0 0 0 0.2rem rgba(124,240,200,0.2); } +.form-control, +.form-select { + background: rgba(255,255,255,0.04); + border: 1px solid rgba(255,255,255,0.08); + color: var(--text); +} +.form-control:focus, +.form-select:focus { + border-color: var(--accent); + box-shadow: 0 0 0 0.2rem rgba(124,240,200,0.2); +} +/* Читаемость выпадашек на всех платформах */ +.form-select, +.form-select option { + color: #0c1014; + background-color: #f8fafc; +} +.footer .form-select { + background-color: #ffffff; + color: #0c1014; +} .alert { border: 1px solid rgba(255,255,255,0.08); } diff --git a/templates/base.html b/templates/base.html index ac8ddb4..a8242ab 100644 --- a/templates/base.html +++ b/templates/base.html @@ -73,9 +73,9 @@
diff --git a/templates/home.html b/templates/home.html index de601df..52b413f 100644 --- a/templates/home.html +++ b/templates/home.html @@ -10,6 +10,7 @@
@@ -64,15 +65,99 @@
+
+
+

Тренеры

+ Подберём специалиста под вашу цель +
+
+
+
+
+
Алексей Кравцов
+
Силовой тренинг · 8 лет стажа
+

Составит программу на набор силы и массы, следит за техникой, специализируется на жиме и приседе.

+ Записаться +
+
+
+
+
+
+
Мария Иванова
+
Функционал & кардио · 6 лет стажа
+

Работает с похудением и общей выносливостью, включает TRX, HIIT и работу с собственным весом.

+ Записаться +
+
+
+
+
+
+
Игорь Петров
+
Реабилитация · 10 лет стажа
+

Помогает восстановиться после травм, аккуратно возвращает к нагрузкам, сотрудничает с врачами.

+ Записаться +
+
+
+
+
+ +
+
+

Клубы в Ростове-на-Дону

+ Все локации работают 24/7 +
+
+ {% for club in clubs %} +
+
+
+
{{ club.name }}
+
{{ club.address }}
+
Бесплатный первый визит по онлайн-заявке
+
+
+
+ {% endfor %} +
+
+ +
+
+

Записаться на тренировку

+ Первое занятие — бесплатно +
+
+
+
+
+

Зарегистрируйтесь или войдите в личный кабинет, чтобы оформить абонемент и начать тренировки.

+
+ {% if user.is_authenticated %} + Перейти в кабинет + {% else %} + Зарегистрироваться + Уже есть аккаунт + {% endif %} +
+
+
+
+
+
+
-

Команда тренеров

-

Опытные специалисты по силовым, функциональному тренингу и реабилитации. Подберём программу под вашу цель — похудение, набор массы или поддержка здоровья.

+

О нас

+

FitnessCenter — сеть клубов в Ростове-на-Дону с круглосуточным доступом, современной силовой зоной, кардио, залом функционального тренинга и комфортными раздевалками.

    -
  • • Персональные и групповые тренировки
  • -
  • • ДНК-тест и нутрициолог
  • -
  • • Приложение с планом занятий
  • +
  • • 24/7, электронный доступ по QR
  • +
  • • Персоналки, групповые занятия, детская зона
  • +
  • • Душ, полотенца, фитнес-бар с кофе и протеином
  • +
  • • Онлайн-кабинет с абонементами и историей посещений