Глава 03. Приложение Веб 1.0
Last updated
Last updated
Чтобы начать наше путешествие в приложения, управляемые гипермедиа, мы собираемся создать простое веб-приложение для управления контактами под названием Contact.app. Мы начнем с базового многостраничного приложения (MPA) в стиле «Web 1.0» в великой традиции CRUD (создание, чтение, обновление, удаление). Это будет не лучшее в мире приложение для управления контактами, но оно будет простым и будет выполнять свою работу.
Это приложение также будет легко постепенно улучшать в следующих главах, используя гипермедиа-ориентированную библиотеку htmx.
К тому времени, как мы закончим создание и улучшение приложения, в следующих нескольких главах оно будет иметь несколько очень интересных функций, которые, по мнению большинства современных разработчиков, требуют использования инфраструктуры SPA JavaScript.
Чтобы продемонстрировать, как работают приложения Web 1.0, нам нужно выбрать серверный язык и библиотеку для обработки HTTP-запросов. В просторечии это называется нашим «Серверным» или «Веб-стеком», и на выбор есть буквально сотни вариантов, многие из которых имеют страстных последователей. Вероятно, у вас есть веб-фреймворк, который вы предпочитаете, и хотя нам хотелось бы написать эту книгу для всех возможных стеков, в интересах простоты (и здравомыслия) мы можем выбрать только один.
Для этой книги мы будем использовать следующий стек:
как наш язык программирования.
в качестве нашей веб-инфраструктуры, позволяющей нам подключать HTTP-запросы к логике Python.
для нашего серверного языка шаблонов, позволяющего отображать HTML-ответы с использованием знакомого и интуитивно понятного синтаксиса.
Почему именно этот стек?
Python — самый популярный язык программирования в мире, согласно индексу , который на момент написания статьи является уважаемым показателем популярности языка программирования. Что еще более важно, Python легко читать, даже если вы с ним не знакомы.
Мы выбрали веб-фреймворк Flask, потому что он прост и не требует много структурирования помимо основ обработки HTTP-запросов.
Этот простой подход хорошо подходит для наших нужд: в других случаях вы можете рассмотреть более полнофункциональный фреймворк Python, такой как , который предоставляет гораздо больше функциональных возможностей «из коробки», чем Flask.
Используя Flask для нашей книги, мы сможем сосредоточить наш код на обмене гипермедиа .
Мы выбрали шаблоны Jinja2, потому что они являются языком шаблонов по умолчанию для Flask. Они достаточно просты и достаточно похожи на большинство других серверных языков шаблонов, поэтому большинство людей, знакомых с любой серверной (или клиентской) библиотекой шаблонов, смогут быстро и легко их понять.
Даже если эта комбинация технологий не является вашим предпочтительным стеком, продолжайте читать: вы многому научитесь из шаблонов, которые мы представим в следующих главах, и не составит труда отобразить их в предпочитаемом вами языке и средах.
С помощью этого стека мы будем отображать HTML на стороне сервера для возврата клиентам, а не создавать JSON. Это традиционный подход к созданию веб-приложений. Однако с появлением SPA этот подход не стал так широко использоваться, как раньше. Сегодня, когда люди заново открывают для себя этот стиль веб-приложений, термин «серверный рендеринг» или SSR становится тем, как люди говорят об этом. Это контрастирует с «клиентским рендерингом», то есть рендерингом шаблонов в браузере с данными, полученными в форме JSON с сервера, как это принято в библиотеках SPA.
В Contact.app мы намеренно сделаем все максимально простым, чтобы максимизировать обучающую ценность нашего кода: это не будет идеально факторизованный код, но читателям будет легко следовать ему, даже если у них мало опыта работы с Python, и Приложение и продемонстрированные методы должны быть легко переведены в предпочитаемую вами среду программирования.
Поскольку эта книга предназначена для изучения того, как эффективно использовать гипермедиа, мы лишь кратко представим различные технологии, которые мы используем в этой гипермедиа. У этого подхода есть некоторые очевидные недостатки: например, если вы не знакомы с Python, некоторые примеры кода Python в книге могут поначалу показаться немного запутанными или загадочными.
Если вы чувствуете, что вам нужно быстро познакомиться с языком, прежде чем углубляться в код, мы рекомендуем следующие книги и веб-сайты:
Мы считаем, что большинство веб-разработчиков, даже разработчиков, незнакомых с Python, смогут следовать нашим примерам. Большинство авторов этой книги до ее написания почти не писали на Python, и мы довольно быстро освоились.
Flask — это простой, но гибкий веб-фреймворк для Python. Мы углубимся в это, коснувшись его основных элементов.
Приложение Flask состоит из серии маршрутов, привязанных к функциям, которые выполняются при выполнении HTTP-запроса по заданному пути. Он использует функцию Python, называемую «декораторами», для объявления маршрута, который будет обрабатываться, за которым затем следует функция для обработки запросов к этому маршруту. Мы будем использовать термин «обработчик» для обозначения функций, связанных с маршрутом.
Давайте создадим наше первое определение маршрута, простой маршрут «Hello Flask». В следующем коде Python вы увидите @app
символ. Это декоратор фляг, который позволяет нам настраивать маршруты. Не беспокойтесь слишком сильно о том, как работают декораторы в Python, просто знайте, что эта функция позволяет нам сопоставить заданный путь с определенной функцией (например, обработчиком). Приложение Flask при запуске будет принимать HTTP-запросы, искать соответствующий обработчик и вызывать его.
Устанавливает, что мы отображаем /
путь как маршрут.
Следующий метод — это обработчик этого маршрута.
Возвращает строку «Hello World!» клиенту.
Метод route()
декоратора Flask принимает аргумент: путь, который должен обрабатываться маршрутом. Здесь мы передаем корень или /
путь в виде строки для обработки запросов к корневому пути.
За этим объявлением маршрута следует простое определение функции index()
. В Python декораторы, вызываемые таким образом, применяются к функции, следующей сразу за ними. Таким образом, эта функция становится «обработчиком» этого маршрута и будет выполняться при выполнении HTTP-запроса по данному пути.
Обратите внимание, что имя функции не имеет значения, мы можем называть ее как угодно, главное, чтобы она была уникальной. В данном случае мы выбрали этот вариант index()
, потому что он соответствует маршруту, который мы обрабатываем: корневому «индексу» веб-приложения.
Итак, у нас есть index()
функция, следующая за определением корневого маршрута, и она станет обработчиком корневого URL-адреса в нашем веб-приложении.
Обработчик в этом случае предельно прост: он просто возвращает клиенту строку «Hello Flask!». Это еще не гипермедиа, но браузер прекрасно отобразит ее:
Отлично, это наш первый шаг во Flask, демонстрирующий основную технику, которую мы собираемся использовать для ответа на HTTP-запросы: маршруты, сопоставленные с обработчиками.
Для Contact.app вместо отображения «Hello Flask!» на корневом пути мы собираемся сделать что-то необычное: мы собираемся перенаправить на другой путь, путь /contacts
. Перенаправления — это функция HTTP, которая позволяет перенаправить клиента в другое место с помощью ответа HTTP.
Мы собираемся отображать список контактов в качестве нашей корневой страницы, и, возможно, перенаправление на путь /contacts
для отображения этой информации немного более соответствует понятию ресурсов с REST. Это суждение с нашей стороны, и мы не считаем его чем-то слишком важным, но это имеет смысл с точки зрения маршрутов, которые мы настроим позже в приложении.
Чтобы изменить наш маршрут «Hello World» на перенаправление, нам нужно изменить всего одну строку кода:
Обновление до вызоваredirect()
Теперь index()
функция возвращает результат redirect()
функции, предоставленной Flask, с указанным нами путем. В этом случае путь /contacts
передается как строковый аргумент. Теперь, если вы перейдете по корневому пути, /
наше приложение Flask перенаправит вас по этому /contacts
пути.
Теперь, когда у нас есть некоторое представление о том, как определять маршруты, давайте приступим к определению и последующей реализации нашего веб-приложения.
Что будет делать Contact.app?
Первоначально это позволит пользователям:
Просмотр списка контактов, включая имя, фамилию, телефон и адрес электронной почты.
Поиск контактов
Добавить новый контакт
Просмотр сведений о контакте
Редактировать данные контакта
Удаление контакта
Итак, как вы можете видеть, Contact.app — это CRUD-приложение, которое идеально подходит для подхода старой школы Web 1.0.
Давайте добавим нашу первую настоящую функциональность: возможность отображать все контакты в нашем приложении в списке (точнее, в таблице).
Эту функциональность можно найти по /contacts
пути, на который перенаправляется наш предыдущий маршрут.
Мы будем использовать Flask для маршрутизации /contacts
пути к функции-обработчику contacts()
. Эта функция сделает одно из двух:
Если в запросе найден поисковый запрос, он будет отфильтрован только по контактам, соответствующим этому запросу.
Если нет, он просто перечислит все контакты.
Это распространенный подход в приложениях в стиле Web 1.0: один и тот же URL-адрес, который отображает все экземпляры некоторого ресурса, также служит страницей результатов поиска для этих ресурсов. Такой подход позволяет легко повторно использовать отображение списка, общее для обоих типов запросов.
Вот как выглядит код этого обработчика:
Найдите параметр запроса с именем q
, что означает «запрос».
Если параметр существует, вызовите Contact.search()
с его помощью функцию.
Если нет, вызовите Contact.all()
функцию.
Передайте результат в index.html
шаблон для отображения клиенту.
Мы видим тот же код маршрутизации, что и в первом примере, но у нас более сложная функция-обработчик. Сначала мы проверяем, q
является ли указанный параметр поискового запроса частью запроса.
Строки запроса
Чтобы вернуться к нашему маршруту Flask, если найден параметр запроса с именем q
, мы вызываем метод search()
объекта Contact
модели, чтобы выполнить фактический поиск контактов и вернуть все соответствующие контакты.
Если параметр запроса не найден, мы просто получаем все контакты, вызывая all()
метод объекта Contact
.
Наконец, мы визуализируем шаблон, index.html
который отображает заданные контакты, передавая результаты любой из этих двух функций, которые мы в конечном итоге вызываем.
Примечание о классе контактов
Класс Contact
Python, который мы используем, является «моделью предметной области» или просто классом «модели» для нашего приложения, обеспечивающим «бизнес-логику» управления контактами.
Это может быть работа с базой данных (это не так) или с простым плоским файлом (это так), но мы пропустим внутренние детали модели. Думайте об этом как о «обычном» классе предметной модели с методами, которые действуют «нормальным» образом.
Мы будем относиться к нему Contact
как к ресурсу и сосредоточимся на том, как эффективно предоставлять гипермедийные представления этого ресурса клиентам.
Шаблоны списка и поиска
Теперь, когда у нас написана логика обработчика, мы создадим шаблон для отображения HTML в нашем ответе клиенту. На высоком уровне наш HTML-ответ должен содержать следующие элементы:
Список совпадающих или всех контактов.
Поле поиска, в котором пользователь может ввести и ввести условия поиска.
Немного окружающего «хрома»: верхний и нижний колонтитулы веб-сайта, которые будут одинаковыми независимо от того, на какой странице вы находитесь.
Мы используем язык шаблонов Jinja2, который имеет следующие возможности:
Мы можем использовать двойные фигурные скобки, {{ }}
чтобы встроить значения выражения в шаблон.
мы можем использовать фигурные проценты {% %}
для директив, таких как итерация или включение другого контента.
Помимо этого базового синтаксиса, Jinja2 очень похож на другие языки шаблонов, используемые для создания контента, и большинству веб-разработчиков должно быть легко следовать.
Давайте посмотрим на первые несколько строк кода в index.html
шаблоне:
Установите шаблон макета для этого шаблона.
Ограничьте содержимое, которое будет вставлено в макет.
Создайте форму поиска, которая будет отправлять HTTP-запрос GET
на адрес /contacts
.
Создайте поле ввода, позволяющее пользователю вводить поисковые запросы.
Первая строка кода ссылается на базовый шаблон layout.html
с extends
директивой. Этот шаблон макета обеспечивает макет страницы (опять же, иногда его называют «хром»): он оборачивает содержимое шаблона в тег <html>
, импортирует все необходимые CSS и JavaScript в <head>
элемент, помещает <body>
тег вокруг основного содержимого и т. д. В этом файле находится весь общий контент, окружающий «обычный» контент всего приложения.
Следующая строка кода объявляет content
раздел этого шаблона. Этот блок содержимого используется шаблоном layout.html
для внедрения содержимого index.html
в HTML-код.
Далее у нас есть первый кусочек настоящего HTML, а не просто директивы Jinja. У нас есть простая HTML-форма, которая позволяет искать контакты, выдав GET
запрос на /contacts
путь. Сама форма содержит метку и поле ввода с именем «q». Значение этого ввода будет отправлено вместе с GET
запросом к /contacts
пути в виде строки запроса (поскольку это запрос GET
).
Обратите внимание, что значение этого ввода установлено в выражение Jinja {{ request.args.get('q') or '' }}
. Это выражение оценивается Jinja и вставляет значение запроса «q» в качестве входного значения, если оно существует. Это «сохранит» значение поиска, когда пользователь выполняет поиск, так что при отображении результатов поиска текстовый ввод будет содержать искомый термин. Это улучшает взаимодействие с пользователем, поскольку пользователь может точно видеть, чему соответствуют текущие результаты, а не иметь пустое текстовое поле в верхней части экрана.
Наконец, у нас есть ввод типа submit. Это будет отображаться как кнопка, и при нажатии на нее форма будет отправлять HTTP-запрос.
Этот интерфейс поиска находится в верхней части нашей страницы контактов. Далее следует таблица контактов: либо все контакты, либо контакты, соответствующие запросу, если поиск был выполнен.
Вот как выглядит код шаблона для таблицы контактов:
Выведите несколько заголовков для нашей таблицы.
Переберите контакты, которые были переданы в шаблон.
Выведите значения текущего контакта, имени, фамилии и т. д.
Столбец «Операции» со ссылками для редактирования или просмотра контактной информации.
Это ядро страницы: мы создаем таблицу с соответствующими заголовками, соответствующими данным, которые мы собираемся отображать для каждого контакта. Мы перебираем контакты, которые были переданы в шаблон методом-обработчиком, используя for
директиву цикла в Jinja2. Затем мы создаем серию строк, по одной для каждого контакта, где мы отображаем имя и фамилию, телефон и адрес электронной почты контакта в виде ячеек таблицы в строке.
Кроме того, у нас есть ячейка таблицы, содержащая две ссылки:
Ссылка на страницу «Редактирование» контакта, расположенную по адресу /contacts/{{ contact.id }}/edit
(например, для контакта с идентификатором 42 ссылка редактирования будет указывать на /contacts/42/edit
).
Ссылка на страницу «Просмотр» для контакта /contacts/{{ contact.id }}
(в нашем предыдущем примере контакта страница просмотра будет находиться по адресу /contacts/42
).
Наконец, у нас есть кое-что финальное: ссылка для добавления нового контакта и директива Jinja2 для завершения блока content
:
Ссылка на страницу, позволяющую создать новый контакт.
Закрывающий элемент блока content
.
И это наш полный шаблон. Используя этот простой серверный шаблон в сочетании с нашим методом-обработчиком, мы можем ответить HTML- представлением всех запрошенных контактов. Пока что так гипермедиа.
Вот как выглядит шаблон с небольшим количеством контактной информации:
На данный момент наше приложение не получит никаких наград за дизайн, но обратите внимание, что наш шаблон при визуализации предоставляет все функциональные возможности, необходимые для просмотра всех контактов и поиска по ним, а также предоставляет ссылки для их редактирования и просмотра подробностей о них. или даже создать новый.
И все это происходит без того, чтобы клиент (то есть браузер) ничего не знал о том, что такое контакты и как с ними работать. Все закодировано в гипермедиа. Веб-браузер, обращающийся к этому приложению, просто знает, как выдавать HTTP-запросы, а затем отображать HTML, не говоря уже о специфике конечных точек наших приложений или базовой модели домена.
Каким бы простым на данный момент ни было наше приложение, оно полностью RESTful.
Следующая функциональность, которую мы добавим в наше приложение, — это возможность добавлять новые контакты. Для этого нам нужно будет обработать /contacts/new
URL-адрес, указанный в ссылке «Добавить контакт» выше. Обратите внимание: когда пользователь нажимает на эту ссылку, браузер отправляет запрос GET
на /contacts/new
URL-адрес.
Все остальные маршруты, которые мы использовали до сих пор GET
, также используются, но на самом деле мы собираемся использовать два разных метода HTTP для этой части функциональности: HTTP GET
для отображения формы для добавления нового контакта, а затем HTTP POST
для того же пути для фактически создайте контакт, поэтому мы собираемся четко указать метод HTTP, который мы хотим обрабатывать, когда объявляем этот маршрут.
Вот код:
Объявите маршрут, явно обрабатывая GET
запросы к этому пути.
Отобразите new.html
шаблон, передав новый объект контакта.
Достаточно просто. Мы просто отображаем new.html
шаблон с новым контактом. ( Contact()
это то, как вы создаете новый экземпляр класса Contact
в Python, если вы с ним не знакомы.)
Хотя код обработчика этого маршрута очень прост, new.html
шаблон более сложен.
В остальных шаблонах мы собираемся опустить директиву макета и объявление блока контента, но вы можете предположить, что они одинаковы, если мы не оговорим иное. Это позволит нам сосредоточиться на «мясе» шаблона.
Если вы знакомы с HTML, вы, вероятно, ожидаете увидеть здесь элемент формы и не будете разочарованы. Мы собираемся использовать стандартную форму управления гипермедиа для сбора контактной информации и отправки ее на сервер.
Вот как выглядит наш HTML:
Форма, которая отправляется по /contacts/new
пути, используя HTTP POST
.
Метка для ввода первой формы.
Первый ввод формы типа электронная почта.
Любые сообщения об ошибках, связанные с этим полем.
В первой строке кода мы создаем форму, которая будет отправляться обратно по тому же пути , который мы обрабатываем: /contacts/new
. Однако вместо того, чтобы отправлять HTTP GET
по этому пути, мы выдадим POST
ему HTTP. Использование POST
таким образом будет сигнализировать серверу, что мы хотим создать новый Контакт, а не получить форму для его создания.
Затем у нас есть метка (всегда хорошая практика!) и входные данные, которые фиксируют адрес электронной почты создаваемого контакта. Имя ввода email
: и при отправке этой формы значение этого ввода будет отправлено в POST
запрос, связанный с email
ключом.
Далее у нас есть входные данные для других полей для контактов:
Наконец, у нас есть кнопка, которая отправит форму, конец тега формы и обратная ссылка на основную таблицу контактов:
В этом прямом примере легко пропустить: мы видим гибкость гипермедиа в действии.
Если мы добавим новое поле, удалим поле или изменим логику того, как поля проверяются или работают друг с другом, это новое положение дел будет отражено в новом гипермедийном представлении, предоставляемом пользователям. Пользователь увидит обновленную новую форму и сможет работать с этими новыми функциями без необходимости обновления программного обеспечения.
Обработка сообщения в /contacts/new
Следующим шагом в нашем приложении является обработка того, POST
что эта форма передает /contacts/new
.
Для этого нам нужно добавить в наше приложение еще один маршрут, который обрабатывает этот /contacts/new
путь. Новый маршрут будет обрабатывать POST
метод HTTP вместо HTTP GET
. Мы будем использовать отправленные значения формы, чтобы попытаться создать новый контакт.
Если нам удастся создать контакт, мы перенаправим пользователя к списку контактов и покажем сообщение об успехе. Если нам это не удастся, мы снова отобразим новую контактную форму с любыми значениями, введенными пользователем, и отобразим сообщения об ошибках о том, какие проблемы необходимо исправить, чтобы пользователь мог их исправить.
Вот наш новый обработчик запросов:
Мы создаем новый объект контакта со значениями из формы.
Мы стараемся его сохранить.
В случае успеха «высветите» сообщение об успехе и перенаправьте на /contacts
страницу.
В случае неудачи повторно отобразите форму, показывая пользователю все ошибки.
Логика этого обработчика немного сложнее, чем у других методов, которые мы видели. Первое, что мы делаем, это создаем новый контакт, снова используя синтаксис Contact()
Python для создания объекта. Мы передаем значения, отправленные пользователем в форму, используя request.form
объект — функцию, предоставляемую Flask.
Это request.form
позволяет нам легко и удобно получать доступ к отправленным значениям формы, просто передавая одно и то же имя, связанное с различными входными данными.
Мы также передаем None
конструктору первое значение Contact
. Это параметр «id», и, передавая его, None
мы сигнализируем, что это новый контакт и для него необходимо сгенерировать идентификатор. (Опять же, мы не вдаваемся в подробности реализации этого объекта модели, наша единственная задача — использовать его для генерации ответов гипермедиа.)
Далее мы вызываем save()
метод объекта Contact. Этот метод возвращает значение true
, если сохранение прошло успешно, а также false
если сохранение не удалось (например, пользователь отправил неверное электронное письмо).
Если нам удалось сохранить контакт (то есть ошибок проверки не было), мы создаем флэш- сообщение, указывающее на успех, и перенаправляем браузер обратно на страницу списка. «Flash» — это обычная функция в веб-фреймворках, которая позволяет хранить сообщение, которое будет доступно при следующем запросе , обычно в файле cookie или в хранилище сеансов.
Наконец, если мы не можем сохранить контакт, мы повторно отображаем new.html
шаблон с контактом. Будет показан тот же шаблон, что и выше, но входные данные будут заполнены отправленными значениями, а любые ошибки, связанные с полями, будут отображаться для обратной связи с пользователем о том, какая проверка не удалась.
Шаблон публикации/перенаправления/получения
Это означает, что если пользователь случайно (или намеренно) обновит страницу, браузер не отправит еще один контакт POST
, потенциально создавая еще один контакт. Вместо этого он выдаст объект GET
, на который мы перенаправляем, который не должен иметь побочных эффектов.
Мы будем использовать шаблон PRG в нескольких местах этой книги.
Итак, у нас есть серверная логика, настроенная на сохранение контактов. И, хотите верьте, хотите нет, это примерно настолько сложно, насколько сложна наша логика обработчика, даже если мы посмотрим на добавление более сложных поведений, управляемых htmx.
Следующая часть функциональности, которую мы реализуем, — это страница сведений о контакте. Пользователь перейдет на эту страницу, нажав ссылку «Просмотр» в одной из строк списка контактов. Это приведет их к пути /contact/<contact id>
(например, /contacts/42
).
Это обычная закономерность в веб-разработке: контакты рассматриваются как ресурсы, а URL-адреса вокруг этих ресурсов организованы последовательным образом.
Если вы хотите просмотреть все контакты, вы отправляете GET
сообщение /contacts
.
Если вы хотите, чтобы гипермедийное представление позволяло вам создать новый контакт, вы отправляете GET
команду /contacts/new
.
Если вы хотите просмотреть конкретный контакт (скажем, с 42), you issue a `GET
идентификатором /contacts/42
.
Вечный велосипедный сарай URL-дизайна
Легко поспорить о деталях схемы пути, которую вы используете для своего приложения:
«Должны ли мы POST
делать /contacts/new
или /contacts
?»
Мы видели много аргументов в Интернете и лично в защиту одного подхода по сравнению с другим. Мы считаем, что важнее понять общую идею ресурсов и гипермедийных представлений , а не беспокоиться о мелких деталях дизайна вашего URL-адреса.
Мы рекомендуем вам просто выбрать разумный, ориентированный на ресурсы макет URL-адреса, который вам нравится, и затем придерживаться единообразия. Помните, что в системе гипермедиа вы всегда можете изменить свои конечные точки позже, поскольку вы используете гипермедиа в качестве механизма состояния приложения!
Логика нашего обработчика подробного маршрута будет очень простой: мы просто просматриваем контакт по идентификатору, который встроен в путь URL-адреса маршрута. Чтобы извлечь этот идентификатор, нам нужно будет представить последнюю часть функциональности Flask: возможность вызывать части пути, автоматически извлекать их и передавать в функцию-обработчик.
Вот как выглядит код, всего несколько строк простого Python:
Сопоставьте путь с переменной пути с именем contact_id
.
Обработчик принимает значение этого параметра пути.
Найдите соответствующий контакт.
Отрисуйте show.html
шаблон.
Вы можете увидеть синтаксис извлечения значений из пути в первой строке кода: вы заключаете часть пути, которую хотите извлечь, <>
и даете ей имя. Этот компонент пути будет извлечен и затем передан в функцию-обработчик через параметр с тем же именем.
Итак, если бы вы перешли к пути /contacts/42
, значение 42
было бы передано в contacts_view()
функцию для значения contact_id
.
Как только у нас есть идентификатор контакта, который мы хотим найти, мы загружаем его, используя метод find
объекта Contact
. Затем мы передаем этот контакт в show.html
шаблон и отображаем ответ.
Наш show.html
шаблон относительно прост: он отображает ту же информацию, что и таблица, но в немного другом формате (возможно, для печати). Если мы позже добавим в приложение такие функции, как «заметки», это даст нам хорошее место для этого.
Опять же опустим «хром» шаблона и сосредоточимся на мясе:
Мы просто отображаем заголовок имени и фамилии с дополнительной контактной информацией под ним и парой ссылок: ссылку для редактирования контакта и ссылку для возврата к полному списку контактов.
Далее мы рассмотрим функциональность на другом конце ссылки «Редактировать». Редактирование контакта будет очень похоже на создание нового контакта. Как и при добавлении нового контакта, нам понадобятся два маршрута, которые обрабатывают один и тот же путь, но используют разные методы HTTP: to GET
вернет /contacts/<contact_id>/edit
форму, позволяющую редактировать контакт, а to POST
этот путь обновит его.
Мы также собираемся объединить возможность удаления контакта с функцией редактирования. Для этого нам нужно будет обработать файл POST
to /contacts/<contact_id>/delete
.
Давайте посмотрим на код для обработки GET
, который, опять же, вернет HTML-представление интерфейса редактирования для данного ресурса:
Как видите, это очень похоже на нашу функцию «Показать контакт». По сути, он почти идентичен, за исключением шаблона: здесь мы рендерим, edit.html
а не show.html
.
Хотя наш код обработчика похож на функцию «Показать контакт», шаблон edit.html
будет очень похож на шаблон для функции «Новый контакт»: у нас будет форма, которая отправляет обновленные значения контакта на тот же URL-адрес «редактирования». и это представляет все поля контакта в качестве входных данных для редактирования вместе со всеми сообщениями об ошибках.
Вот первая часть формы:
Введите POST
путь /contacts/{{ contact.id }}/edit
.
Как и на new.html
странице, ввод привязан к электронной почте контакта.
Этот HTML-код почти идентичен нашей new.html
форме, за исключением того, что эта форма будет отправлять данные POST
по другому пути в зависимости от идентификатора контакта, который мы хотим обновить. (Здесь стоит отметить, что вместо POST
, мы бы предпочли использовать PUT
или PATCH
, но они недоступны в простом HTML.)
После этого у нас есть оставшаяся часть нашей формы, опять же очень похожая на new.html
шаблон, и кнопка для отправки формы.
В заключительной части нашего шаблона у нас есть небольшая разница между new.html
и edit.html
. Под основной формой редактирования мы разместили вторую форму, позволяющую удалить контакт. Это делается путем выдачи POST
пути /contacts/<contact id>/delete
. Точно так же, как мы предпочли бы использовать PUT
для обновления контакта, мы бы предпочли использовать HTTP- DELETE
запрос для его удаления. К сожалению, это также невозможно в простом HTML.
Чтобы завершить страницу, есть простая гиперссылка на список контактов.
Учитывая все сходства между шаблонами new.html
и edit.html
, вы можете задаться вопросом, почему мы не проводим рефакторинг этих двух шаблонов, чтобы разделить между ними логику. Это хорошее наблюдение, и в производственной системе мы, вероятно, поступили бы именно так.
Однако для наших целей, поскольку наше приложение маленькое и простое, мы оставим шаблоны отдельно.
Факторинг ваших приложений
Одна вещь, которая часто сбивает с толку людей, которые приходят к гипермедийным приложениям на основе JavaScript, — это понятие «компонентов». В приложениях, ориентированных на JavaScript, принято разбивать приложение на небольшие клиентские компоненты, которые затем объединяются вместе. Эти компоненты часто разрабатываются и тестируются изолированно и предоставляют разработчикам хорошую абстракцию для создания тестируемого кода.
В отличие от этого, в приложениях, управляемых гипермедиа, вы размещаете свое приложение на стороне сервера. Как мы уже говорили, приведенную выше форму можно преобразовать в общий шаблон между шаблонами редактирования и создания, что позволит вам реализовать многоразовую реализацию DRY (не повторяйте себя).
Обратите внимание, что факторинг на стороне сервера имеет тенденцию быть более детальным, чем на стороне клиента: вы склонны разделять общие разделы , а не создавать множество отдельных компонентов. У этого есть свои преимущества (он обычно прост), а также недостатки (он не так изолирован, как клиентские компоненты).
В целом, правильно спроектированное серверное гипермедийное приложение может быть чрезвычайно СУХИМ.
Обработка сообщения в /contacts/<contact_id>
Далее нам нужно обработать HTTP- POST
запрос, который отправляет форма в нашем edit.html
шаблоне. Мы объявим другой маршрут, который будет обрабатывать тот же путь, что и указанный GET
выше.
Вот новый код обработчика:
Обработать a POST
до /contacts/<contact_id>/edit
.
Найдите контакт по идентификатору.
Обновите контакт новой информацией из формы.
Попытайтесь сохранить его.
В случае успеха отобразите сообщение об успехе и перенаправьте на страницу сведений.
В случае неудачи повторно отобразите шаблон редактирования, отобразив все ошибки.
Логика этого обработчика очень похожа на логику обработчика добавления нового контакта. Единственное реальное отличие состоит в том, что вместо создания нового контакта мы просматриваем контакт по его идентификатору, а затем вызываем update()
для него метод со значениями, которые были введены в форму.
Еще раз, эта согласованность между нашими операциями CRUD является одним из приятных и упрощающих аспектов традиционных веб-приложений CRUD.
Мы объединили функцию удаления контактов с тем же шаблоном, который используется для редактирования контакта. Эта вторая форма отправит HTTP-запрос POST
на /contacts/<contact_id>/delete
, и нам также нужно будет создать обработчик для этого пути.
Вот как выглядит контроллер:
Обработайте POST
путь /contacts/<contact_id>/delete
.
Найдите и затем вызовите delete()
метод контакта.
Показывайте сообщение об успехе и перенаправляйтесь к основному списку контактов.
Код обработчика очень прост, поскольку нам не нужно выполнять какую-либо проверку или условную логику: мы просто ищем контакт так же, как мы это делали в других наших обработчиках, и вызываем для него метод, а затем перенаправляем обратно в список delete()
. контактов с флэш-сообщением об успехе.
Шаблон в этом случае не нужен, контакт пропал.
И, ну… Хотите верьте, хотите нет, но это все наше приложение для контактов!
Если до сих пор у вас были проблемы с частями кода, не волнуйтесь: мы не ожидаем, что вы станете экспертом по Python или Flask (мы таковыми не являемся!). Вам просто нужно базовое понимание того, как они работают, чтобы извлечь пользу из оставшейся части книги.
Это небольшое и простое приложение, но оно демонстрирует многие аспекты традиционных приложений Web 1.0: CRUD, шаблон Post/Redirect/Get, работу с логикой домена в контроллере, организацию наших URL-адресов в связной ресурсной системе. ориентированный способ.
Более того, это веб-приложение, глубоко ориентированное на гипермедиа . Не особо задумываясь об этом, мы использовали REST, HATEOAS и все другие концепции гипермедиа, которые мы обсуждали ранее. Мы готовы поспорить, что это наше простое маленькое контактное приложение более RESTful, чем 99% всех когда-либо созданных API JSON!
Просто благодаря использованию гипермедиа HTML мы естественным образом попадаем в сетевую архитектуру RESTful.
Ну и замечательно. Но что случилось с этим маленьким веб-приложением? Почему бы не закончить на этом и заняться разработкой приложений в стиле Web 1.0?
Ну, на каком-то уровне в этом нет ничего плохого. В частности, для такого простого приложения старый способ создания веб-приложений может оказаться вполне приемлемым.
Однако наше приложение страдает от той «неуклюжести», о которой мы упоминали ранее при обсуждении приложений Web 1.0: каждый запрос заменяет весь экран, вызывая заметное мерцание при переходе между страницами. Вы теряете состояние прокрутки. Вам придется щелкать немного больше, чем в более сложном веб-приложении.
Contact.app на данный момент просто не выглядит «современным» веб-приложением.
Не пора ли использовать фреймворк JavaScript и API-интерфейсы JSON, чтобы сделать наше контактное приложение более интерактивным?
Нет. Нет, это не так.
Оказывается, мы можем улучшить взаимодействие с пользователем этого приложения, сохранив при этом его фундаментальную гипермедийную архитектуру.
Компоненты инкапсулируют раздел страницы вместе с ее динамическим поведением. Хотя инкапсуляция поведения является хорошим способом организации кода, она также может отделять элементы от окружающего их контекста, что может привести к неправильным или неадекватным связям между элементами. Результатом является то, что можно было бы назвать компонентным супом , когда информация скрыта в состоянии компонента, а не присутствует в HTML, который теперь непонятен из-за отсутствия контекста.
Прежде чем обращаться к компонентам для повторного использования, рассмотрите свои варианты. Механизмы более низкого уровня часто (позволяют) создавать более качественный HTML. В некоторых случаях компоненты действительно могут улучшить ясность вашего HTML.
Тот факт, что HTML-документ — это то, к чему вы почти не прикасаетесь, потому что все, что вам нужно, будет добавлено через JavaScript, выводит документ и структуру страницы из фокуса.
Чтобы избежать <div>
супа (или супа Markdown, или супа компонентов), вам необходимо знать разметку, которую вы создаете, и иметь возможность ее изменить.
Некоторые инфраструктуры SPA и некоторые веб-компоненты усложняют эту задачу, помещая уровни абстракции между кодом, который пишет разработчик, и сгенерированной разметкой.
Хотя эти абстракции могут позволить разработчикам создавать более богатый пользовательский интерфейс или работать быстрее, их повсеместное распространение означает, что разработчики могут упустить из виду фактический HTML (и JavaScript), отправляемый клиентам. Без тщательного тестирования это приводит к недоступности, плохому SEO и раздуванию сайта.
от No Starch Press
Зед Шоу
, доктор Чарльз Р. Северанс
Обратите внимание, что исходный код Contact.app доступен на .
« Строка запроса» является частью спецификации URL. Вот пример URL-адреса со строкой запроса: . Строка запроса — это все, что следует после ?
, и имеет формат пары имя-значение. В этом URL-адресе параметру запроса q
присвоено строковое значение joe
. В простом HTML строка запроса может быть включена в запрос либо путем жесткого кодирования в теге привязки, либо, что более динамично, с использованием тега формы с запросом GET
.
Этот обработчик реализует общую стратегию разработки в стиле Web 1.0, называемую шаблоном или PRG. Выполняя перенаправление HTTP после создания контакта и перенаправляя браузер в другое место, мы гарантируем, что контакт POST
не попадет в кеш запросов браузера.
В следующих нескольких главах мы рассмотрим , библиотеку, ориентированную на гипермедиа, которая позволит нам улучшить наше контактное приложение, сохранив при этом подход на основе гипермедиа, который мы использовали до сих пор.
— Мануэль Матузович,