SQLAlchemy Association Proxy distinct right side - python

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.

Related

Querying user specific data in flask

I'm building an app that shows a database of different plant species. Each user sees the same table of plant species except for the "notes" column, which each user can edit to their own liking. This is the database structure I have created:
class Note(db.Model):
__tablename__ = 'plantdatabasenote'
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text(), nullable=False)
plant_id = db.Column(db.Integer, db.ForeignKey("plant.id"))
user_id = db.Column(db.Integer, db.ForeignKey("profile.id"))
class Plant(db.Model):
__tablename__ = 'plant'
id = db.Column(db.Integer, primary_key=True)
common_name = db.Column(db.String(200), nullable=False)
date_created = db.Column(db.DateTime, default=datetime.utcnow)
notes = db.relationship("Note", backref="user_notes")
class Profile(db.Model):
__tablename__ = 'profile'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(200), unique=True, nullable=False)
I tried to retrieve the notes of user 1 with the following:
Plant.query.filter(Plant.notes.any(user_id=1)).all()
Unfortunately, this does not give me all the plants with the notes of user 1. Any idea how to fix this?

How do I ensure that the user can follow of multiple contents like topic, user

I have a models like:
from app import db
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, fields
from datetime import datetime
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, db.ForeignKey('follower.followable_id'), primary_key=True)
username = db.Column(db.String(32), nullable=False)
email = db.Column(db.String(320), nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
password_salt = db.Column(db.String(22), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow())
user_comment = db.relationship('UserComment', back_populates='author')
def to_json(self):
return {'username': self.username, 'email': self.email}
def get_id(self):
return self.id
class Topic(db.Model):
__tablename__ = 'topic'
id = db.Column(db.Integer, db.ForeignKey('follower.followable_id'), primary_key=True)
topic_name = db.Column(db.String(100))
class Follower(db.Model):
__tablename__ = 'follower'
id = db.Column(db.Integer, primary_key = True)
follower_id = db.Column(db.Integer, db.ForeignKey('user.id'))
followable_id = db.Column(db.Integer, db.ForeignKey('followable.followable_id'))
My goal is for the user to be able to follow topics and users. I tried to connect the id column of the user and topic table to the followable_id column of the follower table for this. And I linked the follower_id of the follower table to the user id.
In this case, I received an error when creating a topic table. (sqlalchemy.greetings.OperationalError: (pymysql.mistake.OperationalError) (1822, "Foreign key restriction could not be added. The missing index for the 'topic_ibfk_1' constraint in the referenced table is 'follower'")
https://stackoverflow.com/a/11618048/13975329 I took a reference from here and tried to create these tables but I couldn't.
How can I create a better design for user-topic follow mechanism and why does it give this error in topic table creation?

SQLAlchemy One-to-(Some of Many) relationship

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))

Use Flask-SqlAlchemy to query relationship database

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

many to many relationship with three tables (sql-alchemy)

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()

Categories