Модель MTV. Маршрутизация. Функции представления

Курс по Django: https://stepik.org/a/183363

Архив проекта: lesson-2-coolsite.zip

Давайте подробнее разберемся как в Django работает механизм обработки запросов от пользователя. Нам это понимание пригодится в дальнейшем, описывая отдельные элементы всей этой процедуры.

Смотрите, вначале, когда запрос приходит на фреймворк, он пропускается через блок маршрутизации:

Здесь фиксируется тип URL-адреса и в списке шаблонов предопределенных адресов ищется первое совпадение. Например, пользователь вводит запрос:

http://127.0.0.1:8000/

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

http://127.0.0.1:8000/category/1/

соответствует шаблону:

category/<число>

Это уже другой маршрут. Или такой запрос:

http://127.0.0.1:8000/women/madonna/

с шаблоном:

women/<слаг (строка)>

И так далее. У нас может быть прописано множество типов URL-адресов и каждый адрес связан со своим обработчиком – представлением (иногда его еще называют контроллером). Если текущий запрос от пользователя не совпал ни с одним предопределенным URL, то возвращается код ошибки 404 – страница не найдена.

Предположим, что маршрутизатор нашел совпадение. Далее, активизируется представление, связанное с найденным типом URL-адреса. Представление (иногда его еще называют контроллером) – это или функция или класс, который отвечает за формирование ответа на соответствующий запрос. Как правило, ответом является HTML-страница. Эта страница, затем, возвращается клиенту, и он ее видит в браузере. Так вот, чтобы контроллер мог сформировать страницу, в общем случае, требуются данные (информация), плюс  шаблоны, в которые эти данные упаковываются. Например, приходит запрос на вывод страницы о Мадонне:

http://127.0.0.1:8000/women/madonna/

Активизируется соответствующее представление, которое берет шаблон информационной страницы и наполняет ее данными об этой певице, хранящиеся в БД:

На выходе получаем сформированную HTML-страницу, которая и возвращается пользователю. Вот такое разделение на данные (model), шаблоны (templates) и представления (views) представляет собой общеизвестный паттерн MTV, то есть, разделение данных и HTML-шаблонов. Техника довольно эффективна и удобна, так как позволяет независимо наполнять БД информацией и параллельно разрабатывать или изменять функционал сайта. Кроме того здесь легче находить ошибки, в отличие от подхода, когда в одном скрипте присутствует и подключение к БД и оперирование шаблонами. Методика «разделяй и властвуй» очень хорошо себя зарекомендовала в мире программирования и довольно часто используется в том или ином виде.

Добавление первого приложения

Отлично, я думаю общие принципы работы фреймворка Django вам понятны. Но пока наш сайт пустой, он не будет реагировать ни на какие запросы пользователей, кроме тестовой главной страницы. Какой должен быть наш следующий шаг? Согласно философии Django мы должны создать новое приложение в рамках нашего сайта. Что это за приложение и зачем оно вообще нужно? Разработчики фреймворка решили, что каждая самостоятельная часть сайта должна представляться в виде своего отдельного приложения. Например, создавая информационный сайт, мы должны будем определить приложение для отображения страниц этого сайта по определенным запросам. Далее, к нам приходит руководитель проекта и сообщает, что еще нужно реализовать форум на сайте. И, так как это функционально независимая часть сайта, то мы создаем еще одно приложение для форума. Затем, руководитель почесал свою репу и вспомнил, что еще нужно сделать раздел с опросом пользователей по разным тематикам. И на сайте появляется еще одно приложение – для опроса. И так далее. Каждая логически и функционально независимая часть сайта предполагает его реализацию в виде отдельного приложения:

Приложения в Django следует реализовывать максимально независимыми, в идеале – полностью независимыми, чтобы в дальнейшем мы могли их просто скопировать в другой сайт и там оно сразу же начинало бы работать. Это не всегда удается, но нужно к этому стремиться.

Итак, давайте создадим в нашем сайте первое приложение, которое возьмет на себя базовый функционал, то есть, оно и будет являться ядром нашего сайта. Для этого я открою терминал и, находясь в каталоге django/djsite/coolsite, выполню команду:

python manage.py startapp women

Здесь startapp – команда для создания нового приложения; women – название приложения. Название может быть любым (мы его придумываем сами), но так, чтобы оно отражало суть своего функционала. В данном случае приложение women (женщины) будет формировать станицы сайта об известных женщинах из разных областей жизни: кино, спорт, музыка, политика.

После выполнения команды у нас в проекте появилась еще одна папка – women, которая уже содержит несколько файлов и файл __init__.py, следовательно, приложение в Django реализуется как пакет языка Python. Также здесь присутствует одна вложенная папка migrations для хранения миграций БД нашего приложения. Подробнее о ней мы поговорим позже. Остальные файлы имеют следующее назначение:

  • admin.py – для настройки админ-панели сайта (админ-панель поставляется совместно с Django и каждый сайт может сразу ее использовать);
  • apps.py – для настройки (конфигурирования) текущего приложения;
  • models.py – для хранения ORM-моделей для представления данных из базы данных;
  • tests.py – модуль с тестирующими процедурами;
  • views.py – для хранения представлений (контроллеров) текущего приложения.

После создания приложения его необходимо зарегистрировать в проекте нашего сайта, чтобы фреймворк Django «знал» о его существовании и корректно с ним работал. Для этого нужно перейти в пакет конфигурации сайта (coolsite), открыть файл settings.py и в списке INSTALLED_APPS прописать новое приложение. В нем уже прописаны несколько стандартных приложений самого фреймворка и к ним мы просто добавим свое:

INSTALLED_APPS = [
...
    'women',
]

По идее этого вполне достаточно и все будет работать, но в действительности Django обращаясь к этому пакету находит файл apps.py, откуда и берет настройки приложения из класса WomenConfig. Чтобы в дальнейшем каждый раз не конкретизировать этот путь, я пропишу его сразу в списке приложений:

INSTALLED_APPS = [
...
    'women.apps.WomenConfig',
]

Представления и маршрутизация

Все, приложение создано и зарегистрировано. Давайте теперь создадим обработчик главной страницы сайта. Для этого нужно определить представление этой страницы. Как я уже отмечал, представления в Django можно реализовывать или в виде функций или в виде классов. Давайте для начала воспользуемся функцией, как наиболее простой реализацией для понимания. Эта функция будет отвечать за формирование ответа для главной страницы приложения women и называться index (название может быть любым, мы его определяем сами):

def index(request):
    return HttpResponse("Страница приложения women.")

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

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

Теперь нам нужно связать эту функцию представления главной страницы с соответствующим URL-адресом. Для этого в пакете конфигурации coolsite откроем файл urls.py и в список адресов urlpatterns добавим новый путь с помощью специальной функции path:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('women/', index),
]

Здесь первый параметр – это суффикс URL-адреса, то есть, часть URL, которая добавляется после доменного имени (при этом в конце ставится слеш). Например, если наш сайт располагается по адресу

http://127.0.0.1:8000

то первый аргумент 'women/' добавляется в конце к этому пути:

http://127.0.0.1:8000/women/

Именно такой адрес мы сейчас и определяем. Второй аргумент – это ссылка на функцию представления, которая должна возвращать ответ на данный запрос. Как мы уже знаем, ответ формируется в виде экземпляра класса HttpResponse.

Теперь нам нужно импортировать функцию index, чтобы она была доступна в пакете конфигурации:

from women.views import index

Если интегрированная среда вам здесь указывает ошибку, то это потому, что рабочим каталогом следует указать проект coolsite.

Проверим работоспособность нашего нового приложения и маршрута. Запустим тестовый веб-сервер:

python manage.py runserver

И откроем страницу:

http://127.0.0.1:8000/women/

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

def categories(request):
    return HttpResponse("<h1>Статьи по категориям</h1>")

Мы здесь используем тег h1, чтобы браузер отобразил эту строку как заголовок первого уровня. Затем, добавляем еще один путь в список urlpatterns:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('women/', index),
    path('cats/', categories),
]

И делаем импорт всех функций из модуля views нашего приложения:

from women.views import *

Все, у нас появился новый URL-адрес:

http://127.0.0.1:8000/cats/

по которому отображается заголовок первого уровня. По аналогии мы можем добавлять самые разные URL в наш сайт.

Обратите внимание, как только мы добавили дополнительные маршруты, тестовая главная страница перестала выдаваться, вместо этого мы видим исключение 404 – страница не найдена. Чтобы задать маршрут для главной страницы, нужно вместо «women/» записать пустую строку:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', index),
    path('cats/', categories),
]

Здесь пустая строка как раз и соответствует маршруту главной страницы и теперь при обращении к ней будет вызываться функция представления index.

Однако такой подход, когда мы маршруты приложения прописываем в пакете конфигурации, нарушает принцип независимости приложений. Действительно, если мы захотим перенести приложение women на другой сайт, то нам придется дополнительно копировать и его маршруты, что не очень удобно и хорошо. Как это можно разрешить? Очень просто. Django позволяет вторым параметром вместо функции представления передавать список URL-адресов приложения и связанные с ними функции. Для этого мы сначала импортируем специальную функцию include:

from django.urls import path, include

А, затем, в списке маршрутов с ее помощью подключим список URL уже из нашего приложения women:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('women/', include('women.urls')),
]

Мы здесь в качестве параметра указываем строку, в которой сначала записываем имя приложения и через точку файл urls, где будут прописаны маршруты приложения women. Ну а дальше все просто. Мы добавляем в приложение новый файл urls.py и в нем формируем список urlpatterns:

from django.urls import path
 
from .views import *
 
urlpatterns = [
    path('', index),
]

Здесь мы, во-первых, импортируем функцию path, которая и связывает URL c функциями представления и, во-вторых, импортируем функции из модуля views текущего пакета. Далее, в списке urlpatterns вызываем функцию path,  первым параметром указываем пустую строку, а вторым функцию index. Как вы думаете, какому URL-адресу будет соответствовать эта пустая строка? Смотрите, в основном пакете конфигурации у нас указано, что адреса в 'women.urls' следует добавлять как суффикс к адресу 'women/', то есть, к адресу:

http://127.0.0.1:8000/women/

Поэтому пустая строка в нашем приложении будет ссылаться именно на этот URL-адрес. Давайте проверим. Запустим веб-сервер и откроем эту страницу. Да, мы видим, что отработала именно функция index.

Если же мы добавим еще один маршрут в наш список приложения:

urlpatterns = [
    path('', index),
    path('cats/', categories),
]

То у нас появится еще один маршрут:

http://127.0.0.1:8000/women/cats/

Как видите все достаточно просто и при этом мы получили относительную независимость нашего приложения women от основного проекта сайта.

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

Курс по Django: https://stepik.org/a/183363

Видео по теме