<<< предыдущая следующая >>> Это девятый выпуск серии Mega-Tutorial Flask, в котором я расскажу вам, как разбивать списки в базе данных. Для справки ниже приведен список статей этой серии. Примечание 1: Если вы ищете старые версии данного курса, это здесь. Примечание 2: Если вдруг Вы хотели бы выступить в поддержку моей(Мигеля) работы в этом блоге, или просто не имеете терпения дожидаться неделю статьи, я (Мигель Гринберг)предлагаю полную версию данного руководства упакованную электронную книгу или видео. Для получения более подробной информации посетите learn.miguelgrinberg.com. В главе 8 я сделал несколько изменений в базе данных, необходимых для поддержки парадигмы «follower» ( подписчик ), которая так популярна в социальных сетях. Имея эту функциональность, я готов удалить последние записи, которые я создал для демонстрации. Это поддельные сообщения. В этой главе приложение начнет принимать сообщения в блогах от пользователей, а также доставляет их на index страницу и страницы профиля. Ссылки GitHub для этой главы: Browse, Zip, Diff. Начнем с чего-то простого. Домашняя страница должна иметь форму, в которой пользователи могут вводить новые сообщения. Сначала я создаю класс формы: Теперь можно добавить эту форму в шаблон для главной страницы приложения: Изменения в этом шаблоне аналогичны тем, что были сделаны в других формах. В заключение — следует добавить создание формы и обработку в функции просмотра: Давайте разберем изменения в этой функции просмотра по порядку: Прежде чем продолжить, я хотел бы обратить внимание на ряд важных моментов, связанных с обработкой веб-форм. Обратите внимание, что после обработки данных формы я завершаю запрос, отправив redirect на главную страницу index. Я мог бы легко пропустить переадресацию и позволить функции продолжать работать в части template rendering, так как это уже функция просмотра index. Итак, зачем redirect?   Стандартная практика — отвечать (response) на (request) запроса POST, созданного при отправке веб-формы с переадресацией. Это поможет как то избежать приступов раздражения при использовании команды обновления в веб-браузерах. Ведь когда вы нажимаете кнопку обновить, веб-браузер, выдаст последний запрос. Если запрос POST с представлением формы возвращает регулярный ответ, то обновление будет повторно отправлять форму. Поскольку это всегда неожиданно, браузер попросит пользователя подтвердить повторную отправку, но большинство пользователей не поймут, что надо браузеру. Но если на запрос POST отвечает перенаправление, браузер получит указание отправить запрос GET, чтобы захватить страницу, указанную в перенаправлении, поэтому теперь последний запрос больше не является POST-запросом, а команда обновления работает более предсказуемым образом. Этот простой трюк не что иное, как паттерн Post/Redirect/Get.. Он избегает вставки повторяющихся сообщений, когда пользователь непреднамеренно обновляет страницу после отправки веб-формы. Если вы помните, я создал пару сообщений в блогах, которые я долгое время показывал на домашней странице. Эти поддельные объекты создаются явно в функции просмотра index в виде простого списка Python: Но теперь у меня есть метод followed_posts() в модели User, который возвращает сообщения, которые данный пользователь хотел бы увидеть. Итак, теперь я могу заменить временные сообщения реальными: Метод followed_posts класса User возвращает объект запроса SQLAlchemy, который настроен на захват сообщений, на которые подписан пользователь из базы данных. Вызов all() по этому запросу запускает его выполнение, а возвращаемое значение представляет собой список со всеми результатами. Таким образом, я получаю структуру, которая очень похожа на ту, что формировала временные сообщения, которые я использовал до сих пор. Это настолько похоже, что шаблон даже не нужно менять. Надеюсь что, вы заметили, то как приложение работает на данный момент, не совсем удобно использовать, позволяя пользователям находить других пользователей. Фактически, на самом деле нет способа увидеть, что другие пользователи там есть вообще. Я собираюсь исправить это с помощью нескольких простых изменений. Надо бы создать новую страницу, которую я собираюсь назвать страницей «Explore». Эта страница будет работать как домашняя страница, но вместо того, чтобы показывать только сообщения от следующих пользователей, она будет показывать глобальный поток сообщений от всех пользователей. Вот новая функция просмотра: Вы заметили что-то странное в этой функции? Вызов render_template() ссылается на шаблон index.html, который я использую на главной странице приложения. Поскольку эта страница будет очень похожа на главную страницу, я решил повторно использовать шаблон. Но одно отличие от главной страницы заключается в том, что на странице «Explore» я не хочу иметь форму для записи сообщений в блоге, поэтому в этой функции просмотра я не включил аргумент form в вызов шаблона. Чтобы предотвратить сбой шаблона index.html, при попытке отобразить веб-форму, которой не существует, я добавлю условие, которое отображает форму, только если она определена: Я также добавлю ссылку на эту новую страницу в панели навигации: Помните подшаблон _post.html, в главе 6, чтобы отображать сообщения в блоге на странице профиля пользователя? Это небольшой шаблон, который был извлечен из шаблона страницы профиля пользователя и стал отдельным, чтобы его можно было использовать и из других шаблонов. Я сейчас сделаю небольшое улучшение, которое позволит показать имя пользователя сообщения в блоге как ссылку: Теперь я могу использовать этот суб-шаблон для визуализации и изучения блога на домашней странице: Вложенный шаблон ожидает, что переменная с именем post будет существовать, и именно так будет называться переменная цикла в шаблоне index, так что это нормально работает. Благодаря этим небольшим изменениям, удобство использования приложения значительно улучшилось. Теперь пользователь может посетить страницу читать сообщения в блоге от неизвестных пользователей и на основе этих сообщений найти новых, чтобы добавить подписку, что очень просто сделать, нажав на имя пользователя для доступа к странице профиля. Удивительно, правда? На этом этапе я предлагаю вам попробовать приложение еще раз, так что бы вы сами испытали эти последние улучшения пользовательского интерфейса.  Приложение выглядит лучше, чем было, но отображение всех записей на домашней странице станет проблемой гораздо раньше, чем можно себе представить. Что произойдет, если у пользователя есть тысяча записей? Или миллион? Управление таким большим списком сообщений будет крайне медленным и неэффективным. Чтобы решить эту проблему, я собираюсь разбить список сообщений. Это означает, что изначально я буду показывать только ограниченное количество сообщений одновременно и включать ссылки для навигации по всему остальному списку сообщений. Flask-SQLAlchemy поддерживает разбиение на страницы изначально методом запроса paginate(). Если, например, мне надо получить первые двадцать записей пользователя, я могу заменить вызов all() в конце запроса: Метод paginate можно вызвать для любого объекта запроса из Flask-SQLAlchemy. Это требует трех аргументов: Возвращаемое значение из paginate — объект Pagination. Атрибут items этого объекта содержит список элементов на запрошенной странице. В объекте Pagination есть еще полезные вещи, о которых я расскажу позже. Теперь давайте подумаем о том, как можно было бы реализовать разбиение на страницы в функции просмотра index(). Можно начать с добавления в приложение элемента конфигурации, определяющего, сколько элементов будет отображаться на странице. Это хорошая идея, чтобы эти «knobs» для всего приложения могли влиять на его поведение из файла конфигурации, потому что тогда я могу все корректировки вносить в одном месте. В итоге я, конечно, буду использовать большее количество, чем три элемента на странице, но для тестирования полезно работать с небольшими числами. Далее, мне нужно решить, каким образом номер страницы будет включен в URL-адреса приложений. Достаточно распространенным способом является использование аргумента query string для указания необязательного номера страницы, по умолчанию на стр. 1, если он не указан. Вот несколько примеров URL-адресов, которые показывают, как я буду реализовывать это: Чтобы получить доступ к аргументам, указанным в строке запроса, я могу использовать объект request.args объекта Flask. Вы уже видели это в главе 5, где я внедрил URL-адреса для входа пользователя из Flask-Login, которые могут включать аргумент строки запроса. Следующий пример демонстрирует, как я добавил разбивку домашней страницы на несколько и исследовать функции просмотра: В этих изменениях два оба маршрута определяют номер страницы для отображения, либо из аргумента page запроса страницы, либо по умолчанию это 1. Затем используется метод paginate() для извлечения только нужной страницы с результатами. Элемент конфигурации POSTS_PER_PAGE, который определяет размер страницы, доступен через объект app.config. Обратите внимание, насколько легки эти изменения, и как мало влияют на каждый кусок кода. Я пытаюсь написать каждую часть, абстрагируясь от работы других части, и это позволяет мне писать модульные и надежные приложения, которые легче расширить и протестировать. При этом вероятность получить фатал или мелкую ошибку существенно мала. Двигаем дальше! И вам следует испытать функциональность разбивки на страницы. Предварительно убедитесь, что у вас более трех сообщений в блоге. Это легче увидеть на странице поиска (explore page), где отображаются сообщения от всех пользователей. Теперь вам видны только три последних сообщения. Если вы хотите увидеть следующие три, введите http://localhost:5000/explore?page=2 в адресной строке браузера. Следующее изменение заключается в добавлении ссылок в нижней части списка сообщений блога, которые позволяют пользователям перейти на следующую и/или предыдущие страницы. Помните, что я упомянул, что возвращаемое значение из вызова paginate() является объектом класса Pagination из Flask-SQLAlchemy? До сих пор я использовал атрибут items этого объекта,
который содержит список элементов, полученных для выбранной страницы. Но у этого объекта есть несколько других атрибутов, которые полезны при создании ссылок на страницы: С помощью этих четырех элементов я могу создать ссылки на страницы (следующие и предыдущие) и передать их шаблонам для отображения: В next_url и prev_url в этих двух функциях будет примене URL-адрес, возвращаемый url_for(), только если есть страница в этом направлении. Если текущая страница находится на одном из концов коллекции сообщений, атрибуты has_next или has_prev объекта Pagination будут False, и в этом случае ссылка в этом направлении будет установлена ​​на None. Один интересный аспект функции url_for(), о котором я умолчал ранее, заключается в том, что вы можете добавить к нему любые аргументы ключевого слова, и если имена этих аргументов напрямую не указаны в URL-адресе, тогда Flask будет включать их в URL-адрес как аргументы запроса. Связи со страницами устанавливаются в шаблоне index.html, поэтому теперь давайте отобразим их на странице, прямо под списком сообщений: Это дополнение добавляет ссылку ниже списка сообщений как для главной страницы index, так и по исследуемым страницам. Первая ссылка помечена как «Новые сообщения» ("Newer posts"), и она указывает на предыдущую страницу (имейте в виду, что я показываю сообщения, отсортированные по последним данным, поэтому первая страница — с новейшим контентом).  Вторая ссылка помечена как «Старые сообщения» ("Older posts") и указывает на следующую страницу сообщений. Если какая-либо из этих двух ссылок — None, то ссылка не будет показана на странице через условное выражение.  На данный момент изменений для страницы index достаточно. Тем не менее, на странице профиля пользователя также должен быть список сообщений, в котором отображаются только сообщения от владельца профиля. Чтобы быть последовательным, страницу профиля пользователя следует изменить аналогично странице index. Я начинаю с обновления функции просмотра профиля пользователя, в которой по-прежнему имеется список временных сообщений. Чтобы получить список сообщений от пользователя, я воспользуюсь тем, что отношение user.posts является запросом, который уже настроен SQLAlchemy в результате определения db.relationship() в модели User. Я возьму этот запрос и добавлю order_by(), чтобы сначала получить самые новые сообщения, а затем сделаю разбивку на страницы точно так же, как я сделал для сообщений в index и explore. Обратите внимание, что ссылки на страницы, которые генерируются функцией url_for(), нуждаются в дополнительном аргументе username, поскольку они указывают на страницу профиля пользователя, которая имеет это username в качестве динамического компонента URL-адреса. В заключение, изменения в шаблоне user.html идентичны тем, которые я сделал на индексной странице: После того, как вы закончите экспериментировать с функцией разбивки на страницы, вы можете установить для элемента конфигурации POSTS_PER_PAGE более разумное значение: <<< предыдущая следующая >>>