Flask-приложение с открытием статей в режиме редактирования и просмотра
На данном этапе наша разработка имеет два python-скрипта (admin.py и project.py), один из которых запускает сайт в режиме редактирования страниц, другой ‒ их просмотра. Наша задача ‒ свести два приложения в одно.
Так как в project.py кода меньше, потом будет проще перенести нужное оттуда в admin.py. Сейчас же admin.py переименуем в app.py (можно вручную) и запустим сайт:
$ mv admin.py app.py $ flask run --debug
В файле app.py функцию-представление page переименуем в edit_page. Также изменится путь в роутере. Путь страницы в режиме редактирования будет иметь префикс /edit. Меняем
@app.route('/<path:file_name>', methods=['GET', 'POST']) def page(file_name):
на
@app.route('/edit/<path:file_name>', methods=['GET', 'POST']) def edit_page(file_name):
Теперь скопируем из файла project.py функцию page. Также потребуется импортировать класс Markup
.
Чтобы отображались изображения, скопируем также функцию load_img, импортируем send_from_directory
.
После этого страницы на сайте будут открываться в режиме просмотра. Чтобы отредактировать их, надо в адресной строке вручную вписать edit/ перед путем страницы. Например: http://127.0.0.1:5000/edit/botany/forms . Позже мы создадим специальную ссылку для перехода на страницу редактирования.
На главной сайта по прежнему открывается форма для создания новой статьи. Переименуем функцию index в файле app.py в create. Меняем
@app.route('/', methods=['GET', 'POST']) def index():
на
@app.route('/create', methods=['GET', 'POST']) def create():
После этого из файла project.py скопируем функцию index. Таким образом на главной странице будет статья из шаблона, а для создания новой статьи надо будет перейти по адресу http://127.0.0.1:5000/create .
Напоследок заберем функцию service_files и удалим project.py. Также можно удалить файл с расширением wsgi, если он имеется, так как в этой версии сайта удаленный сервер будет настраиваться по-другому. Мы не будем там запускать flask-приложение.
Для открытия статей в режиме редактирования добавим ссылки в шаблон page.html над заголовком статьи:
<div style="background-color:rgba(57,102,95,0.14);height:30px;padding:5px 10px;"> <a href="{{url_for('edit_page', file_name=address[1:])}}" style="float:left;">Править эту статью</a> <a href="{{url_for('create')}}" style="float:right;">Создать новую</a> </div>
Этот блок кода не будет включаться в статичную html-страницу, так как он там не нужен. Поэтому позже мы обрамим его условным оператором.
Удостоверьтесь, что в коде программы при рендере шаблона page.html передается аргумент address:
return render_template('page.html', address='/' + p['path'], title=p['h1'], desc=p['short_desc'], body=Markup(p['body']))
Теперь на всех страницах сайта, кроме главной, появятся две ссылки. С помощью одной будет открываться на редактирование текущая страница. По другой ‒ создаваться новая статья.
При открытии страницы в режиме редактирования пункт раздела меню не разворачивается и ссылка на страницу там не подсвечивается.
Это происходит потому, что код в script.js не обрабатывает путь с префиксом edit/. Как исправить эту недоработку, зависит от макета вашего сайта. Далее приводится описание в приложении к данному.
Поскольку в статичном варианте сайта не будет страниц редактирования, то не стоит менять/дописывать код в исходном script.js. Вместо этого в каталоге templates создадим подкаталог edit-scripts и в нем новый файл script.js с таким кодом:
const thePath_edit = window.location.pathname const docName = thePath_edit.substring(thePath_edit.indexOf('/', 1)) const d = document.querySelector(`li > a[href="${docName}"]`) if (d != null) { d.parentNode.style.background="#bcece2" const parents = d.parentNode.parentNode.parentNode parents.style.display = 'block' parents.previousElementSibling.classList.toggle('active') d.scrollIntoView({ behavior: "smooth", block: "center" }); }
Фактически он дублирует подобный из оригинального script.js, но предварительно "отрезает" приставку edit/.
Созданный нами скрипт подключим только к шаблону create.html в конце документа перед закрывающим {% endblock %}
:
<script src="/edit-scripts/script.js"></script>
Также в app.py добавим функцию для перенаправления этого адреса на нужную нам директорию:
@app.route('/edit-scripts/<script>') def load_edit_scripts(script): return send_from_directory('templates/edit-scripts', script)
После этого меню должно разворачиваться и в режиме редактирования и в режиме просмотра.
Если сейчас отредактировать существующую статью, то при ее сохранении снова загрузится страница с формой. Хотя ожидаемо, что нас должно "перебрасывать" на страницу просмотра статьи. Чтобы исправить этот недочет, надо внести некоторые исправления в функцию edit_page.
Когда данные из формы редактирования отправляются методом POST, то выполняется их проверка. Если было сделано что-то недопустимое (например, передан пустой заголовок), то должна снова загружаться страница с формой редактирования. В данном случае не надо перенаправлять на страницу просмотра. Поэтому в конце ветки, обрабатывающей POST-запрос, мы не можем просто заменить
return render_template('create.html')
на
return redirect(url_for('page', file_name=file_name))
Может потребоваться и то, и то, а значит, надо использовать условный оператор.
if success: return redirect(url_for('page', file_name=file_name)) else: return render_template('create.html')
Переменной success еще нет. Определим ее вначале ветки, обрабатывающей POST, сразу после определения переменной updated.
success = True
Исходно мы предполагаем, что редактирование статьи пройдет успешно. Осталось поменять значение success на False
в тех местах кода, когда исправленная статья не проходит проверку на корректность.
Во-первых, когда заголовок пуст:
if request.form['h1'] == '': flash('Заголовок не может быть пустым!', 'error') success = False
Во вторых, когда строка адреса пуста. В третьих, когда адрес был изменен, но такой путь уже есть в базе данных для другой страницы.
if request.form['path'] == '': flash('Строка адреса не может быть пуста!', 'error') success = False elif request.form['path'] != p['path']: if not test_path(request.form['path']): flash('Страница с таким адресом уже есть!', 'error') success = False else: ...
Теперь обратим внимание на то, что когда происходит перенаправление на страницу просмотра, мы не видим там flash-сообщения, так как их вывод предусмотрен только в шаблоне create.html, но не page.html.
Поскольку оба наследуют от base.html перенесем сюда код, связанный с выводом сообщений:
.error { color: red; text-align: center; } .success { color: green; text-align: center; } … {% for category, message in get_flashed_messages(with_categories=true) %} <p class="{{ category }}">{{ message }}</p> {% endfor %}
Стили надо поместить в контейнер <style>
и над блоком {% block content %}
разместить все вместе (и стили и jinja-выражение), чтобы потом можно было обрамить то, что будет лишним на статичных страницах, в один блок условного оператора.