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-выражение), чтобы потом можно было обрамить то, что будет лишним на статичных страницах, в один блок условного оператора.

Flask для начинающих




Все разделы сайта