SQL Alchemy deleting association from many to many - python

I know this question has already been asked by I could not find a clean solution.
My problem is simple: I have a table with a many to many association. Where users have many workspaces and workspaces have many users.
invitations_table = db.Table('invitations', db.Model.metadata,
db.Column('user_id', db.Integer, db.ForeignKey('user.id', ondelete='cascade'), primary_key=True),
db.Column('workspace_id', db.Integer, db.ForeignKey('workspace.id', ondelete='cascade'), primary_key=True)
)
Now my two class are declared like that:
class Workspace(db.Model):
id = db.Column(db.Integer, primary_key = True)
...
u_invited = db.relationship("User",
secondary = invitations_table,
cascade = 'all',
back_populates = "w_invited")
class User(db.Model):
id = db.Column(db.Integer, primary_key = True)
...
w_invited = db.relationship("Workspace",
secondary = invitations_table,
back_populates = "u_invited")
Now, If I delete my workspace, I can't cascade the deletion on the associative table as well:
def delete_all_workspace(self):
workspace = Workspace.query.filter_by(owner=self)
# for w in workspace.all():
# w.u_invited.clear()
succeed = workspace.delete()
db.session.commit()
The problem is: I need, as some people suggest, to manually loop into my list of objects then call clear() in order to not get this exception:
sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) update or delete on table "workspace" violates foreign key constraint "invitations_workspace_id_fkey" on table "invitations"
DETAIL: Key (id)=(142) is still referenced from table "invitations".
I though ondelete='cascade' will be enough to tell sqlalchemy to delete the associative table as well but that's not working as expected.
My question is: Is there a way to tell SQLAlchemy to clear the associative table invitations as well without having the burden to clear it manually?
Thanks for your answers

Related

Support duplicate entries in a M2M associative table

I am working on a SQL-Alchemy app using flask and flask-db and have been scratching my head over how to solve this question. My models looks like this:
class event_schematics_map():
event_schematics_table = db.Table(
'event_schematics_table',
db.Column('fk_schematic_id', db.Integer, db.ForeignKey('schematics.id')),
db.Column('fk_event_id', db.Integer, db.ForeignKey('events.id'))
)
class Events(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), index=True, unique=False)
date = db.Column(db.String(120), unique=False)
owner = db.Column(db.Integer, db.ForeignKey('user.id'))
schematics = db.relationship('Recipe', secondary=event_schematics_map.event_schematics_table, backref='schematic')
class Schematics(db.Model):
__tablename__ = 'schematics'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.VARCHAR(70), index=True)
schematics_description = db.Column(db.String(1024), index=True)
creator_id = db.Column(db.Integer, db.ForeignKey('user.id'))
Schematics are created and are on the many side of O2M with a user table separately not shown above. The map table is used as the glue in the M2M relationship.
Currently I am adding new schematics to each event and updating the assoc table like so: events.schematics.append(SomeNewSchematic) which works fine until I attempt to enter multiple instances of the exact same Schematic like this:
schem1 = Schematics(name='TheOnlySchematic')
schem2 = Schematics(name='TheOnlySchematic')
event.schematics.append(schem1)
event.schematics.append(schem2)
etc
in which case I can only apply one as I think the entry is being duplicated. I believe this may be solved by an additional field in the assoc table event_schematics_map, but unsure if I am overlooking something simpler or how to implement this.
Effectively I want to support multiple entries of the exact same model
I believe my problem is along the same lines as can I append twice the same object to an instrumentedlist in sqlalchemy - but I could not see a solution for this.
Really appreciate any pointers or to know how to solve this problem.
Thank you for your reply and setting me straight here,
You are quite right, duplicate entries does not make sense.
I ended up solving this by using an associative table as discussed in other answers to track the occurrence of each schematic.

SQLAlchemy how do I copy an auto-incrementing primary key column?

It seems like a bit of a workaround to commit changes, query the database for the key, and insert it somewhere else.
I assumed one should use the synonym() function for this, but it doesn't seem to have any effect, i.e. my synonymous column doesn't exist when querying the database afterwards.
E.g.
class MyClass(Base):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True, autoincrement=True)
job_status = Column(String(50))
other_id = synonym("id")

Foreign Key to Same Table in sqlalchemy

I have a MySQL table, defined in sqlalchemy with following structure:
class User(Base):
__tablename__ = 'user'
__table_args__ = {'mysql_charset': 'utf8', 'mysql_engine': 'InnoDB'}
id = Column(Integer, primary_key=True)
handle = Column(String(250), nullable=False)
owned = Column(Boolean(), default=False)
owner_id = Column(Integer, ForeignKey("user.id"), nullable=True, default=null )
current_price = Column(Integer, nullable=False, default=1)
balance = Column(Integer, nullable=False, default=0)
I want a relationship so that the owner_id can either be null, OR if it is set it must refer to a valid user.id, in the same table.
I don't quite understand the sqlalchemy relationship stuff well enough to be able to do it. The special stuff at the top of this page http://docs.sqlalchemy.org/en/latest/orm/relationship_persistence.html seems to suggest that it's possible, but I can't figure it out.
I want to then be able to either add Users like:
u1 = User(handle="bob")
u2 = User(handle="jim", owner=u1)
Thanks for any help!
I should add that sqlalchemy has no problem doing the CREATE TABLE with the correct FOREIGN KEY constraint, and I can manually INSERT data into the table that obeys the rules as I want them in MySQL, it's only using the sqlalchemy model that fails.
EDIT: SOLVED
The 'default=null' on owner_id was causing the problem for some reason. Helpful docs were here: http://docs.sqlalchemy.org/en/rel_1_0/orm/self_referential.html and code example from that page here: http://docs.sqlalchemy.org/en/rel_1_0/orm/examples.html#examples-adjacencylist
For the google spider bots, errors that I got during this process were:
sqlalchemy.exc.IntegrityError: (_mysql_exceptions.IntegrityError) (1452, 'Cannot add or update a child row: a foreign key constraint fails (`twitfriends`.`tree`, CONSTRAINT `tree_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `tree` (`id`))') [SQL: u'INSERT INTO tree (parent_id, name) VALUES (%s, %s)'] [parameters: (<sqlalchemy.sql.elements.Null object at 0x7fe7e8c468d0>, 'rootnode')]
And
ArgumentError: Node.next and back-reference Node.prev are both of the same direction <symbol 'ONETOMANY>. Did you mean to set remote_side on the many-to-one side ?
Since there is only one foreign key for User, I would expect sqlalchemy to automatically figure out the join conditions. You can also add a backref so you can get the other side of the relationship.
class User(Base):
...
owner = relationship('User', remote_side=['id'], backref='owned_users')
Docs
Ex.
u1 = User(handle="bob")
u2 = User(handle="jim", owner=u1)
print u2.owned_users[0] == u1
# True

In Flask-SQLAlchemy, Should i use create_all() to create tables in production environment?

I am new to Flask.
I am working on Flask-SQLAlchemy,and i also tried Flask-Migrate.
It's handy to create production and test environments quickly.
But i think it's not as convenient as Django-Migration.
When i tried to create many-to-many data model.
I got following error sometimes:
sqlalchemy.exc.IntegrityError: (_mysql_exceptions.IntegrityError) (1215, 'Cannot add foreign key constraint') [SQL: u'
CREATE TABLE ad_accounts (
access_token_id INTEGER,
ad_account_id INTEGER,
FOREIGN KEY(access_token_id) REFERENCES fb_access_token (id),
FOREIGN KEY(ad_account_id) REFERENCES ad_account (id)
)
']
My models:
from sqlalchemy.dialects.mysql import BIGINT
ad_accounts = db.Table('ad_accounts',
db.Column('access_token_id', db.Integer, db.ForeignKey('fb_access_token.id')),
db.Column('ad_account_id', db.Integer, db.ForeignKey('ad_account.id'))
)
class AdAccount(db.Model):
__bind_key__ = 'ads'
__tablename__ = 'ad_account'
id = db.Column(db.Integer, primary_key=True)
account_id = db.Column(BIGINT(unsigned=True), default=None)
account_status = db.Column(db.Integer, default=None)
business_name = db.Column(db.String(255), default='')
owner = db.Column(db.String(255), default='')
timezone_name = db.Column(db.String(255), default='')
created_time = db.Column(db.Integer, default=0)
activities = db.relationship('Activity', backref='ad_account', lazy='dynamic')
class FbAccessToken(db.Model):
__bind_key__ = 'ads'
__tablename__ = 'fb_access_token'
id = db.Column(db.Integer, primary_key=True)
admin_id = db.Column(db.Integer, db.ForeignKey('admin_user.admin_id'))
# fb_account_id = db.Column(db.String(32), default='')
ad_accounts = db.relationship('AdAccount', secondary=ad_accounts, backref='access_token_list', lazy='dynamic')
update_time = db.Column(db.Integer, default=0)
page_id = db.Column(BIGINT(unsigned=True), default=0)
current_account_id = db.Column(BIGINT(unsigned=True), nullable=True)
When every time i was running 'python app.py db upgrade' in product envirment,i was afraid to break it down.
And someone told me that i should alter table by manual SQL.
I am confused now,i want to do it in a convenient way,i used to do it in django-migration.
Suppose I have already created database and tables in product envirment, do I still have to execute create_all or 'python app.py db upgrade'(In Flask-Migrate)?
And how to add a comment on a column in Flask-Migrate?
On an existing database, in production, you obviously don't want to recreate your database schema and lose any existing data. Look into database migrations, here are the relevant packages:
flask-migrate (it uses alembic internally)
sqlalchemy-migrate
I am not sure if I got well what you want.
I think flask Migrate is a good tool but it have some limitations as stated Here.
Basically , since alembic doesn't detect every migration you have to edit the migration script by adding foreign keys names when using them as it says in alembic documentation here
.
I face the same problem with my migration and I resolve it by editing the migration script in folder '/migration/version' by manually adding foreign keys name! with this line of code
sa.ForeignKeyConstraint(['access_token_id'], ['fb_access_token.id'],use_alter=True, name='fk_fb_access_token.id_id' ),
or if you don't like to edit migration script you can use the object
ForeignKeyConstraint
instead of
db.ForeignKey()
and give it all the parameter specially name when defining your foreign keys
But In general, it's because you need to name the foreign keys and don't use the default names

python sqlalchemy - can't get cascade delete to work - in mssql manager

I have this code, thats been mostly taken from the sqlalchemy site
class Order(Base):
__tablename__ = 'order'
id = Column(Integer, Sequence('tri_id_seq'), primary_key=True)
text = Column(String(1024), nullable=False)
items = relationship("Item", cascade="save-update, delete-orphan, merge, delete", backref="parent")
class Item(Base):
__tablename__ = 'item'
id = Column(Integer, Sequence('tri_id_seq'), primary_key=True)
text = Column(String(1024), nullable=False)
parent_id = Column(Integer, ForeignKey('order.id'))
I want deletes to Order to cascade down and delete its items as well. In code:
# test insert/delete - save data to mssql server
i1 = Item(text="item one")
i2 = Item(text="item two")
o = Order(text="one", items=[i1, i2])
session.add(o)
session.commit()
session.delete(o) # delete it
# tests to make sure items/order gone ....
session.commit()
This works ok.
BUT if I try and delete an Order in MS SQL management studio. i.e.
DELETE FROM [dbo].[order] WHERE id = 1
I get the error
"the DELETE statement conflicted with the REFERENCE constraint FK__item__parent_id_1D00044F" The conflict error blah blah....
I guess theres something missing on the relationship definitions but I can't see it.
Any help/thoughts?
ta.
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete='CASCADE'), nullable=False)
user = relationship("User", backref=backref('comments', cascade="all,delete", order_by=id))
This kind of setup works for me, my User class doesn't have any special fields except for primary key.
So basically, this works as intended, when I delete the user - his comments are gone as well.
It doesn't have to be deletion through SQLAlchemy either, this code creates an appropriate table structure, that even if you delete user manually (sql query) - cascade deletion will still work.

Categories