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

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.

Related

How to link two existing records through many to many relationship

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.

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.

How to check if value is already exist in database (SQLAlchemy)

I am trying to check if unique element is already present in postgresdb.
My method in views.py is
def bestfriend(username):
print username
user = Users.query.filter_by(username = username).first()
if user == None:
flash('bestfriend not found.')
return redirect(url_for('index'))
print user
u = g.user.friend(user)
#print bestfriend.id
if u is None:
#flash('Cannot be friend %(username)s.', username = username)
return redirect(url_for('user', username = username))
if db.session.query(bestfriend).filter(bestfriend.id==u.id).first():
flash('Already Exist')
return redirect(url_for('index'))
db.session.add(u)
db.session.commit()
flash('Your bestfriend has been added.')
return redirect(url_for('user', username = username))
My model.py is
bestfriend= db.Table('bestfriend',
db.Column('id',db.Integer, primary_key = True),
db.Column('friendid', db.Integer, db.ForeignKey('users.id'))
)
class Users(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key = True)
username = db.Column('username', db.String(20), unique=True , index=True)
password = db.Column('password' , db.String(10))
email = db.Column('email',db.String(50),unique=True , index=True)
registered_on = db.Column('registered_on' , db.DateTime)
posts = db.relationship('Post', backref = 'author', lazy = 'dynamic')
followed = db.relationship('Users',
secondary = followers,
primaryjoin = (followers.c.follower_id == id),
secondaryjoin = (followers.c.followed_id == id),
backref = db.backref('followers', lazy = 'dynamic'),
lazy = 'dynamic')
bestfriends = db.relationship('Users',
secondary = bestfriend,
primaryjoin = (bestfriend.c.friendid == id),
secondaryjoin = (bestfriend.c.id == id),
backref = db.backref('bestfriend', lazy = 'dynamic'),
lazy = 'dynamic')
I am able to insert value in database table bestfriend
Table "public.bestfriend"
Column | Type | Modifiers
----------+---------+-----------
id | integer | not null
friendid | integer |
Indexes:
"bestfriend_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"bestfriend_friendid_fkey" FOREIGN KEY (friendid) REFERENCES users(id)
but after insertion, I have to check if record is already present, using this line
if db.session.query(bestfriend).filter(bestfriend.id==u.id).first():
I am not sure about this statement. I have already tried similar solutions How to elegantly check the existence of an object/instance/variable and simultaneously assign it to variable if it exists in python? but it didn't worked for me.
I didn't receive any reply so adding some more info. I just want to check if id = 8 is already present in bestfriend table
app=> select * from bestfriend;
id | friendid
----+----------
8 | 11
Your statement does not work because bestfriend is a Table, not a mapped class. To fix the typo, just add c. to the query:
exists = db.session.query(bestfriend).filter(bestfriend.c.id==u.id).first()
Given that your relationship is defined as dynamic, you can do it easier with the following:
exists = user.bestfriends.filter(Users.id == u.id).one()
Side remark: Note that your bestfriend table is not structured correctly to allow many-to-many relationship. In order to do that, please change the definition of the table as per below:
bestfriend = db.Table('bestfriend',
db.Column('id',db.Integer, db.ForeignKey('users.id'), primary_key = True),
db.Column('friendid', db.Integer, db.ForeignKey('users.id'), primary_key = True)
)
Another remark: I do not understand the code flow: you add a friend at step u = g.user.friend(user), but later check if it exists. Shall logic not be reversed?
If you want to find if a record exists in a table, You can do it like this:
select 1 from table_name;
or better
select 1 from table_name where rownum=1;
The image below shows the query with output.
If there are any data, it will return 1 in the column.

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_