Хочу поделится как я считаю хорошим решением динамической подгруздки в select.
Рассмотрю пример на категория + под категория. При выборе категории будут подгружается под категории.
Использовать для этого буду Flask, AJAX, jQuery, SQLite.
Подгружаться данные будут с помощью AJAX и Json.
Для краткости python код будет в app.py, а шаблон в template/index.html
Структура проекта:
app.py
template/index.html
Библиотеки которые должны стоять:
Flask==0.11 Flask-SQLAlchemy==2.1 Flask-WTF==0.12 Jinja2==2.8 MarkupSafe==0.23 SQLAlchemy==1.0.13 WTForm==1.0 WTForms==2.1 Werkzeug==0.11.10
Для начало необходимо создать минимальное приложение которое отображает шаблон.
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
Далее создаём index.html в директории templates:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flask-select</title>
</head>
<body>
Hello!
</body>
</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 строку:
... <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script> ...
После нашей формы необходимо добавить JavaScript который будет менять свойства sub_category, загружать под категории посредствам Ajax.
...
<script type="text/javascript">
function choice_category(){
var tmp_id = parseInt ($("#category").val());
if(tmp_id == 0)
{
$("#sub_category").attr('disabled', 'disabled');
}
else
{
$("#sub_category").removeAttr('disabled');
load_subcategory();
}
}
function load_subcategory(){
$.ajax({
type: "POST",
url: "/get_sub_category",
data: $('form').serialize(),
success: function(response) {
var json = jQuery.parseJSON(response)
obj = Object.keys(json)
$("#sub_category")
.find('option')
.remove()
.end()
.append('<option value="0">Не выбрано</option>')
.val('0');
var value, key;
for(item in obj){
value = json[obj[item]];
key = obj[item];
$("#sub_category").append($("<option></option>")
.attr("value",key)
.text(value));
}
},
error: function(error) {
console.log(error);
}
});
}
$(document).ready(function() {
choice_category();
$("#category").change(function() {
choice_category();
});
$("#sub_category").change(function() {
});
});
</script>
...
Но и это ещё не всё! Через 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.
Готовый исходный код можно взять с github
git clone https://github.com/newivan/flask_select.git
Форма по-любому нужна! Без форм во вьюхах появляется хаос валидации..
В реальном проекте ещё желательно в форме предусмотреть валидацию между полями категория и под-категория, чтобы кто-нибудь «фрукты-сметана» или «напитки-яблоки» не засабмитил =)