Перейти к содержанию

Подготовка редакционного приложения к эксплуатации

Оглавление курса · Скачать все состояния проекта.

Учебная задача: Зафиксировать границы настроек, базы, статики и пользовательских файлов.

Входное состояние: Полный локальный цикл создания, чтения, изменения и удаления заметок. Выходное состояние: Документирован договор выпуска; приложение остаётся локальным и неразвёрнутым. Эта глава продолжает «Редакционный стол» — небольшой сайт заметок. Нужны основы Python, HTML и устройство HTTP-запроса. Используем Python 3.12 и Django 5.2.18 LTS; номер ветки выбран для этой серии. Полный каталог lesson-16 в архиве содержит собственные настройки, шаблоны и историю миграций. При самостоятельном прохождении выберите один снимок и отдельную локальную базу; смешивание файлов разных глав меняет договор примера.

Исходники подготовлены редакционным чтением. Зависимости, миграции, сервер и команды работы с данными здесь не выполнялись. Все описанные ответы и изменения являются ожидаемыми, example_execution: not_run.

Редакционный стол получил связанный путь от запроса до модели: посетитель читает опубликованные заметки, автор входит, создаёт черновик и меняет собственный материал. Последняя глава не объявляет приложение работающим на внешнем сервере. Она описывает условия, при которых локальные исходники можно передать в эксплуатацию, и отделяет подготовленные решения от действий, которые ещё предстоит выполнить. Наличие страницы и архива не является свидетельством развёртывания.

В серии используется Django 5.2.18 из поддерживаемой ветки LTS и Python 3.12. Это зафиксированный договор примера, а не название самого нового выпуска. Файл зависимостей содержит также диапазон Pillow для поля изображения; разрешённый набор пакетов и его фиксированный файл ещё не получены установкой. Перед выпуском нужно согласовать конкретную среду и проверить совместимость именно в ней. В этой работе установка и запуск не выполнялись.

Настройки относятся к среде

Локальный DEBUG позволяет получить подробную диагностику и использовать вспомогательную раздачу медиа. Во внешней среде он должен быть выключен. Подробная страница исключения содержит сведения о внутреннем состоянии, которые полезны разработчику и не предназначены для посетителя. Изменение флага не устраняет причину ошибок, поэтому нужен отдельный способ получать сообщения о сбоях и при этом показывать человеку краткий ответ.

Секретный ключ читается из окружения. Учебное значение допустимо только для локального состояния; при выключенном DEBUG настройки останавливают запуск, если оно осталось неизменным. Это позволяет заметить неверную конфигурацию до обслуживания запросов. Само наличие переменной не обеспечивает сохранность секрета: его нельзя помещать в публичный архив, историю репозитория или сообщения об ошибках. В нашем скачиваемом примере находится только демонстрационное значение.

ALLOWED_HOSTS ограничивает допустимые имена хоста для запросов к Django. Для локальной среды указаны localhost и адрес обратной связи. Внешняя среда должна передать конкретное имя сервиса. Не заменяйте этот список звездочкой ради устранения первого отказа: сообщение об отклонённом хосте помогает обнаружить расхождение между настройкой и маршрутом запроса. Публичный домен и внутренний адрес процесса относятся к разным уровням размещения.

Для режима без DEBUG включены безопасные cookie сессии и CSRF, перенаправление к HTTPS и HSTS. Эти параметры предполагают, что HTTPS действительно настроен в выбранной инфраструктуре. Если TLS завершается перед приложением на доверенном прокси, потребуется правильно описать эту связь и обработку соответствующего заголовка. Не доверяйте любому пришедшему извне заголовку как доказательству HTTPS. В исходниках не задаётся выдуманный адрес прокси, которого у нас ещё нет.

Три разных вида файлов

SQLite хранит состояние редакционных записей и пользователей. Это не временный файл, который можно удалить при каждом обновлении кода. Нужны постоянное место хранения и резервная копия. Локальная схема удобна для самостоятельного прохождения, но серия не доказывает её пригодность для многопроцессной командной редакции с большой нагрузкой. При выборе другого сервера базы потребуется согласованный переход, включая данные и порядок миграций.

Статические файлы относятся к версии приложения: CSS, JavaScript и оформительские изображения. Для внешней среды их собирают в STATIC_ROOT и раздают выбранным сервером или хранилищем. Команда сбора не создаёт заметки и не переносит обложки. Путь исходного CSS внутри приложения и адрес, который видит браузер, связаны через механизм статики, а не совпадают по требованию. В локальном снимке этот механизм уже использован в базовом шаблоне.

Медиа — пользовательские обложки — возникают после выпуска и переживают смену исходников. Их хранение должно быть постоянным, а раздача изолирована от исполнения кода приложения. Нельзя считать каталог media частью обычной сборки, которую безопасно перезаписать. Резервная копия базы без файлов сохранит названия обложек, но не сами изображения. Для восстановления материала потребуются оба набора данных и согласованная процедура.

Удаление строки Note не очищает файл автоматически. Поэтому отдельно нужен процесс обнаружения неиспользуемых медиа, срок хранения и осторожное удаление. Не начинайте такой процесс только по отсутствию ссылки в одном запросе: нужно учесть возможные общие ссылки, параллельную загрузку и восстановление из резервной копии. В текущем примере автоматический сборщик файлов отсутствует; это честная граница исходников, а не скрытая завершённая функция.

Порядок передачи в эксплуатацию

Файл RELEASE.md перечисляет предпосылки: окружение, секреты, база, миграции, статика, медиа, сервер WSGI и HTTPS. Выбор конкретного сервера приложений и внешнего хранилища оставлен инфраструктуре, которой в этой локальной работе нет. Встроенный сервер разработки помогает читателю увидеть приложение, но не является готовой схемой обслуживания внешнего трафика. Не переносите команду самостоятельного упражнения в постоянную среду без отдельного решения.

Перед обновлением данных нужно понимать текущую историю миграций. Например, slug появился через заполнение существующих строк, а author допускает исторически неизвестного владельца. Повторная загрузка учебной фикстуры в настоящую редакционную базу может изменить состояние, поэтому фикстура предназначена для отдельной локальной базы. Права администратора, обычного автора и анонимного посетителя должны оцениваться раздельно. Получение доступа к панели не является обычным способом регистрации автора.

Ожидаемое состояние последнего снимка можно описать без утверждения о выполнении: публичная выборка ограничена публикацией, личная выборка владельцем, формы сохраняют разрешённые поля, удаление принимает POST, загрузка использует отдельный каталог. Это вывод из чтения исходников. Фактическое подтверждение поведения потребует самостоятельной проверки выбранной среды после установки и миграций. В серии прямо зафиксировано example_execution: not_run.

Сохраните список того, что осталось решением редакции: кто публикует материал через административную панель, кто назначает автора старым заметкам, как обрабатываются конфликты двух вкладок и как долго хранятся лишние обложки. Такая передача помогает следующему участнику продолжить проект с понятными границами. Скачиваемый архив содержит все шестнадцать состояний, поэтому можно вернуться к конкретному шагу и проследить, когда появилось каждое правило.

Полные файлы этого шага

Следующие файлы показаны полностью. Остальные согласованные файлы находятся в том же снимке архива; переносите весь каталог при выборе исходного состояния.

desk/settings.py

import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
DEBUG = os.environ.get("DJANGO_DEBUG", "1") == "1"
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "local-course-only-change-before-release")
if not DEBUG and SECRET_KEY == "local-course-only-change-before-release":
    raise RuntimeError("Set DJANGO_SECRET_KEY for a release")
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
INSTALLED_APPS = [
    "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes",
    "django.contrib.sessions",
        "django.contrib.messages",
        "django.contrib.staticfiles",
        "notes.apps.NotesConfig",
]
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
        "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "desk.urls"
TEMPLATES = [{"BACKEND": "django.template.backends.django.DjangoTemplates",
    "DIRS": [BASE_DIR / "templates"],
              "APP_DIRS": True, "OPTIONS": {"context_processors": [
                  "django.template.context_processors.request",
                      "django.contrib.auth.context_processors.auth",
                  "django.contrib.messages.context_processors.messages"]}}]
WSGI_APPLICATION = "desk.wsgi.application"
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3"}}
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"},
]
LANGUAGE_CODE = "ru"
TIME_ZONE = "Europe/Minsk"
USE_I18N = True
USE_TZ = True
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
LOGIN_URL = "login"
LOGIN_REDIRECT_URL = "notes:list"
LOGOUT_REDIRECT_URL = "notes:list"

MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

if not DEBUG:
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True
    SECURE_SSL_REDIRECT = True
    SECURE_HSTS_SECONDS = 3600

RELEASE.md

# Контракт выпуска «Редакционного стола»

Это авторские исходные файлы курса, execution: not_run. Установка, миграции, запуск,
тесты, сборка static и публикация не выполнялись. Перед собственным выпуском читатель
должен отдельно подтвердить эти операции в своей среде.

- Python 3.12, Django 5.2.18 LTS; SQLite для одного локального учебного процесса.
- DJANGO_DEBUG=0, уникальный DJANGO_SECRET_KEY, DJANGO_ALLOWED_HOSTS.
- WSGI entry point: desk.wsgi:application; process manager and TLS endpoint are infrastructure choices.
- collectstatic writes STATIC_ROOT; front server serves static assets.
- MEDIA_ROOT is persistent storage for trusted authors; production server must serve media separately.
- BACKUP: both db.sqlite3 and media; retain DB and files from the same checkpoint.
- Public list/detail expose published notes; authors can preview their own drafts.
- Publication remains an editor operation in admin; author form excludes status and author.
- Image uploads require isolated media origin and review before admitting untrusted users.
- Expected acceptance: anonymous viewer cannot see a draft; another author cannot edit it;
  logout/delete are POST with CSRF; failure to read DB does not produce an empty successful list.

Документация и исходники

Механизм этой главы описан в официальной документации Django 5.2. При самостоятельной проверке сначала сопоставьте маршрут, представление и состояние данных, затем сравните результат с указанным ожидаемым поведением. Архив учебного проекта содержит все шестнадцать самостоятельных этапов и договор эксплуатации.