Flask не стоит на месте. Решил дополнить пример обработки формы при помощи AJXA. Я использую данный код для обработки формы заказа звонка на сайтах. Приведу список requirements.txt (для гарантии работы кода лучше использовать его). Версия Python 3.8.2.
Установка Flask и библиотек
# настройка виртуального окружения python, всё как всегда :)
python3 -m venv venv
# активация окружения
source venv/bin/activate
# обновления pip
pip install pip --upgrade
# установка всего необходимого
pip install -r requirements.txt
Flask==1.1.2
Flask-WTF==0.14.3
Подготовка
Необходимо создать структуру проекта, лучше сделать для примера как я привел ниже:
# структура проекта
.
├── app.py
├── forms.py
├── requirements.txt
└── templates
├── base.html
└── index.html
Создать директорию templates, и два файла в ней base.html и index.html.
Так же создаю файл с index.html который расширяет base.html.
Строки 3-8 содержат форму.
Строка 10 содержит div с id=»msg» в него будет записан ответ с сервера.
Сроки 13-18 код который связывает события «submit» кнопки из формы, с функцией отправки данных через ajax, в качестве параметра передается id формы и id элемента для записи ответа (без #). В данном случаи «form1» и «msg». event.preventDefault() — отменяет стандартное поведение.
Строки 23-45 функция sendAjaxForm отправляет данные формы (form1).
Что бы код можно было использовать для различных форм, я сделал передачу параметром формы и сообщения.
Итак, строка 23 просто получаем объект формы по id (#frim1).
Строка 25 type должен быть GET или POST это метод, беру его из параметров формы (method).
Аналогична строка 26 url должен содержать адрес для обработки запроса, в данном случаи так же берётся из формы (action). (её обрабатывает на сервере функция send см. файл app.py строка 22).
Строка 27 данные формы готовятся к отправки на сервер.
Строки 28 если всё прошло успешно, получен ответ от сервера он будет в response.
Строка 29 преобразую данные в объект json для удобства работы.
Строка 30 вывожу ответ в div с id=msg.
Строки 31-33 — успех, очищаю форму.
Строки 34-38 — что то не так, ошибка.
Строки 40-42 — если ошибки в запросе на сервер, нет связи, ошибка в url и т. д.
С HTML и JavaScript всё 🙂
Форма Flask файл forms.py
Строки 8-9 параметр DataRequired говорит о том, что они обязательны для заполнения. Length — размер не меньше 4 знаков.
В старых версиях wtf вместо StringField был TextField, сейчас лучше писать StringField. В версии 3.0 TextField перестанет работать.
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired
from wtforms.validators import Length
class ContactForm(FlaskForm):
name = StringField('Имя', validators=[DataRequired()])
phone = StringField('Телефон', validators=[DataRequired(), Length(min=4)])
Файл app.py сам проект на Flask
Строка 6 форма обратной связи.
Строка 21 декоратор обработки url «/send».
Строка 22 функция отправки «сообщения».
Строка 24 если валидация успешна, возвращает json с положительным ответом (строка 27) иначе с отрицательным (строка 30).
from flask import Flask
from flask import request
from flask import render_template
import json
# форма для обратной сзвязи
from forms import ContactForm
app = Flask(__name__)
app.config['SECRET_KEY'] = "12345"
@app.route('/', methods=['GET', 'POST'])
def index():
form = ContactForm()
return render_template("index.html",
title="index page",
form=form)
@app.route('/send', methods=['POST'])
def send():
form = ContactForm()
if request.method == "POST":
if form.validate_on_submit():
# отправить почту, записать в БД и т. д.
return json.dumps({'success': 'true', 'msg': 'Ждите звонка!'})
else:
# обработать ошибку
return json.dumps({'success': 'false', 'msg': 'Ошибка на сервере!'})
if __name__ == '__main__':
app.run(debug=True)
Запуск app.py
python3 app.py
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 334-159-801
Вездесущий спам — читал где то, что одно время 80% почтового трафика это был SPAM.
Сейчас уже почтового спама не так много доходит до нас (просто средства борьбы с ним стали лучше).
Я столкнулся со спамом в формах обратной связи на сайтах, да и вообще в формах которые на сайтах позволяют писать и отправлять сообщения.
С годами, образовалось большое количество сайтов, и клиенты стали жаловаться на спам, конечно при разработке были сделаны простые защиты (скрытое поле и/или 2+2 и т. д. и т. п.) так же был опыт использования reCAPTCHA v2 — но были свои нюансы.
reCAPTCHA v2 считаю, что это хорошее решение. Да и ещё оно работает из коробки с flask-wtf примерно с 2017 года. Раньше нужно было «колхозить».
Решил написать для себя простой шаблон, ничего лишнего, форма обратной связи в контактах сайта защищенная reCAPTCHA. Мне легче использовать такую выжимку из проекта, чем копаться в большом готовом проекте с кучей библиотек. Здесь нечего лишнего, бери и используй.
Что понадобиться?
Ключи от google публичный и приватный. (Я создал для хоста 127.0.0.1 возможно для теста подойдут они. А так нужно при создании указывать домен на котором будет сайт с капчей.)
Содержимое файла index.html (в директории templates). Строка 1 указывает какой шаблон будет расширен текущим. Строка 5 будет содержать сообщение об успехе или провале валидации. Собственно в 11 строке содержится recaptcha.
Следующий файл forms.py (корень проекта). Строка 8 содержит поле комментарий DataRequired обозначает обязательно для заполнения. Строка 9 recaptcha поле с «галочкой».
from flask_wtf import FlaskForm
from flask_wtf import RecaptchaField
from wtforms import TextField
from wtforms.validators import DataRequired
class ContactForm(FlaskForm):
text = TextField('Комментарий', validators=[DataRequired()])
recaptcha = RecaptchaField()
И наконец самый главный файл приложения app.py (корень проекта).
Строка 5 — ранее описанная форма.
Строка 10, 11 — ключи которые нужно получить в google используя аккаунт google.
Строка 16 — форма для передачи в шаблон и проверки валидности.
Строка 18 — если метод POST значит была нажата в форме кнопка submit и соответственно нужно проверить полученные данные на валидность.
Строка 19 — проверка данных на валидность.
Если валидация пройдена, то msg примет значение «Успех!» иначе «Ошибка валидации».
from flask import Flask
from flask import request
from flask import render_template
# форма с валидацией и капчей
from forms import ContactForm
app = Flask(__name__)
app.config['SECRET_KEY'] = "12345"
# ключи recaptcha от google
app.config['RECAPTCHA_PUBLIC_KEY'] = "6Ld74-oUAAAAAJC0UOY6PtrOrNcxQ2VQCfGAqBOC"
app.config['RECAPTCHA_PRIVATE_KEY'] = "6Ld74-oUAAAAAD2_Jl2IVKh2uCCI9OPX_7oTdLz4"
@app.route('/', methods=['GET', 'POST'])
def index():
form = ContactForm()
msg = ""
if request.method == "POST":
if form.validate_on_submit():
msg="Успех!"
# отправить почту, записать в БД
else:
msg="Ошибка валидации"
# обработать ошибку
return render_template("index.html",
title="index page",
form=form,
msg=msg)
if __name__ == '__main__':
app.run(debug=True)
Запуск проекта:
python3 app.py
В видео видно, что если ошибиться несколько раз, будет показана капча и просто «галочкой» уже не отделаться.
В заключении
Я расписал подробно шаги для того, что бы запомнить самому, делать всё на автомате. В будущем буду экономить время и не лениться добавлять валлидацию там где нужно. Это не переводная статья и не куски документации один к одному — это то что я использую в своих проектах.
В новой версии flask-wtf появиться параметр RECAPTCHA_DISABLE который позволит красиво отключать и включать проверку капчи.
А как быть, если нужно отключить проверку капчи сейчас?
Вообще это одна из проблем, почему мне не нравилось использовал данную капчу, решение пока у меня такое, создавать две одинаковые формы с reCAPTCHA и без. Пример кода ниже forms.py и app.py. Выделил строки которые поменялись.
Файл forms.py
from flask_wtf import FlaskForm
from flask_wtf import RecaptchaField
from wtforms import TextField
from wtforms.validators import DataRequired
class ContactForm(FlaskForm):
text = TextField('Комментарий', validators=[DataRequired()])
class ContactRecaptchaForm(ContactForm):
recaptcha = RecaptchaField()
Файл app.py
from flask import Flask
from flask import request
from flask import render_template
app = Flask(__name__)
app.config['SECRET_KEY'] = "12345"
# ключи recaptcha от google
app.config['RECAPTCHA_PUBLIC_KEY'] = "6Ld74-oUAAAAAJC0UOY6PtrOrNcxQ2VQCfGAqBOC"
app.config['RECAPTCHA_PRIVATE_KEY'] = "6Ld74-oUAAAAAD2_Jl2IVKh2uCCI9OPX_7oTdLz4"
app.config['RECAPTCHA_DISABLE'] = True # будет капча или нет
# форма с валидацией и капчей или без неё.
# в новой версии flask-wtf планируется сделать RECAPTCHA_DISABLE красиво
# из коробки, это временное решение
if app.config['RECAPTCHA_DISABLE'] == True:
from forms import ContactRecaptchaForm as ContactForm
else:
from forms import ContactForm
@app.route('/', methods=['GET', 'POST'])
def index():
form = ContactForm()
msg = ""
if request.method == "POST":
if form.validate_on_submit():
msg="Успех!"
# отправить почту, записать в БД
else:
msg="Ошибка валидации"
# обработать ошибку
return render_template("index.html",
title="index page",
form=form,
msg=msg)
if __name__ == '__main__':
app.run(debug=True)
Готовый код с примером Flask + reCAPTCHA v2 Google
Книга оказалась очень полезная, в ней можно найти практический всё, что нужно. Подойдет для поиска подходящего модуля из стандартной библиотеки под задачу. Читать её от корки до корки нет смысла, всё же это справочник. Но я узнал из неё много интересного.
Книга огромная, читать (смотреть её) удобно только за столом.
Описывает относительно современную версию языка Python3.6 и 3.7. Материал из книги будет актуален ещё несколько лет. Если 2.7 версию использовали столько лет (и будет ещё тянуть старые проекты) то приобретение данный книги, если вы в профессии оправданно.
Оглавление
Глава 1. Текст Глава 2. Структуры данных Глава 3. Алгоритмы Глава 4. Дата и время Глава 5. Математика Глава 6. Файловая система Глава 7. Постоянное хранение и обмен данными Глава 8. Сжатие и архивирование данных Глава 9. Криптография Глава 10. Параллельные вычисления: процессы, потоки и сопрограммы Глава 11. Обмен данными по сети Глава 12. Интернет Глава 13. Электронная почта Глава 14. Строительные блоки приложений Глава 15. Интернационализация и локализация приложений Глава 16. Инструменты разработки Глава 17. Инструменты среды времени выполнения Глава 18. Инструменты языка Глава 19. Модули и пакеты Приложение А. Замечания относительно портирования программ Приложение Б. Внешние ресурсы, дополняющие стандартную библиотеку Указатель модулей Python Предметный указатель
Хочу написать отзыв на книгу по CSS. Книга мне понравилась, купил в бумаге. Хорошо написана. В последнее время очень сильно WEB технологии разогнались. Но сейчас не об этом. Мне нужно было самостоятельно приступить к вёрстке крупного проекта. Информация которой я владел была неструктурированная, надёргана тут и там, в добавок устаревшая. Поверхностное владение Flex и Grid. В итоге решил купить книгу, что бы привести знания в порядок и использовать современный подход на сколько позволяет книга (информация в наше время очень быстро устаревает).
Книга написана легко, перевод меня устроил, читать её в удовольствие. Но и как все книги подобного рода, требуют сразу переменить полученные знания на практике. Описаны WebPack и адаптивная вёрстка, Flex и Grid, использование шрифтов и многое другое. Минимум воды, если есть отступления автора то они к месту. Если CSS нужен по работе, определённо стоит обратить на данную книгу внимание.
Оно реально стоит того? В моём случаи да. Пусть даже если посещаемость сайта не 10к в минуту, мне нравиться, что я отдаю css и js минимального размера. Google page speed рад этому.
Важнейший для меня момент: после редактирования файла css (js) он часто продолжает грузиться из кэша, что мешает, и добавляет проблем при обновление сайта, приходилось переименовывать вручную, так же править путь в шаблоне, использование Flask-Static-Compress решает этот вопрос. Короче это удобно когда сайт уже в интернете.
Использовать очень просто:
Наконец для всех библиотек на Python3.6+ появилось всё, что мне нужно, так же свежий Python 3.6+ и 3.7+ доступен на хостинге который я использую. Виртуальное окружение в новых «питонах» создаю так:
Хочу поделится как я считаю хорошим решением динамической подгруздки в select.
Рассмотрю пример на категория + под категория. При выборе категории будут подгружается под категории.
Использовать для этого буду Flask, AJAX, jQuery, SQLite.
Подгружаться данные будут с помощью AJAX и Json.
Для краткости python код будет в app.py, а шаблон в template/index.html
Структура проекта:
app.py
template/index.html
$ python app.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
...
Если всё завилось то продолжаем.
Необходимо где то хранить список категорий и под категорий. Для этого подойдет база SQLite. Работать с ней будем через SQLAlchemy.
Отредактируем app.py и приведём его к виду:
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import String
from sqlalchemy import Integer
from sqlalchemy import Column
from sqlalchemy import ForeignKey
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.sqlite'
db = SQLAlchemy(app)
# категория
class Category(db.Model):
__tablename__ = 'category'
id = Column(Integer, primary_key=True)
name = Column(String(1024))
def __repr__(self):
return '<Category %s>' % self.name
def __unicode__(self):
return self.name
# под категория
class SubCategory(db.Model):
__tablename__ = 'sub_category'
id = Column(Integer, primary_key=True)
name = Column(String(1024))
category_id = db.Column(Integer, ForeignKey('category.id'))
category = db.relationship("Category", backref="Ctegory.id")
def __repr__(self):
return '<SubCategory %s>' % self.name
def __unicode__(self):
return self.name
# создание таблиц
db.create_all()
# заполнение базы
# если записи отсутствуют
if len(Category.query.all()) is 0:
for name in ['фрукты', 'напитки', 'молочные продукты']:
category = Category(name=name)
db.session.add(category)
db.session.commit()
if len(SubCategory.query.all()) is 0:
for name in ['апельсины', 'яблоки', 'груши']:
sub_category = SubCategory(category_id=1, name=name)
db.session.add(sub_category)
for name in ['сок', 'вода', 'газировка']:
sub_category = SubCategory(category_id=2, name=name)
db.session.add(sub_category)
for name in ['молоко', 'сметана', 'масло']:
sub_category = SubCategory(category_id=3, name=name)
db.session.add(sub_category)
db.session.commit()
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
Это только создаст БД и таблицы в ней. Ещё нам понадобится сама форма для отображения выбора. Здесь я решил не использовать Bootstrap и обойтись простой формой.
В тело index.html необходимо добавить следующий код:
...
Пример работы выбора категории и под категории.
<form method="post" name="form" action="/">
{{ form.csrf_token }}
{{ form.category.label }}
{{ form.category }}
{{ form.sub_category.label }}
{{ form.sub_category }}
<input type="submit" value="Отправить">
</form>
...
Для отображения формы необходимо добавить в app.py импорт библиотек и сам класс формы.
...
from flask_wtf import Form
from wtforms import SelectField
...
class FormCategory(Form):
category = SelectField(u'Категория', coerce=int)
sub_category = SelectField(u'Под категория', coerce=int)
def __init__(self, *args, **kwargs):
super(FormCategory, self).__init__(*args, **kwargs)
self.category.choices = \
[(g.id, u"%s" % g.name) for g in Category.query.order_by('name')]
# выбранное поле по умолчанию
self.category.choices.insert(0, (0, u"Не выбрана"))
self.sub_category.choices = list()
# выбранное поле по умолчанию
self.sub_category.choices.insert(0, (0, u"Не выбрана"))
@app.route('/')
def index():
form = FormCategory()
return render_template('index.html',
form=form
)
...
Страница должна выглядеть примерно так:
Необходимо заблокировать выбор под категории пока не выбрана категория. Для этого будем менять свойства sub_category через jQuery.
Подключим её добавив между head в файле index.html строку:
Но и это ещё не всё! Через Ajax мы обращаемся к get_sub_category для которого так же необходимо добавить обработку в app.py и импорт библиотек.
...
from flask import request
from flask import json
...
@app.route('/get_sub_category', methods=('GET', 'POST'))
def get_sub_category():
category_id = request.form['category']
item_list = SubCategory.query.filter_by(category_id=category_id).all()
result_list = dict()
for item in item_list:
result_list[item.id] = item.name
return json.dumps(result_list)
...
Теперь наконец всё должно заработать.
В самом простом случаи можно обойтись вообще без FormCategory из листинга 5 но тогда всё равно придется передавить список категорий, а в самом шаблоне добавится цикл для заполнения категории. Валидацию в таком случаи будет проблематичней сделать, по этому считаю данный вариант оптимальным. Так, что не слушайте людей которые говорят, что форма для 2-х элементов не нужна и всё проще и быстрей сделать просто через input и label.
Вообще для данной формы идеально бы подошел вариант с wtf.quick_form, если ваш сайт использует Bootstrap.
В Emacs мне нравится, что там есть встроенное переключение раскладки клавиатуры, и проблем с горячими клавишами нет, когда включен русский язык.
В Vim по умолчанию ничего нет и я долго мучился, когда нужно писать то на одном языке то на другом. Решение найдено на просторах интернета, запишу его сюда, что бы не потерять.
В .vimrc нужно добавить:
set keymap=russian-jcukenwin
set iminsert=0
set imsearch=0
highlight lCursor guifg=NONE guibg=Cyan
Теперь при включенной русской раскладки можно использовать сочетания клавиш и всё будет хорошо. Язык же нужно переключать нажатием сочетания Ctrl+^.