How to link two existing records through many to many relationship - orm

I need to add roles to staff, the following code works but somehow it does not allow to add the same role to multiple staff. if the same role gets assigned again, the previous assignment is deleted from the secondary table! why is this happening
staff_roles = Table('staff_roles', Base.metadata,
Column('staff_id', Integer, ForeignKey('staff.id')),
Column('role_id', Integer, ForeignKey('roles.id'))
)
class Staff(Base):
__tablename__="staff"
id= Column(Integer, primary_key=True, index=True)
img= Column(String)
civil_id= Column(Integer)
nationality= Column(String)
disabled= Column(Boolean, default=False)
created_at= Column(DateTime)
updated_at= Column(DateTime)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
roles = relationship("Roles", uselist=True, secondary=staff_roles, back_populates="staff", lazy='raise')
class Roles(Base):
__tablename__="roles"
id= Column(Integer, primary_key=True, index=True)
name= Column(String)
staff = relationship("Staff", uselist=False, secondary=staff_roles, back_populates="roles", lazy='raise')
def add_role_to_staff(db: Session, user_id:int, role_ids:list):
roles = db.query(tables.Roles).options(joinedload(tables.Roles.staff)).filter(tables.Roles.id.in_(role_ids)).all()
staff = db.query(tables.Staff).options(joinedload(tables.Staff.roles)).filter(tables.Staff.user_id == user_id).first()
if len(roles) == 0 or staff is None:
raise HTTPException(status_code=404, detail="role or staff does not exist")
for role in roles:
staff.roles.append(role)
try:
db.commit()
except IntegrityError as e:
db.rollback()
process_db_error(e)
from the logs:
INFO:sqlalchemy.engine.base.Engine:DELETE FROM staff_roles WHERE staff_roles.staff_id = %(staff_id)s AND staff_roles.role_id = %(role_id)s
INFO:sqlalchemy.engine.base.Engine:{'staff_id': 2, 'role_id': 2}
INFO:sqlalchemy.engine.base.Engine:INSERT INTO staff_roles (staff_id, role_id) VALUES (%(staff_id)s, %(role_id)s)
INFO:sqlalchemy.engine.base.Engine:{'staff_id': 1, 'role_id': 2}
INFO:sqlalchemy.engine.base.Engine:COMMIT
Why does it delete?!

This is interesting, the issue was in the Roles table in staff relationship. uselist should be True.

Related

SQLAlchemy DELETE: a foreign key contraint fails

I have the following two tables:
class User(Base):
__tablename__ = "user_entity"
id = Column(String(32), primary_key=True, index=True)
email = Column(String(32))
email_constraint = Column(String(32))
email_verified = Column(Boolean)
enabled = Column(Boolean)
federation_link = Column(String(32))
first_name = Column(String(32))
last_name = Column(String(32))
realm_id = Column(String(32))
username = Column(String(32))
created_timestamp = Column(String(32))
service_account_client_link = Column(String(32))
not_before = Column(Integer)
children = relationship("Userattribute", back_populates="parent")
class Userattribute(Base):
__tablename__ = "user_attribute"
name = Column(String(32))
value = Column(String(32))
id = Column(String(32), primary_key=True, index=True)
user_id = Column(String(32), ForeignKey('user_entity.id', ondelete="CASCADE"))
parent = relationship("User", back_populates="children")
I want to delete a user from the user_entity table by his ID:
[SQL: DELETE FROM user_entity WHERE user_entity.id = %s]
Delete implementation:
def delete_user(db: Session, id: str):
return db.query(models.User).filter_by(id=id)
#app.delete("/entity/{id}", response_model=schemas.Userentity)
def del_user(id: str, db: Session = Depends(get_db)):
user = crud.delete_user(db, id=id)
user_obj = user.first()
if user_obj is not None:
user.delete()
db.commit()
return JSONResponse(content={"message": "user erfolgreich gelöscht"})
else:
raise HTTPException(status_code=400, detail=f"ID existiert nicht")
Unfortunately I get the following error:
'Cannot delete or update a parent row: a foreign key constraint fails
Why does this fail? The expected behaviour is that when I delete a user, all user_attributes in the user_attribute table get also deleted. How to fix that?
I can't reproduce your error exactly: user.delete() raises an AttributeError. The correct call would be db.delete(user) I think.
The deletion behaviour must also be specified in the relationship on the parent:
children = relationship(
"Userattribute",
back_populates="parent",
cascade="all, delete"
)
For efficiency, you might also want to specify passive_deletes=True.
See Using foreign key ON DELETE cascade with ORM relationships.

Django : Multiple possible types in ForeignKey Field

I want to model the following relationship where the Vehicle owner field could be either a Person or a Company. How can we do that in Django?
class Person(models.Model):
name = ...
other_details = ...
class Company(models.Model):
name = ...
other_details = ...
class Vehicle(models.Model):
owner = models.ForeignKey(x) # 'x' could be a Person or Company
Use Generic foreign key
ex.
class Vehicle(models.Model):
content_type = models.ForeignKey(ContentType, null=True, blank=True)
object_id = models.PositiveIntegerField(null=True, blank=True)
content_object = generic.GenericForeignKey('content_type', 'object_id')
while saving the object you need to get the content_type of the model to which you want to give generic FK and object id of that model.
from django.contrib.contenttypes.models import ContentType
content_type = ContentType.objects.get_for_model(Company)
object_id = company_object.id
ve = Vehicle()
ve.content_type = content_type
ve.object_id = object_id
ve.save()
hope this will help you.

Unique if not null SQLAlchemy and Django

Given this simple table written in SQLAlchemy and Django models.py, how would I set UPC to be unique if not null. UPC won't be available for all items, but if is it should be unique.
class Products(base):
__tablename__ = u'products'
id = Column(Integer(), primary_key=True, autoincrement = True)
product_name = Column(String(), unique=True, nullable=False)
upc = Column(String(), nullable = True)
And
class Products(models.Model):
id = models.AutoField(primary_key=True)
product_name = models.TextField()
upc = models.TextField(null=True, blank=True)
Multiple rows with NULL values should not be a problem for the unique constraint. Only "values" must be unique, NULL is no value.
Have you tried?:
upc = Column(String(), unique=True, nullable=True)

sqlalchemy query from SQL with multiple where conditions

I have three tables which are defined as:
class User(Base):
__tablename__ = 'users'
id = Column(Integer(10), primary_key=True)
firstname = Column(String(64))
surname = Column(String(64))
class SWMS(Base):
__tablename__ = 'swms'
id = Column(Integer(10), primary_key=True)
owner_id = Column(Integer(10), ForeignKey('users.id', ondelete='CASCADE'))
filename = Column(String(255))
swmowner = relationship('User', backref=backref('users'))
class SWM_perms(Base):
__tablename__ = 'swm_perms'
id = Column(Integer(10), primary_key=True)
swm_id = Column(Integer(10), ForeignKey('swms.id', ondelete='CASCADE'))
user_id = Column(Integer(10), ForeignKey('users.id', ondelete='CASCADE'))
swm = relationship('SWMS', backref=backref('swms'))
swmuser = relationship('User', backref=backref('swmusers'))
Essentially, the SWMS table is a table of document info where the owner_id defines the user who created the document. SWM_perms is a table that has a mapping of document id to user id - to define which users are allowed to see the document.
To produce a table of all documents which are either 1) owned by the user or 2) are viewable by the user, in SQL I would do:
select owner_id, users.firstname, users.surname, filename
from swms, swm_perms, users
where users.id=swms.owner_id and
((swms.id=swm_perms.swm_id and swm_perms.user_id = 27) or (owner_id = 27));
How would you define this query in sqlalchemy? I am familiar with the or_() function but the variants I am trying do not generate the correct objects.
cond1 = and_(SWMS.id==SWM_perms.swm_id,SWM_perms.user_id==27)
swms = DBSession.query(User,SWMS).filter(or_(cond1,SWMS.owner_id==27)).\
filter(User.id==SWMS.owner_id).all()
and then you can do a list comprehension to pull the fields you want:
details = [(u.firstname, s.filename, s.blob_key, s.last_modified) for u,s in swms]
Also worth noting you can use the '&' operator, in place of 'and_', in the body of the query. See the example (second code block) they give here:
http://docs.sqlalchemy.org/en/rel_1_0/core/sqlelement.html#sqlalchemy.sql.expression.and_

Need some help accessing a ForeignKey row's data while performing a select with SQLAlchemy

Background Schema:
class Checkpoint(db.Model):
id = db.Column(db.Integer, primary_key=True)
creator = db.Column(db.Integer, db.ForeignKey('user.id'))
name = db.Column(db.String(255))
description = db.Column(db.String(255), nullable=True)
price = db.Column(db.Float, nullable=True)
expiry = db.Column(db.DateTime, nullable=True)
date_created = db.Column(db.DateTime)
type = db.Column(db.String(255))
image = db.Column(db.String(255))
longitude = db.Column(db.Float)
latitude = db.Column(db.Float)
class UserCheckpoint(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship("User")
checkpoint_id = db.Column(db.Integer, db.ForeignKey('checkpoint.id'))
checkpoint = db.relationship("Checkpoint")
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255))
facebook_info = db.Column(db.String(255), db.ForeignKey('facebook_user.id'))
facebook_user = db.relationship("FacebookUser")
class FriendConnection(db.Model):
id = db.Column(db.Integer, primary_key=True)
fb_user_from = db.Column(db.String(255), db.ForeignKey('facebook_user.id'))
fb_user_to = db.Column(db.String(255), db.ForeignKey('facebook_user.id'))
class FacebookUser(db.Model):
id = db.Column(db.String(255), primary_key=True)
name = db.Column(db.String(255))
first_name = db.Column(db.String(255), nullable=True)
middle_name = db.Column(db.String(255), nullable=True)
last_name = db.Column(db.String(255), nullable=True)
gender = db.Column(db.String(255), nullable=True)
username = db.Column(db.String(255), nullable=True)
link = db.Column(db.String(255), nullable=True)
I have a user, and as you can see, each user has a Facebook profile, as well as a table depicting inter-facebook-profile friendships. So given the user, the user would have a list of Facebook friends. I would like to get all UserCheckpoints that belong either to the user or his friends, with a given Checkpoint condition:
coord_conditions = and_(Checkpoint.longitude <= longitude + exp_no,
Checkpoint.longitude >= longitude - exp_no,
Checkpoint.latitude <= latitude + exp_no,
Checkpoint.latitude >= latitude - exp_no,
)
How can I do this using the ORM from SQLAlchemy? Thanks!
Summary: How to select UserCheckpoints given that the user_id belong to a list of friends/self; while UserCheckpoint.checkpoint has a set of conditions to fulfill.
Each relation has two methods to defined conditions on related objects: .has() for single referred object and .any() for collections. These methods allow straightforward translation of your task to SQLAlchemy expression. Let's add missing relations to FacebookUser:
class FacebookUser(Model):
# Definitions from question are here
user = relationship(User, uselist=False)
friends = relationship('FacebookUser',
secondary=FriendConnection.__table__,
primaryjoin=(id==FriendConnection.fb_user_from),
secondaryjoin=(FriendConnection.fb_user_to==id))
I've defined FacebookUser.user assuming one-to-one relation (which is usually supplemented with unique constraint on the foreign key column). Just remove uselist=False and adjust name if you allow several users being connected to one facebook account.
A shorter definition of your condition for coordinates:
coord_conditions = Checkpoint.longitude.between(longitude - exp_no,
longitude + exp_no) & \
Checkpoint.latitude.between(latitude - exp_no,
latitude + exp_no)
This condition is definitely wrong even for approximation (-179.9° and 179.9° are very close, while the difference is huge), but this is not main topic of the question.
A condition for users of interest (user with id equal to user_id and his friends):
user_cond = (User.id==user_id) | \
User.facebook_user.has(
FacebookUser.friends.any(FacebookUser.user.has(id=user_id)))
Now the query is quite simple:
session.query(UserCheckpoint).filter(
UserCheckpoint.checkpoint.has(coord_conditions) & \
UserCheckpoint.user.has(user_cond))
Unless you have (or expect) performance issues, I'd suggest avoid optimizing it at the cost of readability.
Basically your query can be split in two parts:
Given the user_id, create a list of users which will contain the user herself as well as all direct friends
Given the list of users from 1., get all UserCheckpoint whose Checkpoint would satisfy the criteria.
Not tested code:
# get direct user for given user_id
u1 = (session.query(User.id.label("user_1_id"), User.id.label("user_id"))
)
# get friends of the user in one direction (from other user to this one)
User2 = aliased(User)
FacebookUser2 = aliased(FacebookUser)
u2 = (session.query(User2.id.label("user_1_id"), User.id.label("user_id")).
join(FacebookUser2, User2.facebook_info == FacebookUser2.id).
join(FriendConnection, FacebookUser2.id == FriendConnection.fb_user_from).
join(FacebookUser, FacebookUser.id == FriendConnection.fb_user_to).
join(User, User.facebook_info == FacebookUser.id)
)
# get friends of the user in other direction (from this user to the other)
User2 = aliased(User)
FacebookUser2 = aliased(FacebookUser)
u3 = (session.query(User2.id.label("user_1_id"), User.id.label("user_id")).
join(FacebookUser2, User2.facebook_info == FacebookUser2.id).
join(FriendConnection, FacebookUser2.id == FriendConnection.fb_user_to).
join(FacebookUser, FacebookUser.id == FriendConnection.fb_user_from).
join(User, User.facebook_info == FacebookUser.id)
)
# create a union to have all pairs (me_or_friend_id, user_id)
u_all = union_all(u1, u2, u3)
# **edit-1: added alias **
u_all = u_all.alias("user_list_view")
# final query which adds filters requirested (by user_id and the checkpoint condition)
q = (session.query(UserCheckpoint).
join(Checkpoint).filter(coord_conditions).
join(u_all, UserCheckpoint.user_id == u_all.c.user_1_id).
filter(u_all.c.user_id == user_id)
)
for u_cp in q.all():
print u_cp
Note, that you could simplify the query somewhat if you defined more relationships in your model and then can remove some primaryjoin conditions from join clauses.