Разработка и создание веб-приложения на Python и Flask с базой данных
В заключительной части: реализуем набор операций для создания, редактирования и удаления записей; обеспечиваем автоматическое сжатие загружаемых обложек до нужного размера с помощью Pillow. Первая часть 🐍📚 Создаем аналог LiveLib.ru на Flask. Часть 1: основы работы с SQLAlchemy В заключительной части туториала мы рассмотрим основные аспекты работы с формами WTForms, разработаем функции CRUD и напишем функцию для экспорта информации из базы данных. Код для первого и второго этапов разработки есть здесь. Для получения пользовательских данных со стороны фронтенда в Flask-приложениях обычно используют формы WTForms. Эти формы «из коробки» предоставляют отличные опции для валидации введенных данных. Расширение функциональности форм за счет макросов и дополнительных валидаторов тоже не вызывает никаких сложностей, как мы это увидим позже. Для взаимодействия с приложением нам потребуются две формы – BookForm и UpdateBook. Первая отвечает за создание новой карточки, вторая – за редактирование существующей записи. Обложки книг можно загружать как во время создания карточки, так и после – в процессе редактирования. Обратите внимание на формат использования валидаторов: Кроме стандартных валидаторов, мы будем использовать один пользовательский: /reader/forms.py Этот валидатор предотвращает создание карточки с названием книги, которое уже есть в базе. Без него приложение остановится с ошибкой: Название книги оказалось неуникальным Эта ошибка связана с атрибутом Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста» Интересно, перейти к каналу Формам для создания и редактирования записей нужны шаблоны. Здесь есть код для create.html и edit.html. Шаблон create.html предусматривает вывод ошибок: create.html Для ввода и обработки разных типов информации используются различные классы: В шаблоне edit.html используется предварительное заполнение формы, уже внесенной в карточку информацией, – чтобы пользователю было проще ее редактировать. Эту информацию отправляет в форму функция edit.html Примечание: для создания и редактирования записей при желании можно использовать один и тот же шаблон. Если вставить в шаблон edit.html код из create.html, он точно так же получит существующие данные из функции представления. Кроме того, часто для редактирования и создания записей используют один и тот же код в одном и том же файле. В данном случае мы используем рендеринг формы редактирования с помощью макроса из _formhelpers.html просто в образовательных целях. Для создания, редактирования и удаления записей нам нужно написать соответствующие функции в файле routes.py. Кроме того, нужно сделать функцию для безопасной загрузки изображений. Начнем с импорта форм и модуля Pillow, который обеспечит автоматическое сжатие обложек и сохранение файлов в нужную папку uploads: routes.py Фласку тоже потребуется импорт дополнительных модулей – flash, url_for и redirect. Pillow нужны модули os и secrets, а для обработки ошибок базы понадобится IntegrityError. Функция для обработки и сохранения обложек книг выглядит так: routes.py Функция создания новых записей выглядит так: routes.py Если во время создания новой записи обложка не будет загружена, в карточке будет отображаться изображение по умолчанию. Как видно по приведенному выше коду, в базе данных сохраняется только имя файла изображения, сами картинки загружаются в папку uploads. Функция для редактирования отправляет в форму уже существующие в карточке данные, чтобы форма не была пустой, и обрабатывает ошибку routes.py Удалить карточку книги проще простого: routes.py Для подтверждения удаления карточки будет использоваться всплывающее окно – код для этого уже есть в шаблонах. Осталось добавить в base.html, index.html, book.html, best.html и thrillers.html ссылки на соответствующие операции по созданию, редактированию и удалению карточек: Теперь карточки можно удалять: Удаляем Достоевского Редактировать: Редактируем информацию И создавать: Создаем новую запись Информацию из наполненной базы данных можно экспортировать самыми разными способами. Мы рассмотрим простой и практичный метод, который не требует установки никаких дополнительных модулей. Файл json, который мы использовали в первой части туториала, был создан именно таким способом. Для реализации метода нужно внести небольшое дополнение в файл моделей /reader/models.py и написать функцию для /reader/routes.py. Сначала дополним класс /reader/models.py Эти данные будут экспортированы. Если не нужен ID записи, или время создания, или еще что-нибудь – соответствующие строки можно удалить. Добавим импорт /reader/routes.py В файл __init__.py добавим параметр Все готово: если перейти по адресу http://localhost:8000/export/, можно увидеть все содержимое базы в виде словаря: Данные экспортированы На этом работа над приложением закончена. Очевидно, что функциональность SQLAlchemy значительно упростила процесс разработки. И хотя SQLAlchemy – не единственная библиотека, предоставляющая Flask-приложениям все преимущества ORM, ее уверенно можно назвать самой понятной и гибкой, чем и объясняется ее популярность. Напоминаем, что финальная версия кода находится здесь.Третий этап
Валидация форм WTForms
def validate_title(self, title): title = Book.query.filter_by(title=title.data).first() if title: raise ValidationError('Такая книга уже есть в списке прочитанных.')
unique=True
столбца title
и ее проще предотвратить, чем обрабатывать. Но обработку мы тоже рассмотрим ниже – она будет реализована в функции представления.Шаблоны для форм
{% if form.genre.errors %} {% for error in form.genre.errors %} <span class="text-danger">{{ error }}</span></br> {% endfor %} {% endif %}
edit
, которую мы рассмотрим чуть позже. Для генерации шаблона нужен вспомогательный макрос – он находится в файле formhelpers.html. Этот файл необходимо поместить в папку /templates вместе с обычными шаблонами. В шаблоне для редактирования есть блок для вывода сообщений об ошибках базы данных, получаемых от Flask и SQLAlchemy (остальные ошибки валидации обрабатывает WTForms):
{% with messages = get_flashed_messages() %} {% if messages %} {% for message in messages %} <span class="text-danger">{{ message }}</span> {% endfor %} {% endif %} {% endwith %}
Обработка данных из форм
from reader.forms import BookForm, UpdateBook from PIL import Image
def save_picture(cover): random_hex = secrets.token_hex(8) _, f_ext = os.path.splitext(cover.filename) picture_fn = random_hex + f_ext picture_path = os.path.join(app.root_path, app.config['UPLOAD_FOLDER'], picture_fn) output_size = (220, 340) i = Image.open(cover) i.thumbnail(output_size) i.save(picture_path) return picture_fn
@app.route('/create/', methods=('GET', 'POST')) def create(): form = BookForm() if form.validate_on_submit(): if form.cover.data: cover = save_picture(form.cover.data) else: cover ='default.jpg' title = form.title.data author = form.author.data genre = form.genre.data rating = int(form.rating.data) description = form.description.data notes = form.notes.data book = Book(title=title, author=author, genre=genre, rating=rating, cover=cover, description=description, notes=notes) db.session.add(book) db.session.commit() return redirect(url_for('index')) return render_template('create.html', form=form)
IntegrityError
– она возникает, если в процессе редактирования существующей карточки пользователь вводит название книги, которое уже есть в базе:
@app.route('/<int:book_id>/edit/', methods=('GET', 'POST')) def edit(book_id): book = Book.query.get_or_404(book_id) form = UpdateBook() if form.validate_on_submit(): if form.cover.data: cover = save_picture(form.cover.data) else: cover = book.cover book.title = form.title.data book.author = form.author.data book.genre = form.genre.data book.rating = int(form.rating.data) book.description = form.description.data book.notes = form.notes.data try: db.session.commit() return redirect(url_for('index')) except IntegrityError: db.session.rollback() flash('Произошла ошибка: такая книга уже есть в базе', 'error') return render_template('edit.html', form=form)
@app.post('/<int:book_id>/delete/') def delete(book_id): book = Book.query.get_or_404(book_id) db.session.delete(book) db.session.commit() return redirect(url_for('index'))
{{ url_for('create') }} {{ url_for('edit', book_id=book.id) }} {{ url_for('delete', book_id=book.id) }}
Экспорт данных из базы
Book
:
from dataclasses import dataclass @dataclass class Book(db.Model): id: int title: str author: str genre: str cover: str rating: int description: str notes: str created_at: str
jsonify
и функцию экспорта в /reader/routes.py:
@app.route('/export/') def data(): data = Book.query.all() return jsonify(data)
app.config['JSON_AS_ASCII'] = False
– иначе jsonify вместо кириллицы экспортирует абракадабру.Материалы по теме
- 3 views
- 0 Comment
Свежие комментарии