Создание формы фляги с выбором из более чем одной таблицы

Я видел большое количество руководств, в которых показаны формы входа в систему с помощью flask и flash-wtf, но ни в одном из них несколько полей выбора не заполняются из значений таблицы базы данных.

Вот что я пытаюсь сделать:

Простая форма регистрации:

Имя

Фамилия

Адресная строка 1

Адресная строка 2

Город

Идентификатор состояния (заполняется из запроса библиотеки состояний идентификатора, состояния)

Идентификатор страны (заполняется из библиотеки стран, запрос страны, идентификатор)

Пример кода или ссылка на прохождение будут очень признательны.


person Michael BW    schedule 12.03.2014    source источник
comment
Я потратил две недели на учебу после работы на полную ставку, чтобы понять разработку Python для Интернета и попытаться выяснить, какой фреймворк мне больше подойдет. Я достаточно хорошо разбираюсь в использовании функций и классов Python и взаимодействии с базой данных, но я откровенно сбит с толку, когда дело доходит до использования ORM или DAL помимо формы входа, поскольку я привык писать свой собственный sql. Если мне можно будет указать место в f'ing manual, я буду рад RTFM.   -  person Michael BW    schedule 13.03.2014
comment
Не смог найти мануал. Так что я сделал один. Настоятельно рекомендуем вам просто клонировать репозиторий github и читать исходный код.   -  person AlexLordThorsen    schedule 15.03.2014


Ответы (1)


Я пытался найти объяснение, как это сделать, и не смог найти. Так что я собираюсь написать один здесь. Вот как я делаю вещи, возможно, есть лучшие способы сделать это.

Исходный код

Вы можете скачать полный исходный код этого руководства в моей учетной записи github. Я в основном копирую и вставляю из исходного кода, но на случай, если github когда-нибудь умрет, вот и все.

Конфигурация

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

В этом уроке мы будем использовать базовую тестовую базу данных sqlalchemy.

app = Flask(__name__)
app.config['SECRET_KEY'] = 'Insert_random_string_here'

Установите для этой конфигурации значение True, если вы хотите видеть все сгенерированные SQL.

app.config['SQLALCHEMY_ECHO'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'

Строки конфигурации WTForms

app.config['WTF_CSRF_ENABLED'] = True

Токены CSRF важны. Подробнее о них читайте здесь, https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet

app.config['WTF_CSRF_SECRET_KEY'] = 'Insert_random_string_here'
db = SQLAlchemy(app)

Модель SQLALchemy

Далее нам нужно создать классы нашей модели, которые будут использоваться во время создания базы данных, а также когда мы хотим манипулировать базой данных. Обычно это должен быть отдельный файл.

Я важен отсюда, как если бы это был отдельный файл.

Обычно вам нужно импортировать что-то вроде приложения import db.

class RegisteredUser(db.Model):
    """
    loads and pushes registered user data after they have signed up.

    SQLalchemy ORM table object which is used to load, and push, data from the
    server memory scope to, and from, the database scope.
    """
    __tablename__ = "RegisteredUser"

    #all of the columns in the database.
    registered_id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(70))
    last_name = db.Column(db.String(70))
    address_line_one = db.Column(db.String(256))
    address_line_two = db.Column(db.String(256))
    city = db.Column(db.String(50))

    """
    Now we're going to create all of the foreign keys for the RegisteredUser
    table. The db.relationship section allows us to easily and automatically
    join the other tables with registeredUser. The Join will only take place
    if you attempt to access columns from the State or country table.

    For more on Foreign keys using SQLAlchemy go to
    """
    state_id = db.Column(
            db.Integer,
            db.ForeignKey('State.state_id'),
            nullable=False)
    #retrives the users name for display purposes.
    state_by = db.relationship(
            'State',
            foreign_keys=[state_id],
            backref=db.backref('State', lazy='dynamic'))
    country_id = db.Column(
            db.Integer,
            db.ForeignKey('Country.country_id'),
            nullable=False)
    #retrives the users name for display purposes.
    country_by = db.relationship(
            'Country',
            foreign_keys=[country_id],)

    #this is the method and function style I've chosen when lines are too long
    def __init__(
            self,
            first_name,
            last_name,
            address_line_one,
            address_line_two,
            city,
            state_id,
            country_id):
        """
        Used to create a RegisteredUser object in the python server scope

        We will be calling these init functions every time we use
        RegisteredUser() as a 'function' call. It will create a SQLalchemy ORM
        object for us.
        """
        self.first_name = first_name
        self.last_name = last_name
        self.address_line_one = address_line_one
        self.address_line_two = address_line_two
        self.city = city
        self.state_id = state_id
        self.country_id = country_id


class State(db.Model):  # pylint: disable-msg=R0903
    """
    Holds State names for the database to load during the registration page.

    SQLalchemy ORM table object which is used to load, and push, data from the
    server memory scope to, and from, the database scope.
    """
    __tablename__ = "State"

    state_id = db.Column(db.Integer, primary_key=True)
    state_name = db.Column(db.String(10), unique=True)

    def __init__(self, state_name):
        """
        Used to create a State object in the python server scope
        """
        self.state_name = state_name


class Country(db.Model):  # pylint: disable-msg=R0903
    """
    Holds Country names for the database to load during the registration page.

    SQLalchemy ORM table object which is used to load, and push, data from the
    server memory scope to, and from, the database scope.
    """
    __tablename__ = "Country"

    country_id = db.Column(db.Integer, primary_key=True)
    #longest country length is currently 163 letters
    country_name = db.Column(db.String(256), unique=True)

    def __init__(self, country_name):
        """
        Used to create a Country object in the python server scope
        """
        self.country_name = country_name


def create_example_data():
    """
    Generates all of the demo data to be used later in the tutorial. This is
    how we can use our ORM objects to push data to the database.

    NOTE: create_example_data is called at the very bottom of the file.
    """
    #Create a bunch of state models and add them to the current session.
    #Note, this does not add rows to the database. We'll commit them later.
    state_model = State(state_name="WA")
    db.session.add(state_model)
    state_model = State(state_name="AK")
    db.session.add(state_model)
    state_model = State(state_name="LA")
    db.session.add(state_model)
    #Normally I load this data from very large CVS or json files and run This
    #sort of thing through a for loop.

    country_model = Country("USA")
    db.session.add(country_model)
    country_model = Country("Some_Made_Up_Place")
    db.session.add(country_model)
    # Interesting Note: things will be commited in reverse order from when they
    # were added.
    try:
        db.session.commit()
    except IntegrityError as e:
        print("attempted to push data to database. Not first run. continuing\
                as normal")

WTForm

Теперь мы собираемся создать наши объекты WTForms. На них будут размещены данные, полученные из базы данных, затем мы передадим их в наши файлы шаблонов, где мы их отобразим.

Я важен отсюда, как если бы это был отдельный файл.

import wtforms
import wtforms.validators as validators
from flask.ext.wtf import Form

class RegistrationForm(Form):
    """
    This Form class contains all of the fileds that make up our registration
    Form. 
    """
    #Get all of the text fields out of the way.
    first_name_field = wtforms.TextField(
            label="First Name",
            validators=[validators.Length(max=70), validators.Required()])
    last_name_field = wtforms.TextField(
            label="Last Name",
            validators=[validators.Length(max=70), validators.Required()])
    address_line_one_field = wtforms.TextField(
            label="Address",
            validators=[validators.Length(max=256), validators.Required()])
    address_line_two_field = wtforms.TextField(
            label="Second Address",
            validators=[validators.Length(max=256), ])
    city_field = wtforms.TextField(
            label="City",
            validators=[validators.Length(max=50), validators.Required()])
    # Now let's set all of our select fields.
    state_select_field = wtforms.SelectField(label="State", coerce=int)
    country_select_field = wtforms.SelectField(label="Country", coerce=int)

Взгляды

импортная колба

def populate_form_choices(registration_form):
    """
    Pulls choices from the database to populate our select fields.
    """
    states = State.query.all()
    countries = Country.query.all()
    state_names = []
    for state in states:
        state_names.append(state.state_name)
    #choices need to come in the form of a list comprised of enumerated lists
    #example [('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')]
    state_choices = list(enumerate(state_names))
    country_names = []
    for country in countries:
        country_names.append(country.country_name)
    country_choices = list(enumerate(country_names))
    #now that we've built our choices, we need to set them.
    registration_form.state_select_field.choices = state_choices
    registration_form.country_select_field.choices = country_choices

@app.route('/', methods=['GET', 'POST'])
def demonstration():
    """
    This will render a template that displays all of the form objects if it's
    a Get request. If the use is attempting to Post then this view will push
    the data to the database.
    """
    #this parts a little hard to understand. flask-wtforms does an implicit
    #call each time you create a form object. It attempts to see if there's a
    #request.form object in this session and if there is it adds the data from
    #the request to the form object.
    registration_form = RegistrationForm()
    #Before we attempt to validate our form data we have to set our select
    #field choices. This is just something you need to do if you're going to 
    #use WTForms, even if it seems silly.
    populate_form_choices(registration_form)
    #This means that if we're not sending a post request then this if statement
    #will always fail. So then we just move on to render the template normally.
    if flask.request.method == 'POST' and registration_form.validate():
        #If we're making a post request and we passed all the validators then
        #create a registered user model and push that model to the database.
        registered_user = RegisteredUser(
            first_name=registration_form.data['first_name_field'],
            last_name=registration_form.data['last_name_field'],
            address_line_one=registration_form.data['address_line_one_field'],
            address_line_two=registration_form.data['address_line_two_field'],
            city=registration_form.data['city_field'],
            state_id=registration_form.data['state_select_field'],
            country_id=registration_form.data['country_select_field'],)
        db.session.add(registered_user)
        db.session.commit()
        return flask.render_template(
            template_name_or_list='success.html',
            registration_form=registration_form,)
    return flask.render_template(
            template_name_or_list='registration.html',
            registration_form=registration_form,)

runserver.py

Наконец, это только для целей разработки. Обычно это находится в файле с именем RunServer.py. Для фактической доставки вашего приложения вы должны работать за каким-либо веб-сервером (Apache, Nginix, Heroku).

if __name__ == '__main__':
    db.create_all()
    create_example_data()
    app.run(debug=True)

Шаблоны

в макрос.html

{% macro render_field(field) %}
  <dt>{{ field.label }}
  <dd>{{ field(**kwargs)|safe }}
  {% if field.errors %}
    <ul class=errors>
    {% for error in field.errors %}
      <li>{{ error }}</li>
    {% endfor %}
    </ul>
  {% endif %}
  </dd>
{% endmacro %}

{% macro render_data(field) %}
  <dt>{{ field.label }}
  <dd>{{ field.data|safe }}
  {% if field.errors %}
    <ul class=errors>
    {% for error in field.errors %}
      <li>{{ error }}</li>
    {% endfor %}
    </ul>
  {% endif %}
  </dd>
{% endmacro %}

В Registration.html

{% from "macros.html" import render_field %}
<form method=post action="/">
    {{registration_form.hidden_tag()}}
  <dl>
    {{ render_field(registration_form.first_name_field) }}
    {{ render_field(registration_form.last_name_field) }}
    {{ render_field(registration_form.address_line_one_field) }}
    {{ render_field(registration_form.address_line_two_field) }}
    {{ render_field(registration_form.city_field) }}
    {{ render_field(registration_form.state_select_field) }}
    {{ render_field(registration_form.country_select_field) }}
  </dl>
  <p><input type=submit value=Register>
</form>

Наконец, в success.html

{% from "macros.html" import render_data %}
<h1> This data was saved to the database! </h1>
<form method=post action="/">
    {{registration_form.hidden_tag()}}
  <dl>
    {{ render_data(registration_form.first_name_field) }}
    {{ render_data(registration_form.last_name_field) }}
    {{ render_data(registration_form.address_line_one_field) }}
    {{ render_data(registration_form.address_line_two_field) }}
    {{ render_data(registration_form.city_field) }}
    {{ render_data(registration_form.state_select_field) }}
    {{ render_data(registration_form.country_select_field) }}
  </dl>
  <p><input type=submit value=Register>
</form>
person AlexLordThorsen    schedule 15.03.2014
comment
Абсолютно потрясающе. Я сделал одно изменение в функции populate_form_choices (или это метод). потому что я не получал правильное значение идентификатора для выбора страны и штата. Registration_form.country_select_field.choices = [(g.country_id, g.country_name) для g в Country.query.order_by('country_name')]. Еще раз спасибо - person Michael BW; 15.03.2014
comment
Есть ли шанс, что вы окажете мне услугу, отправив ваши изменения в репозиторий github? - person AlexLordThorsen; 16.03.2014
comment
Есть ли способ создать только ОДНУ таблицу вместо всех? - person rh0x; 12.12.2015
comment
Я исправил проблему, упомянутую Майклом Б.В. выше, в форке репозитория Алекса. Вот он: github.com/lfernandez55/WTFormMultipleSelectTutorial. Отличный пример кстати. Как получилось, что в документации WTForms нет таких примеров? Сумасшедшая оплошность. - person Luke; 26.01.2020