Обновлять отношения "многие ко многим", используя фласки, SQLAlchemy и WTForms?

Я хочу создать форму для отношений "многие ко многим", используя фласки, SQLAlchemy и WTForms, которые представляют эти модели:

personaddress = db.Table('personaddress',
    db.Column('person', db.Integer, db.ForeignKey('person.id')),
    db.Column('address', db.Integer, db.ForeignKey('address.id'))
)

class Person(db.Model):
    __tablename__ = "person"
    id = db.Column(Integer, primary_key=True)
    name = db.Column(String, nullable=False)
    addresses = db.relationship('Address', secondary=personaddress, backref=db.backref('person', lazy='dynamic'))

class Address(db.Model):
    __tablename__ = "address"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)

Требования

Теперь я хочу создать одну страницу, содержащую формы для достижения следующих результатов:

  • добавить/отредактировать/удалить человека
  • добавить/изменить/удалить адрес
  • добавить/отредактировать/удалить связь между человеком и адресом

Важное требование: с помощью QuerySelectField я могу выбрать существующие адреса для человека. Но я хочу добавить новые адреса в той же форме.

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

Вопросы

  • Как обычно выполняются вышеуказанные требования в Flask?
  • Является ли этот сценарий "много ко многим" тем, что Django может легко обрабатывать через свой интерфейс администратора?

Ответы

Ответ 1

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

Использование QuerySelectField в WTForms поможет вам заполнить, например. < выберите > с идентификаторами, парами значений, соответствующими существующим адресам. Но он по-прежнему отображает регулярную форму html в шаблоне.

Используя какой-то мультиселектор с возможностью динамически добавлять новые параметры в интерфейсе, вы можете отправлять дополнительные адреса в той же форме. Конечная точка будет заботиться о создании новых адресов, если они не существуют в db.

Форма WTForm будет:

from app import db

class PersonAddressForm(Form):
    id = HiddenField('id')
    name = StringField('Name')
    addresses = QuerySelectField('Addresses', 
            query_factory=lambda: db.session.query(Address), 
            get_pk=lambda a: a.id, get_label=lambda a: a.name)

    # Custom validate
    def validate(self):
        # ... custom validation
        return True

И маршрут вроде как:

# ... this will be used to create and update a user
@route('create/<userid>', methods=["GET"])
def get_user_form(userid):
    # ... Get the Person
    user = Person()
    if userid:
        # ... if userid supplied, use existing Person object
        user = Person.query.get(userid)

    # ... Populate the form
    person_form = PersonAddressForm(obj=user)

    # ... return form
    return render_template('somepage.html', form=person_form)

@route('create/<userid>', methods=["POST"])
def post_person_form(userid):
    person_form = PersonAddressForm(request.form)

    if person_form.validate():
        # ... Get db object
        person = db.session.query(Person).get(form.id)

        # ... Add changes to the object from the form
        person_form.populate_obj(obj=person_address)

        # ... Get addresses
        addresses = form.addresses.raw_data

        # ... loop over and add to person
        for address in addresses:
            # Add or create an address
            actual_address = db.session.query(Address).get(address.id)

            # ... check if address is existing
            if not actual_address:
                # ... if address not existing, create new one
                actual_address = Address(address.name)
                db.session.add(actual_address)

            # ... Append new or created address to person
            person.addresses.append(actual_address)

        # ... save changes to the db
        db.session.commit()

        # ... Update/Create complete
        return redirect(url_for('get_users'))

    else:
        # ... form not valid, notify user
        # ...

Это приведет к редактированию/созданию пользователя и созданию адреса. А также создайте связь между ними. Чтобы он также поддерживал удаление адреса, измените

person.addresses.append(actual_address)

к

person.addresses = list_of_actual_addresses

и измените это в модели человека (cascade = 'delete-orphan')

addresses = db.relationship('Address', secondary=personaddress, cascade='delete-orphan' backref=db.backref('person', lazy='dynamic'))

Это приведет к тому, что форма обновит все отношения адресов каждый раз, и каскад удалит осиротевшие адреса. Таким образом, весь список адресов для человека будет обновляться каждый раз при отправке формы.

При работе с WTForms в шаблонах я настоятельно рекомендую использовать макросы, если вы этого еще не сделали. Вам придется переписать его в некоторой степени, но проверьте этот вне.

Надеюсь, что это поможет