Reputation: 61
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 (e.g. 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)]
Why is that? How do I properly update the tables?
Upvotes: 0
Views: 685
Reputation: 61
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.
Upvotes: 1