Технологии и методы программирования. Учебное пособие
.pdfСоздадим общий шаблон (templates/base.html) с базовой структурой для всех HTML-файлов. В тегах <title> и <body> можно встроить HTML-блоки и заменить содержимое, так как информация на всех страницах будет разной. Пример:
<body>
{% block body %} {% endblock %} </body>
index.html после этого можно переписать следующим образом:
{% extends 'base.html' %}
{% block body %} <h1>Основная страница</h1> {% endblock %}
Для создания CSS-стилей создается папка static/css, в ней соответственно создается стандартный файл main.css. Язык CSS используется для оформления структурированных документов, например HTML-до- кументов. Синтаксис – это плоский список CSS-правил. CSS-правило состоит из селектора и перечня свойств и их значений:
селектор { свойство: значение;
свойство: значение;
}
Селектор находится в начале CSS-правила, до фигурных скобок, и определяет, к каким HTML-элементам применятся свойства и значения из правила. Простейшие (и самые популярные) селекторы – это селекторы по тегам и по классам. Селекторы по тегам содержат имя тега без символов < и > и применяются ко всем подходящим тегам. Селекторы по классам начинаются с точки, за которой идет имя класса, и применяютсяковсемтегам сподходящиматрибутомclass. Настраницеможет быть несколько списков, и стили применятся ко всем спискам, даже к тем, которые вы менять не хотели. Чтобы избежать таких ситуаций, лучше не использовать селекторы по тегам или использовать их как можно реже.
41
Если у CSS-правил отличаются только селекторы, а свойства и значения одинаковые, то их можно сгруппировать через запятую. Список свойств и значений находится внутри фигурных скобок CSS-правила. Свойство определяет, какую характеристику внешнего вида мы хотим изменить, а значение – как именно.
В main.css пропишем следующее (серый фон):
body { background: grey;
}
Файлmain.css подключаетсявbase.html (передтегом<title>) следующим образом:
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css')}}">
Для корректного отображения допишем первую строку в index.html: from flask import Flask, render_template, url_for
Вместо ручного прописывания CSS-файлов можно также использовать готовые стили – например, от Bootstrap:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
Работа с БД SQLite
ДляработысбазамиданныхтребуетсяимпортироватьSQLAlchemy: from flask_sqlalchemy import SQLAlchemy
После создания Flask-объекта подключим SQLite:
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
Создаем объект базы данных: db = SQLAlchemy(app)
42
Далее можно записать как один, так и несколько классов, каждый из которых будет выполнять роль определенной таблицы в базе данных. Для описания полей внутри таблицы необходимо прописать переменныевнутриклассов. Вкаждуюпеременнуюустанавливаетсяопределенное значение, что соответствует типу устанавливаемых данных в поле таблицы. Пример:
class Article(db.Model):
id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100), nullable=False) intro = db.Column(db.String(300), nullable=False) text = db.Column((db.Text), nullable=False)
date = db.Column(db.DateTime, default=datetime.utcnow)
В данном случае создается таблица «Статьи» с полями «Идентификатор» (является целым числом и первичным ключом – уникальным значением), «Название», «Вступление» (строковый тип, не может быть пустым), «Текст» (текстовый тип, не может быть пустым) и «Дата» (тип «дата» созначениемпоумолчанию«текущаядата»). Дляиспользования datetime.utcnow необходимо подключить datetime:
from datetime import datetime
При получении объекта из базы данных всегда возвращается идентификатор записи. С такой информацией мало что можно сделать, поэтому лучше в классе модели дописывать метод «__repr__». В нем можно указать, какое значение будет возвращается при получении объекта из БД:
def __repr__(self):
return '<Article %r>' % self.id
Для регистрации базы данных в проекте используется терминал PyCharm. Переходим в интерактивный режим:
flask shell
Импортируем объект: from app import db
43
Вызываем функцию создания: db.create_all()
Выходим из интерактивного режима: exit()
В итоге создан файл blog.db. Создадим файл create-article.html в папкеtemplates ипропишемформудлядобавлениястатьивбазуданных:
<div class="container mt-5"> <h1>Добавление статьи</h1> <form method="post">
<input type="text" name="title" id="title" class="form-control"><br>
<textarea name="intro" id="intro" class= "form-control"></textarea><br>
<textarea name="text" id="text" class= "form-control"></textarea><br>
<input type="submit" class="btn btn-success" value="Отправить"><br>
</form>
</div>
В основном файле отследим файл create-article.html и реализуем добавление статей:
@app.route('/create-article', methods=['POST', 'GET'])
def create_article():
if request.method == "POST": title = request.form['title'] intro = request.form['intro'] text = request.form['text']
article = Article(title=title, intro=intro, text=text)
try:
db.session.add(article)
44
db.session.commit() return redirect('/') except:
return "При добавлении возникла ошибка" else:
return render_template("create-article.html")
Здесь используются исключения. Исключения – еще один тип данных в языке Python. Исключения необходимы для того, чтобы сообщать программисту об ошибках. Рассмотрим иерархию встроенных в Python исключений, хотя иногда вам могут встретиться и другие, так как программисты могут создавать собственные исключения.
BaseException – базовое исключение, от которого берут начало все остальные.
SystemExit – исключение, порождаемое функцией sys.exit при выходе из программы.
KeyboardInterrupt – порождается при прерывании программы пользователем (обычно сочетанием клавиш Ctrl+C).
GeneratorExit – порождается при вызове метода close объекта
generator.
Exception – здесь заканчиваются полностью системные исключения (которые лучше не трогать) и начинаются обыкновенные, с которыми можно работать.
StopIteration – порождается встроенной функцией next, если
витераторе больше нет элементов.
ArithmeticError – арифметическая ошибка.
FloatingPointError – порождается при неудачном выполнении операции с плавающей точкой. На практике встречается нечасто.
OverflowError – возникает, когдарезультатарифметическойоперации слишком велик для представления. Не появляется при обычной работесцелымичислами(таккакPython поддерживаетдлинныечисла), но может возникать в некоторых других случаях.
ZeroDivisionError – деление на ноль.
AssertionError – выражение в функции assert ложно.
AttributeError – объект не имеет данного атрибута(значения или метода).
45
BufferError – операция, связанная с буфером, не может быть выполнена.
EOFError – функциядостиглаконцафайлаинесмоглапрочитать то, что хотела.
ImportError – не удалось импортирование модуля или его атри-
бута.
LookupError – некорректный индекс или ключ.
IndexError – индекс не входит в диапазон элементов.
KeyError – несуществующий ключ (в словаре, множестве или другом объекте).
MemoryError – недостаточно памяти.
NameError – не найдено переменной с таким именем.
UnboundLocalError – сделана ссылка на локальную переменную
вфункции, но переменная не определена ранее.
OSError – ошибка, связанная с системой.
BlockingIOError
ChildProcessError – неудача при операции с дочерним процес-
сом.
ConnectionError – базовый класс для исключений, связанных с подключениями.
BrokenPipeError
ConnectionAbortedError
ConnectionRefusedError
ConnectionResetError
FileExistsError – попытка создания файла или директории, которые уже существуют.
FileNotFoundError – файл или директория не существует.
InterruptedError – системный вызов прерван входящим сигна-
лом.
IsADirectoryError – ожидался файл, но это директория.
NotADirectoryError – ожидалась директория, но это файл.
PermissionError – не хватает прав доступа.
ProcessLookupError – указанного процесса не существует.
TimeoutError – закончилось время ожидания.
46
ReferenceError – попытка доступа к атрибуту со слабой ссылкой.
RuntimeError – возникает, когда исключение не попадает ни под одну из других категорий.
NotImplementedError – возникает, когда абстрактные методы класса требуют переопределения в дочерних классах.
SyntaxError – синтаксическая ошибка.
IndentationError – неправильные отступы.
TabError – смешивание в отступах табуляции и пробелов.
SystemError – внутренняя ошибка.
TypeError – операция применена к объекту несоответствующего
типа.
ValueError – функция получает аргумент правильного типа, но некорректного значения.
UnicodeError – ошибка, связанная с кодированием/раскодированием Unicode в строках.
UnicodeEncodeError – исключение, связанное с кодированием
Unicode.
UnicodeDecodeError – исключение, связанное с декодированием
Unicode.
UnicodeTranslateError – исключение, связанное с переводом
Unicode.
Warning – предупреждение.
Теперь, зная, когда и при каких обстоятельствах могут возникнуть исключения, мы можем их обрабатывать. Для обработки исключений используется конструкция try – except. Разберем пример с ZeroDivisionError:
try:
k = 1 / 0
except ZeroDivisionError:
print ("Деление на 0 запрещено")
В данном случае выдается сообщение о невозможности деления на ноль. В блоке try мы выполняем инструкцию, которая может породить исключение, а в блоке except мы перехватываем их. При этом перехватываются как само исключение, так и его потомки. Например,
47
перехватываяArithmeticError, мытакжеперехватываемFloatingPointError, OverflowError и ZeroDivisionError.
Следует отметить, что необходимо импортировать из Flask request и redirect. Объект request используется для получения доступа к входящим данным в Flask. В объекте request хранятся все входящие данные запроса, включая тип MIME, источник, IP-адрес, необработанные данные, метод HTTP, заголовки и др. Объект redirect при вызове возвращает объект ответа и перенаправляет пользователя в другое целевое местоположение.
Функция create_article проверяет метод передачи данных. Если методом является POST, то из формы HTML берутcя значения title, intro и text и создается article на основе класса Article. Далее производится попытка добавления article в базу данных при помощи session.add() и session.commit(). В случае корректной об-
работки происходит перенаправление, в обратном случае выводится ошибка.
Отображение данных из БД (модели)
Для взаимодействия с базой данных необходимо использовать модели. Модель представляет собой прямой проводник к определенной таблице в БД. Через модель можно создавать новые записи, обновлять уже существующие, удалять записи и получать записи для дальнейшего отображения в шаблонах.
Создадим файл templates/posts.html для отображения данных из таблицы базы данных. Чтобы получить записи из таблицы, нужно создать объект на основе вашей модели и далее обратиться к одной из многих доступных функций. Реализуем отображение статей:
@app.route('/posts') def posts():
articles = Article.query.order_by(Article.date.desc()).all()
return render_template("posts.html", articles=articles)
48
В этом случае производится запрос query к модели Article, который выдает все записи, отсортированные по дате от более новых к старым. Результат записывается в модель articles и передаётся на страницу posts.html. В файле posts.html пропишем следующую конструкцию:
{% for el in articles %}
<div class="alert alert-info"> <h2>{{ el.title }}</h2>
<p>{{ el.intro }}</p>
<p><b>Дата: {{ el.date.date() }}</b></p>
<a href="/posts/{{ el.id }}" class="btn btn-warn- ing">Детали</a>
</div>
{% endfor %}
Здесь в цикле по всем элементам articles выводятся title, intro и date в HTML-документ. Присутствует также ссылка типа /posts/ID для отображениядеталей. Пропишем восновномдокументеперенаправлениена данные страницы:
@app.route('/posts/<int:id>') def post_detail(id):
article = Article.query.get(id)
return render_template("post_detail.html", article=article)
Здесь в функции post_detail при помощи get() берется запись с определенным идентификатором. Она передается в файл post_detail.html. Создадим его и пропишем конструкцию для отображения:
{% block title %} {{ article.title }} {% endblock %}
{% block body %}
<div class="container mt-5"> <h1>{{ article.intro }}</h1>
49
<div class="alert alert-info"> <p>{{ article.text }}</p>
<p><b>Дата: {{ article.date.date() }}</b></p> </div>
</div>
{% endblock %}
Функции удаления и обновления
Для удаления и обновления записи добавим следующие ссылки:
<a href="/posts/{{ article.id }}/del" class= "btn btn-danger">Удалить</a>
<a href="/posts/{{ article.id }}/update" class="btn btn-success">Редактировать</a>
Пропишем в основном документе удаление записи:
@app.route('/posts/<int:id>/del') def post_delete(id):
article = Article.query.get_or_404(id)
try:
db.session.delete(article)
db.session.commit() return redirect('/posts') except:
return "При удалении возникла ошибка"
Здесь в функции post_delete при помощи get_or_404() (в случае, еслизаписьнесуществует, выдаетсяошибка404) беретсязапись с определенным идентификатором. Далее производится попытка удаления article из базы данных при помощи session.delete() и session.commit(). В случае корректной обработки происходит перенаправление, в обратном случае выводится ошибка.
Пропишем обновление записи:
@app.route('/posts/<int:id>/update', methods=['POST', 'GET'])
50
