1380 lines
49 KiB
Markdown
1380 lines
49 KiB
Markdown
---
|
||
created: 2026-06-01
|
||
updated: 2026-06-01
|
||
---
|
||
|
||
# Экзамен — Разработка веб-ориентированных информационных систем
|
||
---
|
||
|
||
## Блок 1. Общие вопросы
|
||
|
||
### Модель OSI
|
||
|
||
**Модель OSI (Open Systems Interconnection)** — теоретическая база, определяющая протоколы, равномерно распределяя их на 7 логических уровней.
|
||
|
||
_Протокол_ — набор правил, определяющий, как происходит обмен данными.
|
||
|
||
| Уровень | Название | Что делает | Устройства/Протоколы |
|
||
|---|---|---|---|
|
||
| 1 | Физический (Physical) | Передаёт физические сигналы (токи, свет, радио) | Хабы, репитеры, Ethernet (L1) |
|
||
| 2 | Канальный (Data Link) | Проверяет ошибки; оперирует кадрами (frame) и MAC-адресами | Коммутаторы, Ethernet (L1+L2) |
|
||
| 3 | Сетевой (Network) | Маршрутизация; оперирует пакетами и IP-адресами; ARP | Маршрутизаторы |
|
||
| 4 | Транспортный (Transport) | Надёжная передача; TCP / UDP | — |
|
||
| 5 | Сеансовый (Session) | Управляет сессиями | — |
|
||
| 6 | Представления (Presentation) | Кодирование, сжатие (JPEG, GIF) | — |
|
||
| 7 | Прикладной (Application) | Сетевые службы: HTTP(S), SMTP, FTP | — |
|
||
|
||
**Инкапсуляция** — добавление служебной информации при движении вниз по уровням; **деинкапсуляция** — обратный процесс.
|
||
|
||
**MAC-адрес** — уникальный идентификатор устройства на канальном уровне.
|
||
|
||
**ARP (Address Resolution Protocol)** — определяет MAC-адрес по IP-адресу в локальной сети.
|
||
|
||
---
|
||
|
||
### IPv4, маска подсети, подсети (VLSM)
|
||
|
||
**IP-адрес** имеет две части: адрес сети и адрес узла.
|
||
|
||
**Маска сети** — 32-битное число из подряд идущих единиц. Примеры:
|
||
- Класс A → 255.0.0.0 (`/8`)
|
||
- Класс B → 255.255.0.0 (`/16`)
|
||
- Класс C → 255.255.255.0 (`/24`)
|
||
|
||
Современный стандарт — **CIDR** (бесклассовая адресация).
|
||
|
||
**Диапазоны приватных сетей:**
|
||
1. 10.0.0.0 – 10.255.255.255
|
||
2. 172.16.0.0 – 172.31.255.255
|
||
3. 192.168.0.0 – 192.168.255.255
|
||
|
||
Специальные адреса:
|
||
- `0.0.0.0` — используется DHCP при первом запросе.
|
||
- `255.255.255.255` — широковещательный (broadcast).
|
||
- `127.*.*.*` — loopback (обращение к самому себе; **localhost** = `127.0.0.1`).
|
||
|
||
**Количество хостов в подсети:**
|
||
$$H = 2^{n} - 2$$
|
||
где $n$ — число бит под номер хоста.
|
||
|
||
**Количество подсетей:**
|
||
$$N = 2^{n}$$
|
||
где $n$ — число бит, заимствованных из хостовой части.
|
||
|
||
**VLSM (Variable Length Subnet Mask)** — маска переменной длины. Позволяет делить пространство на подсети разного размера, чтобы никто не перехватил трафик и каждому устройству достался адрес.
|
||
|
||
Запись: `192.168.25.0/24`, где 24 — число единиц в маске (= 255.255.255.0).
|
||
|
||
Пример: `192.168.1.0/24` — 254 хоста, адрес сети `192.168.1.0`, широковещательный `192.168.1.255`.
|
||
|
||
**Выделение адресов:** IANA → Региональные регистраторы (RIPE для Европы) → Национальные регистраторы → Провайдеры.
|
||
|
||
---
|
||
|
||
### IPv6
|
||
|
||
**Причина появления:** IPv4 даёт ~4 млрд адресов — их не хватает. IPv6 использует 128 бит.
|
||
|
||
$$340\ 282\ 366\ 920\ 938\ 463\ 463\ 374\ 607\ 431\ 768\ 211\ 456 \text{ адресов}$$
|
||
|
||
**Формат:** 8 групп по 4 шестнадцатеричных цифры:
|
||
```
|
||
2041:0000:140F:0000:0000:0000:875B:131B
|
||
```
|
||
|
||
Правила сокращения:
|
||
1. Несколько блоков нулей подряд → `::` (только один раз в адресе).
|
||
2. Одиночный блок нулей → `0`.
|
||
3. Ведущие нули в блоке можно опустить.
|
||
|
||
Пример: `2041:0000:140F:0000:0000:0000:875B:131B` → `2041:0:140F::875B:131B`
|
||
|
||
**Отличия от IPv4:**
|
||
- OSPF v2 → OSPFv3; ICMP → ICMPv6; ARP → NDP (Neighbor Discovery Protocol).
|
||
- RIP → RIPng; BGP → MP-BGP; EIGRP поддерживает оба.
|
||
- **Двойной стек** — одновременная работа IPv4 и IPv6.
|
||
|
||
**Типы адресов:**
|
||
- **Global Unicast** — публичные, аналог белых IPv4.
|
||
- **Unique Local** (префикс `FD`) — аналог приватных IPv4.
|
||
- **Link-Local** (префикс `FE80`) — для связи внутри сегмента.
|
||
- **Multicast** (префикс `FF`).
|
||
|
||
**Подсети в IPv6:** стандартный префикс подсети — `/64`. Первые 64 бита — префикс сети, последние 64 — идентификатор интерфейса. Число подсетей при `/48` от провайдера: 2^16 ≈ 65 536.
|
||
|
||
**Получение префиксов:** IANA → Региональные реестры → ISP → Клиент.
|
||
|
||
---
|
||
|
||
### NAT (Network Address Translation)
|
||
|
||
**Проблема:** нехватка публичных IPv4. **Решение:** NAT переводит приватные адреса в публичные.
|
||
|
||
**Термины:**
|
||
- **Inside Local** — приватный адрес устройства внутри сети.
|
||
- **Inside Global** — публичный адрес, под которым виден пакет снаружи.
|
||
- **Outside Local** — адрес внешнего устройства во внутренней топологии.
|
||
- **Outside Global** — реальный адрес внешнего устройства.
|
||
|
||
**Типы NAT:**
|
||
|
||
| Тип | Суть |
|
||
|---|---|
|
||
| Static NAT | 1:1 — один внутренний ↔ один внешний (для серверов) |
|
||
| Dynamic NAT | Пул внутренних ↔ пул внешних (автоматически) |
|
||
| PAT (NAT Overload) | Многие внутренние → один внешний + уникальные порты (домашние роутеры) |
|
||
|
||
**PAT:** использует номер порта TCP/UDP для разделения сессий. ICMPv4 использует Query ID вместо порта.
|
||
|
||
**Stub-сеть** — тупиковая сеть, весь трафик которой выходит через один маршрутизатор.
|
||
|
||
| Преимущества NAT | Недостатки NAT |
|
||
|---|---|
|
||
| Экономия публичных IPv4 | Увеличение задержки |
|
||
| Конфиденциальность топологии | Проблемы с IPsec, входящими соединениями |
|
||
| Независимость от провайдера | Потеря сквозной трассировки |
|
||
|
||
---
|
||
|
||
### TCP и UDP
|
||
|
||
Основные протоколы транспортного уровня (L4).
|
||
|
||
**TCP (Transmission Control Protocol) — RFC 793:**
|
||
- Устанавливает соединение трёхсторонним рукопожатием:
|
||
1. **SYN** — запрос на соединение от отправителя.
|
||
2. **SYN-ACK** — подтверждение от получателя.
|
||
3. **ACK** — начало передачи.
|
||
- После каждой порции данных получатель отправляет подтверждение.
|
||
- **Надёжный, но медленный.** Используется там, где важна точность: пароли, транзакции, почта.
|
||
|
||
**UDP (User Datagram Protocol):**
|
||
- Просто отправляет пакеты без установления соединения.
|
||
- Может теряться часть данных или нарушаться порядок.
|
||
- **Быстрый, но ненадёжный.** Используется там, где важна скорость: видеозвонки, стриминг, игры.
|
||
|
||
---
|
||
|
||
### DNS (Domain Name System)
|
||
|
||
**DNS** — система доменных имён. Аналог: контактная книга (ФИО → номер телефона → домен → IP).
|
||
|
||
**Цепочка разрешения:**
|
||
1. Браузер → **кэш браузера**.
|
||
2. → Локальный DNS (файл `hosts`).
|
||
3. → **Resolver** (обычно сервер провайдера или 8.8.8.8).
|
||
4. → **Root server** (отвечает за `.`).
|
||
5. → **TLD server** (Top Level Domain):
|
||
- gTLD: `.com`, `.org`, `.edu`
|
||
- ccTLD: `.ru`, `.ua`, `.by`, `.us`
|
||
6. → **Authoritative nameserver** — знает конкретный IP.
|
||
|
||
**Виды запросов:**
|
||
1. **Recursive** — запрос IP по домену (resolver сам обходит всю цепочку).
|
||
2. **Iterative** — запрос IP или следующего сервера.
|
||
3. **Inverse** — запрос домена по IP.
|
||
|
||
---
|
||
|
||
### Localhost и порты
|
||
|
||
**localhost** — стандартное доменное имя для loopback-адреса `127.0.0.1` (IPv4) или `::1` (IPv6). Означает "этот же компьютер".
|
||
|
||
**Стандартные порты:**
|
||
|
||
| Протокол | Порт |
|
||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- |
|
||
| HTTP | 80 |
|
||
| HTTPS | 443 |
|
||
| FTP | 21 |
|
||
| SSH | 22 |
|
||
| DNS | 53 |
|
||
| SMTP | 25 |
|
||
|
||
---
|
||
|
||
### HTTP / HTTPS / HTTP/3
|
||
|
||
**HTTP (HyperText Transfer Protocol)** — протокол прикладного уровня (L7) для передачи данных в вебе.
|
||
|
||
**Структура HTTP-запроса:**
|
||
```
|
||
METHOD /path HTTP/1.1
|
||
Host: example.com
|
||
Content-Type: application/json
|
||
|
||
[тело запроса]
|
||
```
|
||
|
||
**Основные методы:**
|
||
|
||
| Метод | Назначение |
|
||
| ------ | -------------------------- |
|
||
| GET | Получить ресурс |
|
||
| POST | Создать / отправить данные |
|
||
| PUT | Заменить ресурс целиком |
|
||
| PATCH | Частично обновить |
|
||
| DELETE | Удалить |
|
||
|
||
**Структура HTTP-ответа:**
|
||
```
|
||
HTTP/1.1 200 OK
|
||
Content-Type: text/html
|
||
|
||
[тело ответа]
|
||
```
|
||
|
||
**Коды ответов:**
|
||
|
||
| Диапазон | Смысл | Примеры |
|
||
| -------- | --------------- | --------------------------------------------------------------- |
|
||
| 1xx | Информационные | 100 Continue |
|
||
| 2xx | Успех | 200 OK, 201 Created, 204 No Content |
|
||
| 3xx | Перенаправление | 301 Moved Permanently, 302 Found |
|
||
| 4xx | Ошибка клиента | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found |
|
||
| 5xx | Ошибка сервера | 500 Internal Server Error, 503 Service Unavailable |
|
||
|
||
**HTTPS** — HTTP поверх TLS/SSL. Шифрует передачу данных; порт 443. Сертификат подтверждает подлинность сервера.
|
||
|
||
**HTTP/2:** мультиплексирование запросов в одном TCP-соединении, сжатие заголовков, server push.
|
||
|
||
**HTTP/3:** работает поверх QUIC (UDP вместо TCP), встроенное шифрование TLS 1.3, устраняет проблему блокировки заголовка (head-of-line blocking).
|
||
|
||
---
|
||
|
||
### URI, URL, URN
|
||
|
||
- **URI (Uniform Resource Identifier)** — общее понятие; любой идентификатор ресурса.
|
||
- **URL (Uniform Resource Locator)** — URI с указанием местонахождения и протокола доступа.
|
||
- **URN (Uniform Resource Name)** — URI без указания местонахождения; постоянное имя.
|
||
|
||
```
|
||
URL: https://example.com:8080/path/page?q=test#section
|
||
схема хост порт путь запрос якорь
|
||
|
||
URN: urn:isbn:978-5-17-084585-3
|
||
```
|
||
|
||
URL ⊆ URI; URN ⊆ URI.
|
||
|
||
---
|
||
|
||
### Идентификация, аутентификация, авторизация
|
||
|
||
- **Идентификация** — присвоение субъектам уникальных идентификаторов (логин, email).
|
||
- **Аутентификация** — проверка подлинности: "ты действительно тот, кем представляешься?" (пароль, токен, биометрия).
|
||
- **Авторизация** — проверка прав: "что тебе разрешено делать?" (роли, разрешения).
|
||
|
||
**Модель CIA:**
|
||
- **Конфиденциальность** — данные недоступны неавторизованным.
|
||
- **Целостность** — данные не искажены.
|
||
- **Доступность** — система доступна легитимным пользователям.
|
||
|
||
**Принципы защиты:**
|
||
- Простота (меньше поверхность атаки).
|
||
- Безопасные настройки по умолчанию (запрещено, если не разрешено явно).
|
||
- Минимизация полномочий.
|
||
- Разделение ответственности.
|
||
|
||
**Уязвимость → Угроза → Атака:**
|
||
- **Уязвимость** — слабое место (ошибка в коде, слабый пароль, неверные права).
|
||
- **Угроза** — потенциальное нарушение безопасности.
|
||
- **Атака** — реализованная угроза.
|
||
|
||
---
|
||
|
||
### OAuth 2.0
|
||
|
||
**OAuth 2.0** — протокол авторизации, позволяющий приложению получить ограниченный доступ к ресурсам пользователя на другом сервисе без передачи пароля.
|
||
|
||
**Роли:**
|
||
- **Resource Owner** — пользователь, владелец данных.
|
||
- **Client** — приложение, запрашивающее доступ.
|
||
- **Authorization Server** — выдаёт токены (например, Google, GitHub).
|
||
- **Resource Server** — хранит защищённые данные.
|
||
|
||
**Базовый flow (Authorization Code):**
|
||
1. Client перенаправляет пользователя на Authorization Server.
|
||
2. Пользователь даёт согласие.
|
||
3. Authorization Server возвращает **code** на redirect_uri клиента.
|
||
4. Client обменивает code на **access_token** (и **refresh_token**).
|
||
5. Client обращается к Resource Server с access_token.
|
||
|
||
**Access token** — краткосрочный, используется для запросов.
|
||
**Refresh token** — долгосрочный, используется для получения нового access_token.
|
||
|
||
---
|
||
|
||
## Блок 2–3. HTML
|
||
|
||
**HTML (Hypertext Markup Language)** — язык разметки для структурирования веб-страниц.
|
||
|
||
### Структура документа
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Заголовок страницы</title>
|
||
</head>
|
||
<body>
|
||
<!-- Контент страницы -->
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
- `<!DOCTYPE html>` — объявление типа документа (HTML5).
|
||
- `<head>` — метаданные: не отображаются в браузере.
|
||
- `<body>` — основное содержимое.
|
||
- `<!-- ... -->` — комментарий.
|
||
|
||
**Метатег viewport** — критичен для мобильных устройств:
|
||
```html
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
```
|
||
|
||
---
|
||
|
||
### Нормальный поток. Блочные и строчные элементы
|
||
|
||
**Нормальный поток** — стандартное поведение: блочные элементы идут сверху вниз, строчные — слева направо внутри строки.
|
||
|
||
- **Блочные элементы** занимают всю доступную ширину и начинаются с новой строки: `<div>`, `<p>`, `<h1>`–`<h6>`, `<section>`, `<form>`.
|
||
- **Строчные элементы** находятся внутри строки и не ломают поток: `<span>`, `<a>`, `<strong>`, `<em>`, `<code>`.
|
||
|
||
---
|
||
|
||
### Семантические теги
|
||
|
||
Семантика — использование тегов по смыслу.
|
||
|
||
| Тег | Назначение |
|
||
|---|---|
|
||
| `<header>` | Верхняя часть страницы или секции |
|
||
| `<nav>` | Навигационные ссылки |
|
||
| `<main>` | Основной контент (один на страницу) |
|
||
| `<section>` | Смысловой раздел |
|
||
| `<article>` | Самостоятельный материал |
|
||
| `<aside>` | Дополнительный контент, боковая панель |
|
||
| `<footer>` | Нижняя часть страницы или секции |
|
||
|
||
`<div>` — нейтральный контейнер без семыслового значения.
|
||
|
||
---
|
||
|
||
### Основные теги
|
||
|
||
**Атрибуты:**
|
||
- `href` — адрес ссылки.
|
||
- `src` — путь к изображению/файлу.
|
||
- `alt` — текстовое описание изображения.
|
||
- `class` — CSS-класс.
|
||
- `id` — уникальный идентификатор.
|
||
- `name` — имя поля формы (отправляется на сервер).
|
||
|
||
**Текст и форматирование:**
|
||
- Заголовки: `<h1>`–`<h6>`.
|
||
- Абзац: `<p>`.
|
||
- Перенос строки: `<br>`.
|
||
- Выделение: `<strong>` (жирный), `<em>` (курсив), `<code>` (код).
|
||
- Пробелы: множественные пробелы схлопываются в один; ` ` — неразрывный пробел.
|
||
|
||
**Списки:**
|
||
- Маркированный: `<ul>` + `<li>`.
|
||
- Нумерованный: `<ol>` + `<li>`.
|
||
|
||
**Ссылки и изображения:**
|
||
```html
|
||
<a href="https://example.com" target="_blank">Текст</a>
|
||
<img src="cat.jpg" alt="Кот" width="500">
|
||
```
|
||
|
||
---
|
||
|
||
### Таблицы
|
||
|
||
Для структурированных данных. Не для вёрстки макета.
|
||
|
||
```html
|
||
<table>
|
||
<caption>Заголовок таблицы</caption>
|
||
<thead>
|
||
<tr>
|
||
<th>Столбец 1</th>
|
||
<th>Столбец 2</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Данные 1</td>
|
||
<td>Данные 2</td>
|
||
</tr>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td>Итог 1</td>
|
||
<td>Итог 2</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
```
|
||
|
||
- `colspan` — объединение ячеек по горизонтали.
|
||
- `rowspan` — объединение ячеек по вертикали.
|
||
|
||
---
|
||
|
||
### Формы
|
||
|
||
```html
|
||
<form action="/handler-url/" method="post">
|
||
<label for="username">Имя:</label>
|
||
<input type="text" id="username" name="name" placeholder="Введите имя" required>
|
||
|
||
<button type="submit">Отправить</button>
|
||
</form>
|
||
```
|
||
|
||
**Атрибуты `<form>`:**
|
||
- `action` — URL обработчика.
|
||
- `method` — `get` или `post`.
|
||
|
||
**Элементы управления:**
|
||
- `<input>` — универсальное поле ввода.
|
||
- `<select>` + `<option>` — выпадающий список.
|
||
- `<textarea>` — многострочное поле.
|
||
- `<button>` — кнопка (`type="submit"`, `type="reset"`, `type="button"`).
|
||
- `<fieldset>` + `<legend>` — группировка полей.
|
||
- `<label for="id">` — подпись к полю (клик по label фокусирует input).
|
||
|
||
**Типы `<input>`:**
|
||
|
||
| `type` | Назначение |
|
||
| ---------- | ------------------------------------------ |
|
||
| `text` | Обычная строка |
|
||
| `password` | Пароль (символы скрываются) |
|
||
| `email` | Email с базовой проверкой формата |
|
||
| `number` | Числовой ввод |
|
||
| `checkbox` | Флажок (можно выбрать несколько) |
|
||
| `radio` | Выбор одного из группы (одинаковый `name`) |
|
||
| `file` | Загрузка файла |
|
||
| `date` | Выбор даты |
|
||
| `hidden` | Скрытое поле |
|
||
| `submit` | Кнопка отправки |
|
||
|
||
```html
|
||
<fieldset>
|
||
<legend>Способ связи</legend>
|
||
<label>
|
||
<input type="radio" name="contact" value="email"> Email
|
||
</label>
|
||
<label>
|
||
<input type="radio" name="contact" value="phone"> Телефон
|
||
</label>
|
||
</fieldset>
|
||
|
||
<label>
|
||
<input type="checkbox" name="agreement" required>
|
||
Согласен с условиями
|
||
</label>
|
||
|
||
<label for="comment">Комментарий:</label>
|
||
<textarea id="comment" name="comment" rows="5"></textarea>
|
||
```
|
||
|
||
**Атрибуты валидации:**
|
||
- `required` — поле обязательно.
|
||
- `minlength` / `maxlength` — длина строки.
|
||
- `min` / `max` — диапазон числа или даты.
|
||
- `pattern` — регулярное выражение.
|
||
|
||
HTML-валидация — на стороне браузера. Не заменяет проверку на сервере.
|
||
|
||
---
|
||
|
||
## Блок 4. CSS: Селекторы и блочная модель
|
||
|
||
**CSS (Cascading Style Sheets)** — язык таблиц стилей для оформления HTML.
|
||
|
||
### Подключение
|
||
|
||
```html
|
||
<!-- Внешняя таблица (предпочтительно) -->
|
||
<link rel="stylesheet" href="style.css">
|
||
|
||
<!-- Внутри <head> -->
|
||
<style> p { color: red; } </style>
|
||
|
||
<!-- Inline (наивысший приоритет, но засоряет HTML) -->
|
||
<p style="color: red;">Текст</p>
|
||
```
|
||
|
||
---
|
||
|
||
### Селекторы
|
||
|
||
| Селектор | Пример | Описание |
|
||
|---|---|---|
|
||
| Универсальный | `*` | Все элементы |
|
||
| По тегу | `h1`, `p` | Конкретный тег |
|
||
| По классу | `.card` | Элементы с классом |
|
||
| По ID | `#header` | Элемент с id |
|
||
| Потомок | `div p` | Все `p` внутри `div` |
|
||
| Дочерний | `div > p` | Прямые наследники |
|
||
| Соседний | `h1 + p` | Первый `p` сразу после `h1` |
|
||
| По атрибуту | `a[href^="https"]` | По значению атрибута |
|
||
|
||
**Группировка:** `h1, h2, p { ... }`
|
||
|
||
**Псевдоклассы:**
|
||
- `:hover`, `:focus`, `:active`.
|
||
- `:first-child`, `:last-child`, `:nth-child(2n)`, `:nth-child(odd)`.
|
||
|
||
**Псевдоэлементы:**
|
||
- `::before`, `::after` — вставляют контент до/после (используют `content`).
|
||
|
||
---
|
||
|
||
### Наследование и каскад
|
||
|
||
Наследование: некоторые свойства (`color`, `font-*`) передаются от родителя к потомкам.
|
||
|
||
**Специфичность (кто побеждает при конфликте):**
|
||
|
||
| Тип | Вес |
|
||
|---|---|
|
||
| Inline-стили | 1,0,0,0 |
|
||
| ID (`#id`) | 0,1,0,0 |
|
||
| Класс, атрибут, псевдокласс | 0,0,1,0 |
|
||
| Тег, псевдоэлемент | 0,0,0,1 |
|
||
|
||
При равной специфичности побеждает последнее объявление.
|
||
|
||
---
|
||
|
||
### Блочная модель (Box Model)
|
||
|
||
Каждый элемент — прямоугольный блок:
|
||
|
||
```
|
||
┌──────────────────────────────┐
|
||
│ margin │
|
||
│ ┌────────────────────────┐ │
|
||
│ │ border │ │
|
||
│ │ ┌──────────────────┐ │ │
|
||
│ │ │ padding │ │ │
|
||
│ │ │ ┌────────────┐ │ │ │
|
||
│ │ │ │ content │ │ │ │
|
||
│ │ │ └────────────┘ │ │ │
|
||
│ │ └──────────────────┘ │ │
|
||
│ └────────────────────────┘ │
|
||
└──────────────────────────────┘
|
||
```
|
||
|
||
- **Content** — содержимое.
|
||
- **Padding** — внутренние поля.
|
||
- **Border** — рамка.
|
||
- **Margin** — внешние отступы.
|
||
|
||
**`box-sizing`:**
|
||
- `content-box` (по умолчанию): `width` = ширина только контента.
|
||
- `border-box`: `width` включает `padding` и `border`.
|
||
|
||
---
|
||
|
||
### Свойство display
|
||
|
||
- `block` — занимает всю ширину, с новой строки.
|
||
- `inline` — по контенту, нельзя задать `width`/`height`.
|
||
- `inline-block` — строчный, но с размерами.
|
||
- `none` — убирает элемент из документа.
|
||
|
||
---
|
||
|
||
### Базовые свойства
|
||
|
||
**Текст и шрифты:**
|
||
```css
|
||
font-family: Arial, sans-serif;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
text-transform: uppercase;
|
||
text-decoration: underline;
|
||
line-height: 1.5;
|
||
letter-spacing: 2px;
|
||
```
|
||
|
||
**Тени текста:**
|
||
```css
|
||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||
```
|
||
|
||
**Фон:**
|
||
```css
|
||
background-color: #fff;
|
||
background-image: url("bg.jpg");
|
||
background-size: cover;
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
```
|
||
|
||
**Границы и тени блока:**
|
||
```css
|
||
border: 1px solid black;
|
||
border-radius: 8px;
|
||
box-shadow: 2px 4px 8px rgba(0,0,0,0.2);
|
||
```
|
||
|
||
**Выравнивание блоков:**
|
||
- `margin: 0 auto;` — горизонтальное центрирование блока с заданной шириной.
|
||
|
||
---
|
||
|
||
## Блок 5. CSS: Адаптивность, Flexbox и Grid
|
||
|
||
### Медиазапросы (@media)
|
||
|
||
```css
|
||
/* Desktop First: стили для телефонов */
|
||
@media screen and (max-width: 768px) {
|
||
.container { flex-direction: column; }
|
||
}
|
||
|
||
/* Mobile First: стили для планшетов и выше */
|
||
@media screen and (min-width: 768px) {
|
||
.container { display: flex; }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Flexbox
|
||
|
||
**Одномерная** модель раскладки (по одной оси).
|
||
|
||
```css
|
||
.container {
|
||
display: flex; /* или inline-flex */
|
||
flex-direction: row; /* row | column | row-reverse | column-reverse */
|
||
flex-wrap: wrap; /* wrap | nowrap */
|
||
justify-content: space-between; /* выравнивание по главной оси */
|
||
align-items: center; /* выравнивание по поперечной оси */
|
||
gap: 16px;
|
||
}
|
||
```
|
||
|
||
**Главная ось** — задаётся `flex-direction` (по умолчанию горизонтальная).
|
||
**Поперечная ось** — перпендикулярна главной.
|
||
|
||
`justify-content`: `flex-start` | `flex-end` | `center` | `space-between` | `space-around` | `space-evenly`.
|
||
|
||
`align-items`: `flex-start` | `flex-end` | `center` | `stretch` | `baseline`.
|
||
|
||
**Свойства элементов (flex-items):**
|
||
```css
|
||
.item {
|
||
flex-grow: 1; /* коэффициент растяжения */
|
||
flex-shrink: 1; /* коэффициент сжатия */
|
||
flex-basis: 200px; /* базовый размер до распределения */
|
||
order: 2; /* порядок отображения */
|
||
}
|
||
/* Сокращение: flex: grow shrink basis */
|
||
.item { flex: 1 1 200px; }
|
||
```
|
||
|
||
---
|
||
|
||
### Grid Layout
|
||
|
||
**Двумерная** модель раскладки (строки + столбцы).
|
||
|
||
```css
|
||
.container {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr); /* 3 колонки равной ширины */
|
||
grid-template-rows: auto 1fr;
|
||
gap: 16px;
|
||
}
|
||
```
|
||
|
||
- **`fr`** — доля свободного пространства.
|
||
- **`repeat(n, size)`** — повторить `n` раз.
|
||
- **`minmax(min, max)`** — задать диапазон размера.
|
||
|
||
**Размещение элементов:**
|
||
```css
|
||
.item {
|
||
grid-column: 1 / 3; /* от 1-й до 3-й линии */
|
||
grid-row: 2 / span 2; /* со 2-й линии на 2 ячейки */
|
||
}
|
||
```
|
||
|
||
**Именованные области:**
|
||
```css
|
||
.container {
|
||
grid-template-areas:
|
||
"header header"
|
||
"sidebar content"
|
||
"footer footer";
|
||
}
|
||
header { grid-area: header; }
|
||
.sidebar { grid-area: sidebar; }
|
||
```
|
||
|
||
---
|
||
|
||
### Flex vs Grid
|
||
|
||
- **Flexbox** — одномерные структуры: меню, тулбары, строки карточек.
|
||
- **Grid** — двумерные макеты: раскладки всей страницы, сложные сетки.
|
||
|
||
---
|
||
|
||
## Блок 6. Django: Модели
|
||
|
||
**Django ORM** — связывает Python-код с реляционной БД. Паттерн Active Record.
|
||
|
||
### Структура проекта
|
||
|
||
```text
|
||
project/
|
||
├── manage.py
|
||
├── project/
|
||
│ ├── settings.py # конфигурация (БД, INSTALLED_APPS)
|
||
│ └── urls.py # корневые маршруты
|
||
└── app/
|
||
├── models.py # модели (таблицы)
|
||
├── views.py
|
||
└── urls.py
|
||
```
|
||
|
||
---
|
||
|
||
### Определение моделей
|
||
|
||
```python
|
||
from django.db import models
|
||
|
||
class Person(models.Model):
|
||
first_name = models.CharField("Имя", max_length=30)
|
||
age = models.IntegerField(default=18)
|
||
bio = models.TextField(blank=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Человек"
|
||
ordering = ["-first_name"]
|
||
```
|
||
|
||
**`class Meta`** — метаданные модели: `verbose_name`, `ordering`, `db_table`, `unique_together`.
|
||
|
||
---
|
||
|
||
### Типы полей
|
||
|
||
| Поле | Описание |
|
||
|---|---|
|
||
| `AutoField` | Автоинкрементный первичный ключ (добавляется автоматически) |
|
||
| `CharField(max_length=N)` | Короткая строка |
|
||
| `TextField` | Длинный текст |
|
||
| `IntegerField` | Целое число |
|
||
| `DecimalField(max_digits, decimal_places)` | Точное десятичное (деньги) |
|
||
| `BooleanField` | `True`/`False` |
|
||
| `DateField` | Дата |
|
||
| `DateTimeField` | Дата + время |
|
||
| `EmailField` | Строка с валидацией email |
|
||
| `FileField` | Путь к файлу |
|
||
| `ImageField` | Файл изображения |
|
||
|
||
**`null=True`** — разрешить `NULL` в БД.
|
||
**`blank=True`** — разрешить пустое значение на уровне валидации формы.
|
||
|
||
---
|
||
|
||
### Отношения
|
||
|
||
```python
|
||
class Author(models.Model):
|
||
name = models.CharField(max_length=100)
|
||
|
||
class Profile(models.Model):
|
||
author = models.OneToOneField(Author, on_delete=models.CASCADE)
|
||
bio = models.TextField()
|
||
|
||
class Book(models.Model):
|
||
title = models.CharField(max_length=200)
|
||
author = models.ForeignKey(Author, on_delete=models.PROTECT,
|
||
related_name="books")
|
||
co_authors = models.ManyToManyField(Author,
|
||
related_name="coauthored_books",
|
||
blank=True)
|
||
```
|
||
|
||
- **`ForeignKey`** — "один ко многим": у одного автора много книг.
|
||
- **`OneToOneField`** — "один к одному".
|
||
- **`ManyToManyField`** — "многие ко многим"; Django автоматически создаёт промежуточную таблицу.
|
||
|
||
**`on_delete`:**
|
||
|
||
| Значение | Поведение |
|
||
| ------------- | ------------------------------------------ |
|
||
| `CASCADE` | Удалить текущую запись вместе со связанной |
|
||
| `PROTECT` | Запретить удаление, пока есть ссылки |
|
||
| `SET_NULL` | Записать `NULL` (нужен `null=True`) |
|
||
| `SET_DEFAULT` | Записать значение по умолчанию |
|
||
| `DO_NOTHING` | Ничего не делать |
|
||
|
||
**`related_name`** — имя обратной связи: `author.books.all()`.
|
||
|
||
**Промежуточная модель Many-to-Many** — используется, когда нужно хранить дополнительные данные о связи:
|
||
|
||
```python
|
||
class Membership(models.Model):
|
||
person = models.ForeignKey("Person", on_delete=models.CASCADE)
|
||
group = models.ForeignKey("Group", on_delete=models.CASCADE)
|
||
date_joined = models.DateField()
|
||
role = models.CharField(max_length=50)
|
||
|
||
class Group(models.Model):
|
||
name = models.CharField(max_length=100)
|
||
members = models.ManyToManyField("Person", through="Membership")
|
||
```
|
||
|
||
---
|
||
|
||
### Миграции
|
||
|
||
```bash
|
||
python manage.py makemigrations # создать файлы миграций
|
||
python manage.py migrate # применить к БД
|
||
python manage.py showmigrations # статусы миграций
|
||
```
|
||
|
||
---
|
||
|
||
## Блок 11. Django: QuerySet и ORM-запросы
|
||
|
||
**QuerySet ленив** — запрос к БД выполняется только при обращении к данным (итерация, срез, `list()`, `len()`).
|
||
|
||
### Базовые методы
|
||
|
||
```python
|
||
# Все объекты
|
||
Person.objects.all()
|
||
|
||
# Один объект (исключение если нет или несколько)
|
||
Person.objects.get(id=1)
|
||
|
||
# Фильтрация
|
||
Person.objects.filter(age__gte=18)
|
||
Person.objects.exclude(age__lt=18)
|
||
|
||
# Порядок
|
||
Person.objects.order_by("-age") # минус = DESC
|
||
|
||
# Уникальные значения
|
||
Person.objects.distinct()
|
||
|
||
# Количество
|
||
Person.objects.count()
|
||
|
||
# Проверка существования (не загружает объекты)
|
||
Person.objects.filter(age=18).exists()
|
||
```
|
||
|
||
### Field Lookups (операторы поиска)
|
||
|
||
```python
|
||
# Точное совпадение
|
||
filter(name__exact="Ivan")
|
||
filter(name__iexact="ivan") # без учёта регистра
|
||
|
||
# Сравнение чисел
|
||
filter(age__gt=18) # >
|
||
filter(age__gte=18) # >=
|
||
filter(age__lt=30) # <
|
||
filter(age__lte=30) # <=
|
||
|
||
# Вхождение
|
||
filter(name__contains="van")
|
||
filter(name__icontains="van") # без учёта регистра
|
||
|
||
# Список / диапазон
|
||
filter(id__in=[1, 2, 3])
|
||
filter(age__range=(18, 30))
|
||
|
||
# NULL
|
||
filter(bio__isnull=True)
|
||
```
|
||
|
||
### Агрегация и аннотация
|
||
|
||
```python
|
||
from django.db.models import Avg, Count, Sum, Min, Max
|
||
|
||
# aggregate — одно итоговое значение; возвращает dict
|
||
result = Book.objects.aggregate(avg_rating=Avg("rating"))
|
||
print(result["avg_rating"])
|
||
|
||
# annotate — вычисляемое поле для каждого объекта
|
||
authors = Author.objects.annotate(total_books=Count("books"))
|
||
for a in authors:
|
||
print(a.name, a.total_books)
|
||
```
|
||
|
||
### Q-объекты (сложные условия)
|
||
|
||
```python
|
||
from django.db.models import Q
|
||
|
||
# OR
|
||
books = Book.objects.filter(Q(rating__gt=4) | Q(title__icontains="Django"))
|
||
|
||
# AND
|
||
books = Book.objects.filter(Q(rating__gt=4) & Q(is_published=True))
|
||
|
||
# NOT
|
||
books = Book.objects.filter(~Q(rating__lt=3))
|
||
```
|
||
|
||
### F-выражения (операции в БД)
|
||
|
||
```python
|
||
from django.db.models import F
|
||
|
||
# Увеличить счётчик просмотров прямо в БД, без загрузки в Python
|
||
Book.objects.filter(id=1).update(views=F("views") + 1)
|
||
```
|
||
|
||
### Цепочки методов
|
||
|
||
```python
|
||
queryset = (
|
||
Person.objects
|
||
.filter(age__gte=18)
|
||
.exclude(is_active=False)
|
||
.order_by("first_name")
|
||
.distinct()
|
||
)
|
||
```
|
||
|
||
### Проблема N+1 и оптимизация
|
||
|
||
**N+1** — один запрос за списком + N запросов в цикле на каждый связанный объект.
|
||
|
||
```python
|
||
# Плохо: N+1
|
||
books = Book.objects.all()
|
||
for book in books:
|
||
print(book.author.name) # запрос на каждую итерацию
|
||
|
||
# Хорошо: select_related (JOIN) для ForeignKey/OneToOne
|
||
books = Book.objects.select_related("author")
|
||
|
||
# Хорошо: prefetch_related (отдельный запрос) для ManyToMany / обратные связи
|
||
books = Book.objects.prefetch_related("authors")
|
||
```
|
||
|
||
---
|
||
|
||
## Блок 7–10. Django: Views, URLs, Templates
|
||
|
||
### Маршрутизация (URLs)
|
||
|
||
```python
|
||
# project/urls.py
|
||
from django.urls import include, path
|
||
|
||
urlpatterns = [
|
||
path("blog/", include("blog.urls")),
|
||
]
|
||
```
|
||
|
||
```python
|
||
# blog/urls.py
|
||
from django.urls import path
|
||
from . import views
|
||
from .views import PostListView, PostDetailView, AboutView
|
||
|
||
app_name = "blog"
|
||
|
||
urlpatterns = [
|
||
path("", views.post_list, name="post_list"), # FBV
|
||
path("<int:pk>/", PostDetailView.as_view(), name="post_detail"), # CBV
|
||
path("about/", AboutView.as_view(), name="about"),
|
||
path("tag/<slug:slug>/", views.posts_by_tag, name="posts_by_tag"),
|
||
]
|
||
```
|
||
|
||
**Конвертеры пути:**
|
||
|
||
| Конвертер | Тип | Пример URL |
|
||
| ---------------- | ---------------------- | -------------------- |
|
||
| `<str:name>` | Строка без `/` | `/user/ivan/` |
|
||
| `<int:pk>` | Целое положительное | `/post/42/` |
|
||
| `<slug:slug>` | Буквы, цифры, `_`, `-` | `/tag/django-orm/` |
|
||
| `<uuid:id>` | UUID | `/item/075194d3-...` |
|
||
| `<path:subpath>` | Строка с `/` | `/files/a/b/c.txt` |
|
||
|
||
**`re_path`** — маршрут с регулярным выражением:
|
||
```python
|
||
from django.urls import re_path
|
||
re_path(r"^post/(?P<pk>[0-9]+)/$", views.post_detail)
|
||
```
|
||
|
||
**Реверс URL:**
|
||
```python
|
||
from django.urls import reverse
|
||
url = reverse("blog:post_detail", kwargs={"pk": 1})
|
||
```
|
||
|
||
```html
|
||
{% url 'blog:post_detail' post.pk %}
|
||
```
|
||
|
||
---
|
||
|
||
### Функциональные представления (FBV)
|
||
|
||
```python
|
||
from django.shortcuts import render, get_object_or_404
|
||
|
||
def post_list(request):
|
||
posts = Post.objects.all()[:5]
|
||
return render(request, "blog/list.html", {"posts": posts})
|
||
|
||
def post_detail(request, pk):
|
||
post = get_object_or_404(Post, pk=pk)
|
||
return render(request, "blog/detail.html", {"post": post})
|
||
```
|
||
|
||
- Принимают `HttpRequest`, возвращают `HttpResponse`.
|
||
- `render(request, template, context)` — собирает ответ.
|
||
- `get_object_or_404` — возвращает объект или 404.
|
||
|
||
---
|
||
|
||
### Классовые представления (CBV)
|
||
|
||
Вызываются через `.as_view()`.
|
||
|
||
```python
|
||
from django.views.generic import TemplateView, ListView, DetailView
|
||
|
||
class AboutView(TemplateView):
|
||
template_name = "blog/about.html"
|
||
|
||
|
||
class PostListView(ListView):
|
||
model = Post
|
||
template_name = "blog/list.html"
|
||
context_object_name = "posts" # по умолчанию: object_list
|
||
paginate_by = 10
|
||
|
||
def get_queryset(self):
|
||
return Post.objects.filter(is_published=True).order_by("-created_at")
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
context["extra"] = "доп. данные"
|
||
return context
|
||
|
||
|
||
class PostDetailView(DetailView):
|
||
model = Post
|
||
template_name = "blog/detail.html"
|
||
context_object_name = "post" # по умолчанию: object
|
||
```
|
||
|
||
**Имена переменных по умолчанию:**
|
||
- `ListView` → `object_list`.
|
||
- `DetailView` → `object`.
|
||
|
||
**Пагинация в ListView:**
|
||
```html
|
||
{% if is_paginated %}
|
||
{% if page_obj.has_previous %}
|
||
<a href="?page={{ page_obj.previous_page_number }}">Назад</a>
|
||
{% endif %}
|
||
Страница {{ page_obj.number }} из {{ page_obj.paginator.num_pages }}
|
||
{% if page_obj.has_next %}
|
||
<a href="?page={{ page_obj.next_page_number }}">Вперёд</a>
|
||
{% endif %}
|
||
{% endif %}
|
||
```
|
||
|
||
**Шаблоны по умолчанию (если не задан `template_name`):**
|
||
- `ListView` → `<app>/<model>_list.html`
|
||
- `DetailView` → `<app>/<model>_detail.html`
|
||
|
||
---
|
||
|
||
### Шаблоны (Django Template Language)
|
||
|
||
**Переменные:**
|
||
```html
|
||
{{ post.title }}
|
||
{{ user.get_full_name }}
|
||
```
|
||
|
||
**Теги:**
|
||
```html
|
||
{% if post.is_published %}
|
||
<p>Опубликовано</p>
|
||
{% elif post.is_draft %}
|
||
<p>Черновик</p>
|
||
{% else %}
|
||
<p>Архив</p>
|
||
{% endif %}
|
||
|
||
{% for post in posts %}
|
||
<p>{{ forloop.counter }}. {{ post.title }}</p>
|
||
{% empty %}
|
||
<p>Постов нет.</p>
|
||
{% endfor %}
|
||
```
|
||
|
||
**`forloop`:**
|
||
- `forloop.counter` — счётчик с 1.
|
||
- `forloop.counter0` — счётчик с 0.
|
||
- `forloop.first` / `forloop.last` — первый/последний элемент.
|
||
|
||
**Фильтры:**
|
||
```html
|
||
{{ post.created_at|date:"Y-m-d" }}
|
||
{{ text|lower }}
|
||
{{ text|upper }}
|
||
{{ list|length }}
|
||
{{ value|default:"нет" }}
|
||
{{ price|floatformat:2 }}
|
||
```
|
||
|
||
---
|
||
|
||
### Наследование шаблонов
|
||
|
||
```html
|
||
<!-- templates/base.html -->
|
||
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<title>{% block title %}Сайт{% endblock %}</title>
|
||
{% load static %}
|
||
<link rel="stylesheet" href="{% static 'css/app.css' %}">
|
||
</head>
|
||
<body>
|
||
<nav>...</nav>
|
||
<main>
|
||
{% block content %}{% endblock %}
|
||
</main>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
```html
|
||
<!-- templates/blog/list.html -->
|
||
{% extends "base.html" %}
|
||
|
||
{% block title %}Посты{% endblock %}
|
||
|
||
{% block content %}
|
||
<h1>Посты</h1>
|
||
{% for post in posts %}
|
||
<a href="{% url 'blog:post_detail' post.pk %}">{{ post.title }}</a>
|
||
{% endfor %}
|
||
{% endblock %}
|
||
```
|
||
|
||
**Include шаблонов:**
|
||
```html
|
||
{% include "blog/partials/card.html" %}
|
||
{% include "blog/partials/card.html" with post=item only %}
|
||
```
|
||
|
||
**Статические файлы:**
|
||
```html
|
||
{% load static %}
|
||
<img src="{% static 'images/logo.png' %}" alt="Логотип">
|
||
```
|
||
|
||
**Вызов методов в шаблонах:** Django вызывает методы без аргументов автоматически:
|
||
```html
|
||
{{ user.get_full_name }} {# вызов get_full_name() #}
|
||
{{ post.get_absolute_url }} {# вызов get_absolute_url() #}
|
||
```
|
||
|
||
Методы с аргументами напрямую вызвать нельзя — логику нужно вынести во view или templatetag.
|
||
|
||
---
|
||
|
||
## Блок 12. Django: Формы
|
||
|
||
### forms.Form
|
||
|
||
```python
|
||
from django import forms
|
||
|
||
class ContactForm(forms.Form):
|
||
name = forms.CharField(max_length=100)
|
||
email = forms.EmailField()
|
||
message = forms.CharField(widget=forms.Textarea)
|
||
```
|
||
|
||
---
|
||
|
||
### forms.ModelForm
|
||
|
||
```python
|
||
from django import forms
|
||
from .models import Blog
|
||
|
||
class BlogForm(forms.ModelForm):
|
||
class Meta:
|
||
model = Blog
|
||
fields = ["name", "tagline"]
|
||
widgets = {
|
||
"name": forms.TextInput(attrs={"class": "form-control"})
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Жизненный цикл формы во view
|
||
|
||
```python
|
||
from django.shortcuts import redirect, render
|
||
|
||
def contact_view(request):
|
||
if request.method == "POST":
|
||
form = ContactForm(request.POST)
|
||
if form.is_valid():
|
||
data = form.cleaned_data
|
||
send_email(data["email"], data["message"])
|
||
return redirect("success")
|
||
else:
|
||
form = ContactForm()
|
||
return render(request, "contact.html", {"form": form})
|
||
```
|
||
|
||
1. `GET` — создать пустую форму → показать страницу.
|
||
2. `POST` — создать форму из `request.POST` → проверить → действие + redirect.
|
||
3. Ошибки — снова показать форму с сообщениями.
|
||
|
||
---
|
||
|
||
### Валидация
|
||
|
||
- **`is_valid()`** — запускает проверку; после него появляются `cleaned_data` и `errors`.
|
||
- **`clean_<fieldname>()`** — проверка одного поля.
|
||
- **`clean()`** — проверка формы целиком (сравнение нескольких полей).
|
||
|
||
```python
|
||
class RegisterForm(forms.Form):
|
||
username = forms.CharField(max_length=50)
|
||
password = forms.CharField(widget=forms.PasswordInput)
|
||
password_repeat = forms.CharField(widget=forms.PasswordInput)
|
||
|
||
def clean_username(self):
|
||
username = self.cleaned_data["username"]
|
||
if username.lower() == "admin":
|
||
raise forms.ValidationError("Это имя нельзя использовать")
|
||
return username
|
||
|
||
def clean(self):
|
||
cleaned_data = super().clean()
|
||
password = cleaned_data.get("password")
|
||
password_repeat = cleaned_data.get("password_repeat")
|
||
if password and password_repeat and password != password_repeat:
|
||
raise forms.ValidationError("Пароли не совпадают")
|
||
return cleaned_data
|
||
```
|
||
|
||
---
|
||
|
||
### Виджеты и подсказки
|
||
|
||
```python
|
||
class ProfileForm(forms.ModelForm):
|
||
class Meta:
|
||
model = Profile
|
||
fields = ["bio", "birth_date"]
|
||
widgets = {
|
||
"bio": forms.Textarea(attrs={"rows": 3, "placeholder": "О себе"}),
|
||
"birth_date": forms.DateInput(attrs={"type": "date"}),
|
||
}
|
||
labels = {"bio": "Биография"}
|
||
help_texts = {"bio": "Не более 500 символов."}
|
||
```
|
||
|
||
---
|
||
|
||
### Отображение в шаблоне
|
||
|
||
```html
|
||
<form method="post">
|
||
{% csrf_token %}
|
||
{{ form.as_p }}
|
||
<button type="submit">Сохранить</button>
|
||
</form>
|
||
```
|
||
|
||
```html
|
||
<!-- Ручной контроль -->
|
||
<form method="post">
|
||
{% csrf_token %}
|
||
{% for field in form %}
|
||
<div>
|
||
{{ field.label_tag }}
|
||
{{ field }}
|
||
{% if field.errors %}
|
||
<ul class="errors">
|
||
{% for error in field.errors %}
|
||
<li>{{ error }}</li>
|
||
{% endfor %}
|
||
</ul>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
<button type="submit">Сохранить</button>
|
||
</form>
|
||
```
|
||
|
||
**CSRF-токен** — защита от Cross-Site Request Forgery: `{% csrf_token %}` обязателен во всех `method="post"` формах.
|
||
|
||
---
|
||
|
||
### Generic Editing Views (CBV для CUD)
|
||
|
||
```python
|
||
from django.urls import reverse_lazy
|
||
from django.views.generic import CreateView, UpdateView, DeleteView
|
||
from .models import Blog
|
||
from .forms import BlogForm
|
||
|
||
class BlogCreateView(CreateView):
|
||
model = Blog
|
||
form_class = BlogForm
|
||
template_name = "blog/blog_form.html"
|
||
success_url = reverse_lazy("blog_list")
|
||
|
||
class BlogUpdateView(UpdateView):
|
||
model = Blog
|
||
form_class = BlogForm
|
||
template_name = "blog/blog_form.html"
|
||
success_url = reverse_lazy("blog_list")
|
||
|
||
class BlogDeleteView(DeleteView):
|
||
model = Blog
|
||
template_name = "blog/blog_confirm_delete.html"
|
||
success_url = reverse_lazy("blog_list")
|
||
```
|
||
|
||
```python
|
||
# urls.py
|
||
path("create/", BlogCreateView.as_view(), name="blog_create"),
|
||
path("<int:pk>/edit/", BlogUpdateView.as_view(), name="blog_update"),
|
||
path("<int:pk>/delete/", BlogDeleteView.as_view(), name="blog_delete"),
|
||
```
|