0

I am trying to send form data in a sqlite database. There are 6 tables with one-to-many and many-to-many relationships between them.

Here's my model:

class Event(db.Model):
    id = db.Column(db.String, default=lambda: uuid.uuid4().hex, primary_key=True)
    name = db.Column(db.String(60), nullable=False)
    date_created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    activities = db.relationship('Activity', backref='event', lazy='dynamic')
    participants = db.relationship('User', backref='event', lazy='dynamic')
    globalweights = db.relationship('Globalw8', backref='event', lazy='dynamic')


relationship_table = db.Table('relationship_table', db.Column('user_id', db.Integer, db.ForeignKey('user.id'), nullable=False), db.Column('activity_id', db.Integer,db.ForeignKey('activity.id'),nullable=False), db.PrimaryKeyConstraint('user_id', 'activity_id'))


class Activity(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), nullable=False)
    event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False)
    participants = db.relationship('User', secondary=relationship_table, backref='activity')
    amounts = db.relationship('Amount', backref='activity', lazy='dynamic')
    activityweights = db.relationship('Activityw8', backref='activity', lazy='dynamic')


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), nullable=False)
    event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False)
    globalw8_id = db.Column(db.Integer, db.ForeignKey('globalw8.id'), nullable=False)
    activityweights = db.relationship('Activityw8', backref='participant', lazy='dynamic')
    amounts = db.relationship('Amount', backref='participant', lazy='dynamic')


class Amount(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    amount = db.Column(db.Float, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    activity_id = db.Column(db.Integer, db.ForeignKey('activity.id'), nullable=False)


class Globalw8(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    weight = db.Column(db.Float, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False)


class Activityw8(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    weight = db.Column(db.Float, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    activity_id = db.Column(db.Integer, db.ForeignKey('activity.id'), nullable=False)

And here's my route:

@app.route('/', methods=['GET', 'POST'])
def home():
    form = eveForm()
    if form.validate_on_submit():
        activity = Activity(name=form.actName.data, event=form.eveName.data)
        event = Event(name=form.eveName.data)
        user = User(name=form.partName.data, event=form.eveName.data, globalw8=form.partGlobWeight.data, activity=form.actName.data)
        globalw8 = Globalw8(weight=form.partGlobWeight.data, participant=form.partName.data, event=form.eveName.data)
        amount = Amount(amount=form.amount.data, participant=form.partName.data, activity=form.actName.data)
        activityw8 = Activityw8(weight=form.partActWeight.data, participant=form.partName.data, activity=form.actName.data)
        db.session.add(activityw8)
        db.session.add(activity)
        db.session.add(event)
        db.session.add(user)
        db.session.add(globalw8)
        db.session.add(amount)
        db.session.commit()

When I submit my form I get the error "AttributeError: 'str' object has no attribute '_sa_instance_state'"

I suspect this is due to my route and how I am trying to collect the form data, in particular data related to relationships (ex. user = User(name=form.partName.data, event=form.eveName.data, globalw8=form.partGlobWeight.data, activity=form.actName.data)

For instance in the example above, event is a backref and nowhere have I found what to affect to it but what I have done, i.e. form.eveName.data

Thanks in advance

Edit: Here's the Traceback

Traceback (most recent call last):
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\flask\app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\flask\app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\flask\app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\flask\app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\flask\app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\flask\app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\flask\app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\xaray\code\Flaskapps\cuentapp\app.py", line 70, in home
    activity = Activity(name=form.actName.data, event=form.eveName.data)
  File "<string>", line 4, in __init__

  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\sqlalchemy\orm\state.py", line 441, in _initialize_instance
    manager.dispatch.init_failure(self, args, kwargs)
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\sqlalchemy\util\langhelpers.py", line 68, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\sqlalchemy\util\compat.py", line 129, in reraise
    raise value
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\sqlalchemy\orm\state.py", line 438, in _initialize_instance
    return manager.original_init(*mixed[1:], **kwargs)
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\sqlalchemy\ext\declarative\base.py", line 836, in _declarative_constructor
    setattr(self, k, kwargs[k])
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\sqlalchemy\orm\attributes.py", line 262, in __set__
    instance_state(instance), instance_dict(instance), value, None
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\sqlalchemy\orm\attributes.py", line 975, in set
    value = self.fire_replace_event(state, dict_, value, old, initiator)
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\sqlalchemy\orm\attributes.py", line 998, in fire_replace_event
    state, value, previous, initiator or self._replace_token
  File "C:\Users\xaray\Anaconda3\envs\Flask\lib\site-packages\sqlalchemy\orm\attributes.py", line 1407, in emit_backref_from_scalar_set_event
    instance_state(child),
AttributeError: 'str' object has no attribute '_sa_instance_state'
xaray
  • 13
  • 4
  • Please tell on which variable this error occurs. A complete copy of the error trail would be better. – gittert Apr 05 '19 at 13:48
  • I've just edited my post with the traceback; if I understand it correctly, it looks related to *activity = Activity(name=form.actName.data, event=form.eveName.data)* – xaray Apr 05 '19 at 14:16
  • You could try the solution applied here https://stackoverflow.com/questions/33083772/sqlalchemy-attributeerror-str-object-has-no-attribute-sa-instance-state – gittert Apr 05 '19 at 15:23
  • Well I have already tried everything I could find on stackoverflow or youtube tutorials that seemed related to my issue, and nothing have worked so far. Thanks anyway. In fact I think I would be interested to hear from someone who has dealt with sqlalchemy many-to-many relationships + wtforms, to see what their code looks like. – xaray Apr 06 '19 at 16:36

1 Answers1

1

Issue 1

I think you have overly specified relationships (In SQL, is it OK for two tables to refer to each other?):

class User(db.Model):
    # ...
    globalw8_id = db.Column(db.Integer, db.ForeignKey('globalw8.id'), nullable=False)

class Globalw8(db.Model):
    # ...
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

You can replace this with a relationship, thereby requiring only one FK input:

class User(db.Model):
    # ...
    globalw8 = db.relationship('GlobalW8', back_populates='user')

class Globalw8(db.Model):
    # ...
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship('GlobalW8', back_populates='globalw8')

Now when you create SQLAlchemy object entities they are transient meaning they do not acquire their associated id from the database before they are committed to the database. But you cannot commit them to the database because you will break the nullable=False constraint. So you need to add them, flush the database to 'act as if commiting but not actually commit' and then re populate info and then commit:

eg.

new_user = User(..)
db.session.add(new_user)
db.session.flush()
new_globalw8 = Globalw8(.., user_id=new_user.id)
db.session.add(new_globalw8)
db.session.commit()

Note this might break some inherent database integrity changing it away from your original structure, so you might want to experiment.

Issue 2

This is actually your specific stack trace error:

File "C:\Users\xaray\code\Flaskapps\cuentapp\app.py", line 70, in home
    activity = Activity(name=form.actName.data, event=form.eveName.data)
..
**AttributeError: 'str' object has no attribute '_sa_instance_state'**

What this is saying is that because of the line:

activities = db.relationship('Activity', backref='event', lazy='dynamic')

then Activity.event is expecting to be an SQLAlchemy Instance State. However, you are supplying it a string from your form: form.eveName.data, which is completely incompatible. What you should really do is create the Event first:

new_event = Event(name=form.eveName.data) 

and then populate that as the event for activities.

activity = Activity(name=form.actName.data, event=new_event)

Note you may get a transient error if you do not flush the database (akin to above), but perhaps not - its been a while since I did this.

Attack68
  • 4,437
  • 1
  • 20
  • 40