简体   繁体   中英

How to update a Flask-SQLAlchemy one to many relationship

I know similar questions have been asked, but I could not find a working solution. I have a one to many relationship with these models:

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
    email = db.Column(db.String(100), unique=True)
    password = db.Column(db.String(100))
    name = db.Column(db.String(1000))
    religions = db.relationship('Religion', backref='users')
    relImp = db.Column(db.Integer)
    sizes = db.relationship('Size', backref='users')
    sizeImp = db.Column(db.Integer)
    majors = db.relationship('Major', backref='users')
    allMajors = db.Column(db.Boolean)
    satMath = db.Column(db.Integer)
    satEng = db.Column(db.Integer)
    actMath = db.Column(db.Integer)
    actEng = db.Column(db.Integer)
    settings = db.relationship('Setting', backref='users')
    settingImp = db.Column(db.Integer)
    regions = db.relationship('Region', backref='users')
    regionImp = db.Column(db.Integer)
    states = db.relationship('State', backref='users')
    stateImp = db.Column(db.Integer)
    specPrefs = db.relationship('SpecPref', backref='users')
    income = db.Column(db.Integer)

class Religion(db.Model):
    __tablename__ = 'religions'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    code = db.Column(db.Integer)
    name = db.Column(db.String(100))

class Size(db.Model):
    __tablename__ = 'sizes'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    code = db.Column(db.Integer)
    name = db.Column(db.String(100))

class Major(db.Model):
    __tablename__ = 'majors'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    code = db.Column(db.Integer)
    name = db.Column(db.String(100))

class Setting(db.Model):
    __tablename__ = 'settings'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    code = db.Column(db.Integer)
    name = db.Column(db.String(100))

class Region(db.Model):
    __tablename__ = 'regions'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    code = db.Column(db.Integer)
    name = db.Column(db.String(100))

class State(db.Model):
    __tablename__ = 'states'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    code = db.Column(db.Integer)
    name = db.Column(db.String(100))

class SpecPref(db.Model):
    __tablename__ = 'specPrefs'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    code = db.Column(db.Integer)
    name = db.Column(db.String(100))

When a user fills out the form, I would like to add all of the responses to the tables. If there are pre-existing responses, they should be updated. I tried doing that with the following code:

form = Questionnaire(request.form)
#all of the below are WTForms selectMultiple fields
rels = []
for rel in form.relAffil.data:
    rels.append(Religion(code=rel,name=dict(religionChoices).get(rel)))

sizes = []
for size in form.size.data:
    sizes.append(Size(code=size,name=dict(sizeChoices).get(size)))

majors = []
for major in form.major.data:
    majors.append(Major(code=major,name=dict(majorChoices).get(major)))

sets = []
for setting in form.setting.data:
    sets.append(Setting(code=setting,name=dict(settingChoices).get(setting)))

regs = []
for reg in form.region.data:
    regs.append(Region(code=reg,name=dict(regionChoices).get(reg)))

states = []
for state in form.state.data:
    states.append(State(code=state,name=dict(stateChoices).get(state)))

specPrefs = []
for spec in form.specPref.data:
    specPrefs.append(SpecPref(code=spec,name=dict(specPrefChoices).get(spec)))

updatedUser = User(id=current_user.id, religions = rels, relImp = form.relImp.data, sizes = sizes, sizeImp = form.sizeImp.data, majors = majors, allMajors = form.allMajors.data, 
    satMath = form.satMath.data, satEng = form.satEng.data, actMath = form.actMath.data, actEng = form.actEng.data, settings = sets, settingImp = form.settingImp.data, regions = regs,
    regionImp = form.regionImp.data, states = states, stateImp = form.stateImp.data, specPrefs = specPrefs, income = form.income.data)


if request.method == 'GET':
    return render_template('getStarted.html', form=form,name=current_user.name)
else: #if 'POST'
    if form.validate():
        db.session.merge(updatedUser)
        db.session.commit()

When there is no previous data, it works just fine. But when data needs to be updated (eg a user filled out the form before but wants to edit and resubmit it), it outputs sqlalchemy.exc.IntegrityError: Cannot insert the value NULL into column 'user_id', table 'dbo.majors'; column does not allow nulls. UPDATE fails. [SQL: UPDATE majors SET user_id=? WHERE majors.id = ?] [parameters: (None, 3)] sqlalchemy.exc.IntegrityError: Cannot insert the value NULL into column 'user_id', table 'dbo.majors'; column does not allow nulls. UPDATE fails. [SQL: UPDATE majors SET user_id=? WHERE majors.id = ?] [parameters: (None, 3)] sqlalchemy.exc.IntegrityError: Cannot insert the value NULL into column 'user_id', table 'dbo.majors'; column does not allow nulls. UPDATE fails. [SQL: UPDATE majors SET user_id=? WHERE majors.id = ?] [parameters: (None, 3)] Why is that? How do I properly update the tables?

To anyone in the future who stumbles upon this question while researching, this is how I solved it:

        #delete current data
        Religion.query.filter(Religion.user_id == current_user.id).delete()
        Size.query.filter(Size.user_id == current_user.id).delete()
        Major.query.filter(Major.user_id == current_user.id).delete()
        Setting.query.filter(Setting.user_id == current_user.id).delete()
        Region.query.filter(Region.user_id == current_user.id).delete()
        State.query.filter(State.user_id == current_user.id).delete()
        SpecPref.query.filter(SpecPref.user_id == current_user.id).delete()

        #now add all of the new data
        db.session.add_all([Religion(code=rel,name=dict(religionChoices).get(rel),user_id = current_user.id) for rel in form.relAffil.data])
        db.session.add_all([Size(code=size,name=dict(sizeChoices).get(size),user_id = current_user.id) for size in form.size.data])
        db.session.add_all([Major(code=major,name=dict(majorChoices).get(major),user_id = current_user.id) for major in form.major.data])
        db.session.add_all([Setting(code=setting,name=dict(settingChoices).get(setting),user_id = current_user.id) for setting in form.setting.data])
        db.session.add_all([Region(code=reg,name=dict(regionChoices).get(reg),user_id = current_user.id) for reg in form.region.data])
        db.session.add_all([State(code=state,name=dict(stateChoices).get(state),user_id = current_user.id) for state in form.state.data])
        db.session.add_all([SpecPref(code=spec,name=dict(specPrefChoices).get(spec),user_id = current_user.id) for spec in form.specPref.data])

        #update the user database with new data
        updatedUser = User(id=current_user.id, relImp = form.relImp.data, sizeImp = form.sizeImp.data, allMajors = form.allMajors.data, 
    satMath = form.satMath.data, satEng = form.satEng.data, actMath = form.actMath.data, actEng = form.actEng.data, settingImp = form.settingImp.data,
    regionImp = form.regionImp.data, stateImp = form.stateImp.data, income = form.income.data)
        db.session.merge(updatedUser)
        
        #TODO: reincrement after deleting?
        #db.session.execute("DBCC CHECKIDENT (religions, RESEED, 0)") #doesn't work, does each user separately

        db.session.commit()

Essentially, I delete all the current data in the tables and reinsert the new data into each table manually. This seems to work, but it messes up the IDs in the relationship tables (since they auto-increment, deleting entries does not reset the IDs). For my purposes, though, it is only an aesthetic problem, so this is the best way I could come up with, though it's not pretty.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM