Share This
Связаться со мной
Крути в низ
Categories
//Создаем Flask-приложение с базой данных. Веб-приложение для хранения информации о прочитанных книгах на Flask

Создаем Flask-приложение с базой данных. Веб-приложение для хранения информации о прочитанных книгах на Flask

Изучаем взаимодействие Flask с SQLAlchemy и WTForms, создавая веб-приложение — лайт-версию сервиса LiveLib.ru — для хранения информации о прочитанных книгах. Реализуем CRUD, пагинацию, фильтры и экспорт данных.

sozdaem flask prilozhenie s bazoj dannyh veb prilozhenie dlja hranenija informacii o prochitannyh knigah na flask 7ac3861 - Создаем Flask-приложение с базой данных. Веб-приложение для хранения информации о прочитанных книгах на Flask

Любое более-менее серьезное веб-приложение использует базу данных для хранения полученной от фронтенда информации. Для упрощения взаимодействия Flask-приложений с базой чаще всего используют библиотеку SQLAlchemy, а для получения и валидации данных пользователя – формы WTForms.

Обзор проекта

Это приложение для ведения списка прочитанных книг. Для каждой книги создается отдельная карточка с постером, именем автора, названием жанра, описанием сюжета, оценкой и примечаниями. Карточки можно редактировать и удалять. Весь код проекта находится здесь.

sozdaem flask prilozhenie s bazoj dannyh veb prilozhenie dlja hranenija informacii o prochitannyh knigah na flask 09953c2 - Создаем Flask-приложение с базой данных. Веб-приложение для хранения информации о прочитанных книгах на Flask

Готовое приложение AvidReader

Что мы изучим в процессе работы

  1. Узнаем, как создать базу данных и заполнить ее тестовыми данными из json-файла.
  2. Реализуем пагинацию и набор CRUD-операций для работы с карточками книг.
  3. Напишем пользовательский валидатор и обработаем ошибку IntegrityError.
  4. Сделаем несколько фильтров для обработки определенных категорий данных.
  5. Добавим в приложение простую (без JS) систему оценки книг.
  6. Рассмотрим способы работы с объектами данных в шаблонизаторе Jinja2.

Первый этап

Прежде всего создадим директорию для проекта, активируем виртуальное окружение и установим все необходимые зависимости с помощью менеджера pipenv:

         mkdir reader cd reader mkdir .venv pipenv shell pipenv install -r requirements.txt      

Структура готового приложения выглядит так:

         |   run.py |    ---reader     |   database.db     |   forms.py     |   models.py     |   routes.py     |   __init__.py     |        +---static     |   ---css     |           style.css     |                +---templates     |       base.html     |       best.html     |       book.html     |       create.html     |       edit.html     |       index.html     |       thrillers.html     |       _formhelpers.html     |            ---uploads      

Приступаем к работе

Приведенный ниже код отвечает за создание экземпляра Flask-приложения и объекта базы данных. Сохраните его в файле /reader/__init__.py:

/reader/__init__.py

         from flask import Flask from flask_sqlalchemy import SQLAlchemy import os app = Flask(__name__) basedir = os.path.abspath(os.path.dirname(__file__)) app.config['SQLALCHEMY_DATABASE_URI'] =     	'sqlite:///' + os.path.join(basedir, 'database.db') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SECRET_KEY'] = 'hard to guess' db = SQLAlchemy(app)      

Примечание: если вы планируете использовать другой тип базы данных – MySQL или PostgreSQL – URI должен выглядеть так:

         mysql://username:password@host:port/database_name  postgresql://username:password@host:port/database_name     

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста» Интересно, перейти к каналу

Модель базы данных

Теперь нужно создать модель (таблицу) в базе данных для хранения информации о книгах. Для этого сохраните приведенный ниже код в файле /reader/models.py:

 /reader/models.py

         from reader import app, db from sqlalchemy.sql import func class Book(db.Model): 	id = db.Column(db.Integer, primary_key=True) 	title = db.Column(db.String(100), unique=True, nullable=False) 	author = db.Column(db.String(100), nullable=False) 	genre = db.Column(db.String(20), nullable=False) 	rating = db.Column(db.Integer) 	cover = db.Column(db.String(50), nullable=False, default='default.jpg') 	description = db.Column(db.Text) 	notes = db.Column(db.Text) 	created_at = db.Column(db.DateTime(timezone=True),                            server_default=func.now())   	def __repr__(self):             return f'<Book {self.title}>'      

Значение cover по умолчанию равно default.jpg – это изображение надо заранее поместить в папку /reader/uploads/. Обратите внимание на один из атрибутов поля title, unique=True: это означает, что название книги должно быть уникальным. Если не предотвратить ввод дубликата (мы сделаем это позже во время валидации формы), работа приложения будет прервана ошибкой IntegrityError UNIQUE constraint failed.

Для запуска приложения создайте файл run.py:

run.py

         from reader import app if __name__ == '__main__': 	app.run(host='127.0.0.1', port=8000, debug=True)      

Все готово для создания базы данных – мы сделаем это в интерактивной консоли Flask:

         (.venv) D:reader>set FLASK_APP=run (.venv) D:reader>flask shell >>> from app import db >>> from reader import db >>> from reader.models import Book >>> db.create_all()      

Загляните в папку /reader – там появился файл базы, database.

Примечание: после создания таблицы ее структуру нельзя просто так изменить (добавить новый столбец, к примеру). Выход – воспользоваться расширением Flask-Migrate, либо, если данных в базе совсем мало и потерять их не жаль, выполнить:

         >>> db.drop_all() >>> db.create_all()      

Заполнение базы тестовыми данными

Интерактивная консоль позволяет добавлять записи в базу по одной:

         >>> book1 = Book(title = 'Преступление и наказание', ... 	author = 'Федор Достоевский', ... 	genre = 'драма', ... 	rating = '4', ... 	description = 'История Родиона Раскольникова.', ... 	notes = 'Невежество - мать всех преступлений.') >>> db.session.add(book1) >>> db.session.commit()      

Или по нескольку сразу:

         >>> book2 = Book(title = 'Нос', ... 	author = 'Николай Гоголь', ... 	genre = 'фэнтези', ... 	rating = '5', ... 	description = 'История сбежавшего носа.', ... 	notes = 'Без носа человек - черт знает что: птица не птица, гражданин не  гражданин, - просто возьми, да и вышвырни в окошко!') >>>  >>> book3 = Book(title = 'Мастер и Маргарита', ... 	author = 'Михаил Булгаков', ... 	genre = 'фэнтези', ... 	rating = '5', ... 	description = 'История о Дьяволе, искуплении и коте Бегемоте.', ... 	notes = 'Вздор! Лет через триста это пройдет.') >>> db.session.add(book2) >>> db.session.add(book3) >>> db.session.commit()      

Если сейчас выполнить запрос к базе, можно увидеть, что все три записи благополучно добавлены:

         >>> Book.query.all() [<Book Преступление и наказание>, <Book Нос>, <Book Мастер и Маргарита>]      

И первый, и второй способы добавления записей в базу, очевидно, занимают слишком много времени. Поэтому проще наполнить базу информацией из файла books.json:

         >>> from reader.models import Book >>> from reader import db >>> import json >>> with open('books.json', encoding="utf8") as f: ...   books_json = json.load(f) ...   for book in books_json: ... 	book = Book(author=book['author'], description=book['description'], genr e=book['genre'], rating=book['rating'], title=book['title'], notes=book['notes'] ) ... 	db.session.add(book) ... 	db.session.commit()     

В результате в базу было добавлено 7 новых записей:

         >>> Book.query.all() [<Book Преступление и наказание>, <Book Нос>, <Book Мастер и Маргарита>, <Book М изери>, <Book Замок Броуди>, <Book Облачный атлас>, <Book Пассажир>, <Book Голов окружение>, <Book Террор>, <Book Мизерере>] >>> exit()      

Основные маршруты и шаблоны

После наполнения базы можно приступать к функциям представления и шаблонам для вывода карточек. Сначала займемся маршрутом для главной страницы. Сохраните этот код в файле /reader/routes.py:

/reader/routes.py

         from reader import app from reader.models import Book   @app.route('/') def index(): 	books = Book.query.all() 	return render_template('index.html', books=books) @app.route('/uploads/<filename>') def send_file(filename): 	return send_from_directory(app.config['UPLOAD_FOLDER'], filename)      

Вторая функция обеспечивает отправку изображений (обложек книг) из директории /reader/uploads. Использование этой функции необходимо потому, что по умолчанию Flask ищет изображения только в директории static (и вложенных в нее папках). Указание на пользовательскую папку для загрузки изображений нужно добавить в __init__.py:

         UPLOAD_FOLDER = 'uploads' app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER      

Также в __init__.py надо сделать импорт модели и маршрутов:

         from reader import routes, models     

Кроме того, для вывода записей нужны два шаблона – base.html и index.html, а также файл со стилями CSS. Поместите их, соответственно, в папки /reader/templates/ и /reader/static/css. Все готово – можно запускать приложение:

         python run.py     

Пока что приложение выглядит так:

sozdaem flask prilozhenie s bazoj dannyh veb prilozhenie dlja hranenija informacii o prochitannyh knigah na flask e93902d - Создаем Flask-приложение с базой данных. Веб-приложение для хранения информации о прочитанных книгах на Flask

Пагинации и изображений пока нет

Оценка книги

Для вывода оценки книги используется простейший код в шаблоне, который печатает количество звездочек, соответствующее оценке в базе:

         {% set stars = book.rating | int %} {% for n in range(stars) %} <span class="fa fa-star checked" style="color:orange"></span> {% endfor %}      

Весь код, созданный на этом этапе – здесь.

Второй этап

Чтобы просматривать карточки книг, нужно сделать новый шаблон book.html, поместить файл illustration.jpg в /reader/uploads/ и добавить необходимый маршрут в routes.py:

routes.py

         @app.route('/<int:book_id>/') def book(book_id): 	book = Book.query.get_or_404(book_id) 	return render_template('book.html', book=book)       

Кроме того, нужно добавить необходимую динамическую ссылку в шаблон index.html:

         <a href="{{ url_for('book', book_id=book.id)}}">Подробнее</a>     

Карточка книги выглядит так:

sozdaem flask prilozhenie s bazoj dannyh veb prilozhenie dlja hranenija informacii o prochitannyh knigah na flask 660c4ec - Создаем Flask-приложение с базой данных. Веб-приложение для хранения информации о прочитанных книгах на Flask

Позже кнопки «Изменить» и «Удалить» станут функциональными

Фильтры и работа с объектами данных

SQLAlchemy делает фильтрацию данных простейшим делом. К примеру, вот так можно обеспечить вывод карточек книг в соответствии с датой добавления:

         books = Book.query.order_by(Book.created_at.desc()).all()     

Так же просто можно отобрать книги по определенному автору или жанру. Сделаем выборку по жанру «триллер»:

         @app.route('/thrillers/') def thrillers(): 	books = Book.query.filter(Book.genre == 'триллер').all() 	return render_template('thrillers.html', books=books)      

И по максимальной оценке 5:

         @app.route('/best/') def best(): 	books = Book.query.filter(Book.rating > 4).all() 	return render_template('best.html', books=books)      

Вставьте эти функции в /readers/routes.py и добавьте в папку templates шаблоны для вывода триллеров и лучших фильмов. В шаблон base.html нужно добавить ссылки для кнопок в верхнем меню:

           <a class="btn btn-info mr-2" href="{{ url_for('thrillers') }}" role="button">Триллеры</a>   <a class="btn btn-info mr-2" href="{{ url_for('best') }}" role="button">Лучшие</a>      

Теперь можно посмотреть на выборку по триллерам:

sozdaem flask prilozhenie s bazoj dannyh veb prilozhenie dlja hranenija informacii o prochitannyh knigah na flask ebe41e5 - Создаем Flask-приложение с базой данных. Веб-приложение для хранения информации о прочитанных книгах на Flask

Фильтр по триллерам

И по лучшим книгам:

sozdaem flask prilozhenie s bazoj dannyh veb prilozhenie dlja hranenija informacii o prochitannyh knigah na flask 51bb8c4 - Создаем Flask-приложение с базой данных. Веб-приложение для хранения информации о прочитанных книгах на Flask

Книги с оценкой 5

Обратите внимание: шаблонизатору Jinja2 не требуются никакие дополнительные ухищрения для работы с объектом данных, созданным в результате фильтрации: загрузка изображений и перенаправление на карточку книги не вызывают никаких проблем:

         {{ url_for('send_file', filename=book.cover) }} {{ url_for('book', book_id=book.id)}}      

Кроме того, к атрибутам объекта данных можно применять фильтры Jinja2. Этот фильтр обеспечивает вывод даты добавления книги в формате день-месяц-год:

         {{ book.created_at.strftime('%d-%m-%Y') }}     

По умолчанию же (без фильтра) дата будет выглядеть так:

         2022-06-25 14:17:53     

Пагинация

Последнее, что мы сделаем на этом этапе – постраничный вывод карточек. Реализовать пагинацию с помощью SQLAlchemy действительно просто. В начале файла /reader/routes.py необходимо добавить импорт request, а затем изменить функцию представления для index таким образом:

/reader/routes.py

         @app.route('/') def index(): 	page = request.args.get('page', 1, type=int) 	books = Book.query.order_by(Book.created_at.desc()).paginate(page=page, per_page=4) 	return render_template('index.html', books=books)      

В шаблон index.html нужно внести всего 2 дополнения – изменить books на books.items:

index.html 

         {% for book in books.items %}     

И добавить вывод номеров страниц в самом низу:

index.html 

         {% for page_num in books.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %}   	{% if page_num %}     	{% if books.page == page_num %}       	<a class="btn btn-info mb-4" href="{{ url_for('index', page=page_num) }}">{{ page_num }}</a>     	{% else %}       	<a class="btn btn-outline-info mb-4" href="{{ url_for('index', page=page_num) }}">{{ page_num }}</a>     	{% endif %}   	{% else %}     	...   	{% endif %} 	{% endfor %}      

Все готово:

sozdaem flask prilozhenie s bazoj dannyh veb prilozhenie dlja hranenija informacii o prochitannyh knigah na flask 5531b99 - Создаем Flask-приложение с базой данных. Веб-приложение для хранения информации о прочитанных книгах на Flask

Пагинация записей на главной странице

В шаблоны best.html и thrillers.html тоже нужно добавить пагинацию. Для этого необходимо внести изменения сначала в их функции представления, а потом и в сами шаблоны.

Функция для best.html выглядит так:

/reader/routes.py

         @app.route('/best/') def best(): 	page = request.args.get('page', 1, type=int) 	books = Book.query.filter(Book.rating > 4).paginate(page=page, per_page=4) 	return render_template('best.html', books=books)      

А для thrillers.html – так:

/reader/routes.py

         @app.route('/thrillers/') def thrillers(): 	page = request.args.get('page', 1, type=int) 	books = Book.query.filter(Book.genre == 'триллер').paginate(page=page, per_page=4) 	return render_template('thrillers.html', books=books)      

Дополнения в самих шаблонах аналогичны тем, что мы уже сделали в index.html – нужно изменить books на books.items и добавить блок вывода пагинации:

best.html и thrillers.html 

         {% for page_num in books.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %}   	{% if page_num %}     	{% if books.page == page_num %}       	<a class="btn btn-info mb-4" href="{{ url_for('thrillers', page=page_num) }}">{{ page_num }}</a>     	{% else %}       	<a class="btn btn-outline-info mb-4" href="{{ url_for('thrillers', page=page_num) }}">{{ page_num }}</a>     	{% endif %}   	{% else %}     	...   	{% endif %} 	{% endfor %}      

Весь код и тестовый контент, созданные на этом этапе, можно взять здесь. В следующей, заключительной части мы реализуем загрузку и автоматическое сжатие изображений, сделаем CRUD-операции и добавим возможность экспорта контента в json-формате.

Материалы по теме

  • 🐍🥤 Flask за час. Часть 1: создаем адаптивный сайт для GitHub Pages
  • 🐍🥤 Flask за час. Часть 2: завершаем разработку и размещаем сайт на GitHub Pages
  • 🐍🗄️ Управление данными с помощью Python, SQLite и SQLAlchemy

  • 0 views
  • 0 Comment

Leave a Reply

Ваш адрес email не будет опубликован.

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Свежие комментарии

    Рубрики

    About Author 01.

    blank
    Roman Spiridonov

    Моя специальность - Back-end Developer, Software Engineer Python. Мне 39 лет, я работаю в области информационных технологий более 5 лет. Опыт программирования на Python более 3 лет. На Django более 2 лет.

    Categories 05.

    © Speccy 2022 / All rights reserved

    Связаться со мной
    Close