Хочу поделится как я считаю хорошим решением динамической подгруздки в 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