Search code examples
pythonsqlalchemyflaskflask-sqlalchemyflask-admin

flask-admin imposes required attribute on relationship


flask-admin marks db.relationship fields of model views as required, so there is no way to add a record if the table to which relationship goes is empty.

Example:

class Type(db.Model):
    id           = db.Column(db.Integer, primary_key=True)
    name         = db.Column(db.String(50), nullable=False, unique=True, index=True)
    area_id      = db.Column(db.Integer, db.ForeignKey("area.id", onupdate='CASCADE', ondelete='CASCADE'), nullable=False, index=True)
    description  = db.Column(db.Text)

class Area(db.Model):
    id           = db.Column(db.Integer, primary_key=True)
    name         = db.Column(db.String(50), nullable=False, unique=True, index=True)
    description  = db.Column(db.Text)
    types = db.relationship('Type', backref='Area', lazy='dynamic', cascade='all, delete-orphan')

types field is required during creation of area record through flask-admin, but If type table is empty, there is no way to add any information to the database. flask-admin wouldn't allow to add anything to Area table because of backref to Type table which makes the field required, but there is nothing in Type table yet. flask-admin wouldn't allow to add anything to Area table as well, because the field types is required on the page.

I couldn't find anything on the subject. There are some topics discussing workarounds for many-to-many relationships, but this is the simplest one-to-many relationship which to my understanding should just work out of the box.

I figured out that if I add Type to inline_models under Area model then I will see a button "Add Types" on create Area page, but if the inline_model itself has a relationship then that wouldn't help either, because of the same reason.

class ModelViewArea(ModelView):
    inline_models = ( models.Type, )

admin.add_view(ModelViewProductArea (models.Area, db.session, name="Area",  category='Product'))
admin.add_view(ModelViewProductType (models.Type, db.session, name="Type",  category='Product'))

Solution

  • It turned out that if a foreign key associated with the relationship has nullable=False then flask-admin marks the field as required in flask-wtf. Once I removed nullable=False from attributes of the foreign key of Type class, flask-admin stopped showing the field as required in both Type model view and Area model view.

    However, as I removed nullable=False there was no more reason for ondelete="CASCADE" and cascade behavior on relationship. Default cascade suits better. The final code looks like:

    class Type(db.Model):
        id           = db.Column(db.Integer, primary_key=True)
        name         = db.Column(db.String(50), nullable=False, unique=True, index=True)
        area_id      = db.Column(db.Integer, db.ForeignKey("area.id", onupdate='CASCADE', ondelete='SET NULL'), index=True)
        description  = db.Column(db.Text)
    
    class Area(db.Model):
        id           = db.Column(db.Integer, primary_key=True)
        name         = db.Column(db.String(50), nullable=False, unique=True, index=True)
        description  = db.Column(db.Text)
        types = db.relationship('Type', backref='Area', lazy='dynamic')