sqlalchemy foreign key relationship attributes - python

I have a User table and a Friend table. The Friend table holds two foreign keys both to my User table as well as a status field. I am trying to be able to call attributes from my User table on a Friend object. For example, I would love to be able to do something like, friend.name, or friend.email.
class User(Base):
""" Holds user info """
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(25), unique=True)
email = Column(String(50), unique=True)
password = Column(String(25))
admin = Column(Boolean)
# relationships
friends = relationship('Friend', backref='Friend.friend_id',primaryjoin='User.id==Friend.user_id', lazy='dynamic')
class Friend(Base):
__tablename__ = 'friend'
user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
friend_id = Column(Integer, ForeignKey(User.id), primary_key=True)
request_status = Column(Boolean)
When I get friend objects all I have is the 2 user_ids and i want to display all properties of each user so I can use that information in forms, etc. I am new to sqlalchemy - still trying to learn more advanced features. This is just a snippet from a larger Flask project and this feature is going to be for friend requests, etc. I've tried to look up association objects, etc, but I am having a hard with it.
Any help would be greatly appreciated.

First, if you're using flask-sqlalchemy, why are you using directly sqlalchemy instead of the Flask's db.Model?
I strongly reccomend to use flask-sqlalchemy extension since it leverages the sessions and some other neat things.
Creating a proxy convenience object is straightforward. Just add the relationship with it in the Friend class.
class Friend(Base):
__tablename__ = 'friend'
user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
friend_id = Column(Integer, ForeignKey(User.id), primary_key=True)
request_status = Column(Boolean)
user = relationship('User', foreign_keys='Friend.user_id')
friend = relationship('User', foreign_keys='Friend.friend_id')
SQLAlchemy will take care of the rest and you can access the user object simply by:
name = friend.user.name
If you plan to use the user object every time you use the friend object specify lazy='joined' in the relationship. This way it loads both object in a single query.

Related

How to design a referral code system in python flask sqlalchemy and postgresql

I'm a bit of a database noob. I'm using flask sqlalchemy and postgresql to design a user system on a website, where each user has a unique, not null referral code. Other users can each use a single referral code, and when they do, both the owner and the user of the code receive some benefits. I'm wondering what the best way to design this is. A many to many relationship?
What I'm currently doing on the user table (other columns like email/password omitted) is:
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
referral_code = db.relationship('Refcodes', backref='owner', uselist=False)
referrals = db.relationship('Referrals', backref='user', uselist=False)
And on the 'refcodes' and 'referrals' table:
class Refcodes(db.Model):
__tablename__ = 'refcodes'
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(10), nullable=False, unique=True)
owner_id = db.Column(db.ForeignKey('user.id'))
referrals = db.Relationship('Referrals', backref='code')
class Referrals(db.Model):
__tablename__ = 'referrals'
id = db.Column(db.Integer, primary_key=True)
code_id = db.Column(db.ForeignKey('refcodes.id'))
user_id = db.Column(db.ForeignKey('users.id'))
I haven't tested it yet since I'm still designing the login/registration page, but my question is if there is some sort of best practice/exact method for similar problems. Apologies for the bad English and the possibly vague/confusing initial questions and thanks in advance for any replies.

SQLAlchemy Join to retrieve data from multiple tables

I'm trying to retrieve data from multiple tables with SQLAlchemy using the .join() method.
When I run the query I was expecting to get a single object back which had all the data from the different tables joined so that I could use a.area_name and so on where area_name is on one of the joined tables. Below is the query I am running and the table layout, if anyone could offer insight into how to achieve the behavior I'm aiming for I would greatly appreciate it! I've been able to use the .join() method with this same syntax to match results and return them, I figured it would return the extra data from the rows as well since it joins the tables (perhaps I'm misunderstanding how the method works or how to retrieve the information via the query object?).
If it helps with the troubleshooting I'm using MySQL as the database
query:
a = User.query.filter(User.user_id==1).join(UserGroup,
User.usergroup==UserGroup.group_id).join(Areas, User.area==Areas.area_id).first()
and the tables:
class User(db.Model):
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True)
usergroup = db.Column(db.Integer, db.ForeignKey('user_group.group_id'), nullable=False)
area = db.Column(db.Integer, db.ForeignKey('areas.area_id'), nullable=False)
class UserGroups(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, nullable=False, unique=True)
group_name = db.Column(db.String(64), nullable=False, unique=True)
class Areas(db.Model):
id = db.Column(db.Integer, primary_key=True)
area_id = db.Column(db.Integer, nullable=False, unique=True)
area_name = db.Column(db.String(64), nullable=False, unique=True)
So it seems that I need to use a different approach to the query, and that it returns a tuple of objects which I then need to parse.
What worked is:
a = db.session.query(User, UserGroups, Areas
).filter(User.user_id==1
).join(UserGroup,User.usergroup==UserGroup.group_id
).join(Areas, User.area==Areas.area_id
).first()
The rest remaining the same. This then returned a tuple that I could parse where the data from User is a[0], from UserGroups is a[1], and Areas is a[2]. I can then access the group_name column with a[1].group_name etc.
Hopefully this helps someone else who's trying to work with this!
Take a look at SQLAlchemy's relationship function:
http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#one-to-many
You may want to add a new attribute to your User class like so:
group = sqlalchemy.relationship('UserGroups', back_populates='users')
This will automagically resolve the one-to-many relationship between User and UserGroups (assuming that a User can only be member of one UserGroup at a time). You can then simply access the attributes of the UserGroup once you have queried a User (or set of Users) from your database:
a = User.query.filter(...).first()
print(a.group.group_name)
SQLAlchemy resolves the joins for you, you do not need to explicitly join the foreign tables when querying.
The reverse access is also possible; if you just query for a UserGroup, you can access the corresponding members directly (via the back_populates-keyword argument):
g = UserGroup.query.filter(...).first()
for u in g.users:
print(u.name)

How do I write a query to get sqlalchemy objects from relationship?

I am learning python and using the framework pyramid with sqlalchemy as the orm. I can not figure out how relationships work. I have 2 tables, offices and users. the foreign key is on the users table 'offices_id'. I am trying to do a query that will return to me what office a user is a part of.
This is how I have my models set up.
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True, nullable=False)
trec_number = Column(Unicode(255), unique=True, nullable=False)
office_id = Column(Integer, ForeignKey('offices.id'))
class Office(Base):
__tablename__ = 'offices'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True, nullable=False)
address = Column(Unicode(255), unique=True, nullable=False)
members = relationship("User", backref='offices')
In my view how would I write a query that would return the the office information for a given user?
I am trying this:
for user in DBSession.query(User).join(Office).all():
print user.address
but I think I am misunderstanding how the queries work because I keep getting errors
AttributeError: 'User' object has no attribute 'address'
when I do this:
for user in DBSession.query(User).join(Office).all():
print user.name
it prints out the users name fine since name is an attribute of the User class.
I also can not get the inverse to work
for offices in DBSession.query(Office).join(User).all():
print offices.users.name
You need to use the name you used in the backref argument to access the Office model. Try user.offices.address

logging versions with SQLAlchemy including relationship tables

related to this question: SQLAlchemy logging of changes with date and user
I'm using a modified version of the "recipe" for versioning changes automatically. I think it's able to handle some forms of relationships already (not sure, though), but I'm not able to handle the case where there's a many-to-many relationship in a separate table.
Here's a simple example that's an issue:
from history_meta import (Versioned, versioned_session)
Base = declarative_base()
user_to_group = Table('user_to_group', Base.metadata,
Column('user_login', String(60), ForeignKey('user.login')),
Column('group_name', String(100), ForeignKey('group.name'))
)
class User(Versioned, Base):
__tablename__ = 'user'
login = Column(String(60), primary_key=True, nullable=False)
password = Column(BINARY(20), nullable=False)
class Group(Versioned, Base):
__tablename__ = 'group'
name = Column(String(100), primary_key=True, nullable=False)
description = Column(String(100), nullable=True)
users = relationship(User, secondary=user_to_group, backref='groups')
When generating the tables in the database with Base.metadata.create_all(engine) I can see that there are only 5 tables: user, group, user_to_group, user_history, and group_history There is no user_to_group_history.
The "versioning" gets added to the declarative objects through inheritance of Versioned, but there's no way (that I can see) to do something similar with the user_to_group table which isn't using the declarative format. There's also notes in the documentation saying that it's not a good idea using a table that's mapped to a class so I'm trying to avoid using a declarative object for the relationship.

Update SQLAlchemy relationship

I have two relationships to the same table. When I add an element to one relationship, this does not reflect to the other relationship until I submit the session. Is there a way to force "update" the relationships?
Concrete example:
class Event(ManagerBase):
"""Defines an event."""
__tablename__ = 'eventing_events'
id = Column(Integer, primary_key=True)
device_id = Column(Integer, ForeignKey(EventingDevice.id), nullable=False)
device = relation(EventingDevice)
type_id = Column(Integer, ForeignKey(EventType.id), nullable=False)
type = relation(EventType)
datetime = Column(DateTime, nullable=False)
summary = Column(String(500))
fields = relation("EventFieldValue",
viewonly=True,
collection_class=attribute_mapped_collection("field.name"))
class EventFieldValue(ManagerBase):
"""The value of a single field of an event."""
__tablename__ = 'eventing_event_field_values'
event_id = Column(Integer, ForeignKey(Event.id), primary_key=True)
event = relation(Event, backref=backref("field_values",
collection_class=attribute_mapped_collection("field")))
field_id = Column(Integer, ForeignKey(Field.id), primary_key=True)
field = relation(Field)
value = Column(Text)
I have two realations from Event to EventFieldValue: fields and field_values (via backref of event). When I add a EventFieldValue to event.field_values, it does not reflect in event.fields until I commit the session.
Because you have two relations, sqlalchemy have to make requests for each one, and doesn't share their cache in the session.
You should take a look at Association proxies, that seems to be exactly what you need. They allow you to define only one relation and to put proxies on the top of them to access stuff in the relation more easily.
Flushing the session should solve this problem. It updates your session with all the new state but doesn't do a commit. You can also look into Refresh/Expire which will reload your objects.

Categories