I am trying to get a many to many relationship working. I have three tables
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
class Groups(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(1000))
class Members(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
I would like to have the option group.members, which should give me all User objects which are member of that group. I implemented it the following way
members = db.relationship('User', secondary="join(Members, User, Members.user_id == User.id)", primaryjoin="and_(Groups.id == Members.group_id)")
this seems to work, but when I delete a group it gives me (sometimes) the error
AttributeError: 'Join' object has no attribute 'delete'
so I guess this is not the right way to implement such a relation.
Any ideas how to do this correctly?
thanks
carl
Perhaps a simpler way to implement this is as follows (adapted from the documentation on Flask-SQLAlchemy
members = db.Table('members',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('group_id', db.Integer, db.ForeignKey('groups.id'))
)
class Groups(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(1000))
members = db.relationship('User', secondary=members, backref=db.backref('group', lazy='dynamic'))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
Instead of using a Model for the joining table (members), let's just use a simple table.
With this configuation, you can easily add/remove members and groups:
u = User(username='matt')
g = Groups(name='test')
db.session.add(u)
db.session.add(g)
db.session.commit()
g.members.append(u)
db.session.commit()
db.session.delete(g)
db.session.commit()
Related
Given the models below, how can I ensure that the Users returned by the Document.users association proxy are distinct?
For example, the DocumentUser association table can have the follow data
document_id
user_id
role_id
Document_1
User_1
Role_1
Document_1
User_1
Role_2
Document_2
User_2
Role_1
To demo, the models are laid out as:
class Document(db.Model):
__tablename__ = 'documents'
id = db.Column(db.String, primary_key=True)
title = db.Column(db.String)
document_user_associations = db.relationship(
'DocumentUser',
lazy='select',
back_populates='document'
)
users = association_proxy('document_user_associations', 'user')
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.String, primary_key=True)
full_name = db.Column(db.String)
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.String, primary_key=True)
name = db.Column(db.String)
class DocumentUser(db.Model):
__tablename__ = 'document_users'
document_id = db.Column(db.String, db.ForeignKey('documents.id') primary_key=True)
user_id = db.Column(db.String, db.ForeignKey('users.id'), primary_key=True)
role_id = db.Column(db.String, db.ForeignKey('roles.id'), primary_key=True)
document = db.relationship('Document', back_populates='document_user_associations')
user = db.relationship('User')
role = db.relationship('Role')
If I were to do the following, assuming the data in the above table is persisted,
document_1 = db.session.query(Document).get(1)
print(document_1.users)
I would hope to see [<User User_1>] but instead I get [<User User_1>, <User User_1>] due to the association object. Is it possible using the association_proxy to return distinct Users given the above model?
EDIT:
In effect, it would be ideal if the association_proxy, or the association table relationship it relies on, to be able to group by the user, providing a list of the roles.
I currently have a SQLAlchemy model User which has two types of measurements associated with it, weight and height. These are added as foreign keys.
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
weight = db.relationship("Weight", cascade="all,delete")
height = db.relationship("Height", cascade="all,delete")
class Weight(db.Model):
__tablename__ = "weight"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
weight = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
class Height(db.Model):
__tablename__ = "height"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
height = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
I want to be able to add more types of measurements, without having to rebuild the database structure every time. Therefore I thought about using a generic Measurement model which has a column for the type of measurement (not an Enum so I can add new types easily):
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
measurements = db.relationship("Measurement", cascade="all,delete")
#property
def weight(self):
pass # <-- No idea what to do here
#property
def height(self):
pass # <-- No idea what to do here
class Measurement(db.Model):
__tablename__ = "measurement"
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime)
value = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
measurement_type_id = db.Column(db.Integer, db.ForeignKey('measurement_type.id'))
measurement_type = db.relationship("MeasurementType")
class MeasurementType(db.Model):
__tablename__ = "measurement_type"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
unit = db.Column(db.String(100), nullable=True)
With this new model, how do I get only the e.g. weights? With the current models, this is as simple as this, where current_user is the currently logged in user:
current_user.weight
But I guess it would have to be something like this:
current_user.measurements.filter(Measurement.measurement_type == "Weight")
(Which doesn't work, as current_user.measurements returns a list.)
Similarly, how do I then add a new value to the measurements? Currently I do this:
current_user.weight.append(Weight(date=date, weight=weight))
db.session.commit()
Do I basically need to replicate the low-level implementation of relationship in order to filter by two things, the user ID and the measurement type?
Or can I somehow achieve this using an association proxy? Or (correctly) using the primaryjoin argument of relationship?
The easiest way I found to achieve this is to use dynamic lazy loading on the relationship, as also recommended on this answer to a similar question. This way the return type is no longer a simple list, but an SQL object. This also uses a simple string as the measurement type, instead of a lookup table.
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
name = db.Column(db.String(1000))
measurements = db.relationship("Measurement", cascade="all,delete", lazy="dynamic")
class Measurement(db.Model):
__tablename__ = "measurement"
id = db.Column(db.Integer, primary_key=True)
m_type = db.Column(db.String(20))
date = db.Column(db.DateTime)
value = db.Column(db.Float)
user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
Afterwards you can use normal filter methods on the measurements:
list(current_user.measurements.filter_by(m_type="weight"))
And adding a new row is also very simple:
current_user.measurements.append(Measurement(m_type="weight", date=date, value=weight))
I have a basic many to many relationship as below,
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
user_favs = relationship('Loop', secondary='active_association')
def __repr__(self):
return '<User {}>'.format(self.username)
class Loop(db.Model):
id = db.Column(db.Integer, primary_key=True)
creation_date = db.Column(db.DateTime())
genre = db.Column(db.String())
author = db.Column(db.Integer, db.ForeignKey('user.id'))
class Active_Association(db.Model):
__tablename__ = 'active_association'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
loop_id = db.Column(db.Integer(), db.ForeignKey('loop.id', ondelete='CASCADE'))
active = db.Column(db.Boolean(), default=False)
I am adding user_favs like so:
loop = Loop.query.filter_by(id = request.form.get('loop')).first()
user = User.query.filter_by(id = request.form.get('user')).first()
user.user_favs.append(loop)
db.session.commit()
Later I am retrieving all user_favs like so:
user = User.query.filter_by(id = current_user.id).first()
loops = user.user_favs
This gives me an array containing all user_favs but I am struggling to a way to filter it by genre.
I could loop over it but that doesn't seem like the proper solution.
I attempted
user = User.query.filter_by(id = current_user.id).first()
loops = user.active_loops
filter(loops.genre == 'jazz')
But this gives me an AttributeError: 'AppenderQuery' object has no attribute 'genre'
Perhaps I have set my whole relationship up wrong and should be able to just filter it directly if set up correctly? This is my first many - many relationship so I may also be on the wrong track with:
active = db.Column(db.Boolean(), default=False)
Which was intended for some other functionality but I do not know how to address that either.
You can use: lazy='dynamic' which will give you a BaseQuery for the relationship itself. Dynamic just means the join type between the two models.
class Loop(db.Model):
id = db.Column(db.Integer, primary_key=True)
creation_date = db.Column(db.DateTime())
genre = db.Column(db.String())
author = db.Column(db.Integer, db.ForeignKey('user.id'))
# will be .authors from Loop.authors
authors = db.relationship(
'User',
secondary=Active_Association,
backref=db.backref('loops'), # will be .loops on User
lazy='dynamic'
)
In essence, it is a query in a query for maximum flexibility.
# gets the user
user = User.query.filter_by(id = current_user.id).first()
# this will return a collection of loops
loops = user.loops.filter_by(genre='jazz')
I have been trying to manage this in one query without the second query but need to sit down to run through it because my joining skillz are a bit rusty with SQLAlchemy.
I'm trying to use Flask-SQLAlchemy to query out database for the user profile page
So far I don't have a solution for this problem, only able to query all the User data by using users.query.all()
Each user has their own role_id, department_id, researchfield_id.
How can i query out all the Role, Department, ResearchField data that has relationship with User through ID?
class User(UserMixin, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
password_hash = db.Column(db.String(128))
is_admin = db.Column(db.Boolean, default=False)
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
research_id = db.Column(db.Integer, db.ForeignKey('researchfields.id'))
class Department(db.Model):
__tablename__ = "departments"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
user = db.relationship('User', backref='department',
lazy='dynamic')
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
users = db.relationship('User', backref='role',
lazy='dynamic')
class ResearchField(db.Model):
__tablename__ = "researchfields"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), index=True)
parent_id = db.Column(db.Integer, db.ForeignKey("researchfields.id") , nullable=True)
users = db.relationship('User', backref='researchfield', lazy='dynamic')
If I understand correctly, what you're seeking for is a way to filter out users based on a specific model. Because in your example, the other way around is redundant - every user has only one department, so no need to filter out departments for that user. In order to achieve that, I would use the backref method provided by SQLAlchemy from the User model.
Here's an example consisting of two of the models:
from sqlalchemy.orm import backref
class User(UserMixin, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
password_hash = db.Column(db.String(128))
is_admin = db.Column(db.Boolean, default=False)
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
department = db.relationship("Department", backref=backref("users", lazy="dynamic"))
class Department(db.Model):
__tablename__ = "departments"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
Now you can use:
department = Department.query.filter_by(id=1).first()
print(department.users.filter_by(is_admin=True).all()) # get all admins with that department
Every user has only one department, so you could just get the user's department by:
user = User.query.filter_by(id=1).first()
print(user.department) # prints Department object
I have a database shown below which works fine. Now I have a user called Bob that owns the space Mainspace. I would like to get a boolean to see if he is a owner of the space. I tried to apply two filters but I get the following error.
sqlalchemy.exc.InvalidRequestError: Can't compare a collection to an object or collection; use contains() to test for membership.
Command:
exists = Space.query.filter_by(name="Mainspace", owner="Bob").first()
Database:
space_access = db.Table('space_access',
db.Column('userid', db.Integer, db.ForeignKey('user.id')),
db.Column('spaceid', db.Integer, db.ForeignKey('space.id')))
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(15), unique=True)
email = db.Column(db.String(50), unique=True)
password = db.Column(db.String(80))
role='admin';
spaces = db.relationship('Space', secondary=space_access, backref=db.backref('owner', lazy='dynamic'))
class Space(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True)
type = db.Column(db.String(50), unique=True)
Try this:
existing = User.query.join(User.spaces).filter(User.username=='Bob', Space.name=='Mainspace').first()
print(existing.id)
if existing != None:
print('Exists')