Checkpoint — Крок 4
Структура файлів після кроку 4
crispy_notes_project/
│
├── requirements.txt ← django-crispy-forms, crispy-bootstrap5, debug-toolbar
│
├── hello_project/
│ ├── settings.py ← CRISPY_TEMPLATE_PACK, context_processors, MESSAGE_TAGS
│ └── urls.py ← accounts/ (login/logout) + hello_app/ + debug_toolbar
│
├── hello_app/
│ ├── models.py ← Note, Notebook, Tag, TodoList, TodoItem,
│ │ ShoppingList, ShopItem, Reminder
│ ├── services.py ← create/update/delete для всіх моделей
│ ├── selectors.py ← get_user_notes, get_user_notebooks, get_user_tags,
│ │ get_todo_lists, get_shopping_lists...
│ ├── context_processors.py ← sidebar_context → notebooks, tags,
│ │ todo_count, shopping_count (+ OperationalError guard)
│ ├── forms.py ← ★ NoteForm, NotebookForm, TagForm,
│ │ TodoListForm, TodoItemForm (form_tag=False),
│ │ ShoppingListForm, ShopItemForm (form_tag=False),
│ │ ReminderForm, ShareForm
│ ├── views.py ← note CRUD + todo CRUD + shopping CRUD + reminders + register
│ ├── urls.py
│ ├── migrations/
│ │ ├── 0001_initial.py
│ │ └── 0002_shoppinglist_shared_with_todolist_shared_with.py ← M2M shared_with
│ └── templates/hello_app/
│ ├── note_list.html ← extends layouts/dashboard.html
│ ├── note_detail.html ← з inline ReminderForm (form_action у view)
│ ├── note_form.html ← Tier 3: {% crispy form %} ★
│ ├── note_confirm_delete.html
│ ├── notebook_list.html
│ ├── notebook_form.html ← {% crispy form %}
│ ├── tag_form.html ← {% crispy form %}
│ ├── todo_list.html ← мої + спільні списки справ
│ ├── todo_detail.html ← inline TodoItemForm (form_tag=False)
│ ├── todo_form.html ← {% crispy form %}
│ ├── todo_confirm_delete.html
│ ├── shopping_list.html
│ ├── shopping_detail.html ← inline ShopItemForm (form_tag=False)
│ ├── shopping_form.html ← {% crispy form %}
│ ├── shopping_confirm_delete.html
│ └── share_form.html ← ShareForm (додати юзера до shared_with)
│
└── templates/ ← ГЛОБАЛЬНІ шаблони (DIRS в settings)
├── base.html ← Рівень 1: HTML5 + Bootstrap CDN
├── layouts/
│ └── dashboard.html ← Рівень 2: Sidebar (НОТАТКИ/ЗАДАЧІ/ТЕГИ) + Topbar
├── registration/
│ ├── login.html ← Bootstrap card, посилання на register
│ └── register.html ← реєстрація нового користувача
└── components/
├── pagination.html ← Bootstrap pagination
├── empty_state.html ← "немає даних" placeholder
└── confirm_modal.html ← Bootstrap modal для delete
Чеклист самоперевірки
- [ ] 3 рівні:
base.html → layouts/dashboard.html → page template
- [ ]
{% block body %} у base.html, {% block content %} у dashboard.html
- [ ] Context Processor зареєстрований у
settings.py → context_processors
- [ ]
sidebar_notebooks і sidebar_tags доступні у шаблоні без явної передачі
- [ ]
{% crispy form %} рендерить форму з Bootstrap-класами
- [ ]
FormHelper.layout описує структуру форми Python-кодом
- [ ] Active state через
request.resolver_match.url_name
- [ ] Logout — POST форма (не GET посилання) для захисту від CSRF
- [ ]
DIRS = [BASE_DIR / 'templates'] у TEMPLATES — для глобальних шаблонів
- [ ]
form_tag = False для inline-форм (TodoItemForm, ShopItemForm)
- [ ]
try/except OperationalError у context processor
- [ ]
MESSAGE_TAGS налаштований у settings.py
Типові помилки
| Помилка |
Причина |
Рішення |
TemplateDoesNotExist: base.html |
base.html не там де Django шукає |
Файл має бути в templates/base.html (з DIRS) або в hello_app/templates/base.html |
NoReverseMatch: 'note_create' |
URL name не зареєстровано або не той namespace |
Перевір hello_app/urls.py і app_name = 'hello_app' |
403 Forbidden після POST |
Відсутній {% csrf_token %} у формі |
Додай {% csrf_token %} всередину <form> |
| Crispy не застосовує Bootstrap стилі |
Не вказано CRISPY_TEMPLATE_PACK |
Додай CRISPY_TEMPLATE_PACK = 'bootstrap5' у settings.py |
| Sidebar порожній |
Context Processor не зареєстрований |
Перевір TEMPLATES OPTIONS context_processors |
AppRegistryNotReady |
Неправильний порядок імпортів |
Перевір INSTALLED_APPS — crispy до hello_app |
| Active state не спрацьовує |
request не в context |
Додай django.template.context_processors.request |
| Debug Toolbar не з'являється |
Не виконано умови |
Перевір DEBUG=True, INTERNAL_IPS, <body> тег |
| Cards різної висоти |
Немає h-100 на .card |
Додай h-100 до <div class="card"> |
| Футер посередині сторінки |
Відсутні Sticky Footer класи |
Додай d-flex flex-column min-vh-100 на <body> |
Практичне завдання
- Відкрий
hello_app/templates/hello_app/note_list.html → templates/layouts/dashboard.html → templates/base.html. Відслідкуй ланцюг {% extends %}.
- Додай новий пункт у Sidebar для "Архів" (
/notes/?archived=1).
- Перепиши одну форму з ручного Bootstrap HTML на Crispy FormHelper + Layout.
- Перевір active state: натискаючи різні пункти меню — підсвітка переміщається.
- Додай Context Processor що передає кількість нових нотаток за сьогодні.
Де читати теорію — швидкий довідник
| Концепція |
Де у коді |
| Template Inheritance |
base.html → layouts/dashboard.html → note_list.html |
{% block %} |
{% block content %}{% endblock %} у батьку + {% block content %}...{% endblock %} у дочірньому |
{{ block.super }} |
Додає вміст батьківського блоку замість заміни |
| FormHelper |
self.helper = FormHelper() у forms.py.__init__ |
| Layout |
Layout(Fieldset(...), Row(Column(...)), Submit(...)) |
{% crispy form %} |
У шаблоні — 1 рядок замість 90 |
| Context Processor |
hello_app/context_processors.py → sidebar_context(request) |
| Реєстрація процесора |
settings.py TEMPLATES OPTIONS context_processors |
| Active nav |
request.resolver_match.url_name == 'note_list' |
| Static files |
{% load static %}{% static 'hello_app/css/custom.css' %} |
| form_tag = False |
forms.py — TodoItemForm, ShopItemForm (inline forms) |
| OperationalError guard |
context_processors.py — try/except для нових таблиць |
| M2M shared_with |
models.py TodoList.shared_with + міграція 0002 |
| Django Messages |
dashboard.html блок {% if messages %} + settings.py MESSAGE_TAGS |
| Components |
templates/components/ + {% include %} у сторінках |
Навігація