Flask-sqlalchemy many to many relationship data - python

I am trying to make a discord like application, but I haven't been able to properly set the relationship. I am using a "members" association table to link the users to a server. I believe that the users and the servers should keep a copy of the relationship so that a user can print all the servers it is connected to and a server can see all the members it has. This is the code I have so far.
members = db.Table('members',
db.Column('member_id', db.Integer, db.ForeignKey('user.id')),
db.Column('workspace_id', db.Integer, db.ForeignKey('workspace.id')))
class User(db.Model):
__tablename__="user"
id=db.Column(db.Integer,primary_key=True)
username=db.Column(db.String,nullable=False, unique=True)
workspaces = db.relationship(
"Workspace",
secondary=members,
primaryjoin=(members.c.workspace_id == id),
backref=db.backref('members', lazy='dynamic'), lazy='dynamic')
class Workspace(db.Model):
__tablename__="workspace"
id=db.Column(db.Integer,primary_key=True)
workspaceName=db.Column(db.String,nullable=False)
subgroups=db.relationship("subGroup",backref="workspace",lazy=True)
code = db.Column(db.String, nullable=False)
users = db.relationship(
"User",
secondary=members,
primaryjoin=(members.c.member_id == id),
backref=db.backref('members', lazy='dynamic'), lazy='dynamic')
Do I append from both sides of the relationship or just append once?

Related

One-To-Many database relationship Foreign Key error - SQLAlchemy

I am having trouble making a One-To-Many relationship between 'User' and 'GymObj' using a Foreign Key. In my code, a user can have only one gym objective however a gym objective can have many users. The code which I attempted seems to be correct as I followed a tutorial for it however a 'NoForeignKeysError' appears.
python code
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(200), nullable=False, unique=True)
birth_year = db.Column(db.Integer, nullable=False)
weight = db.Column(db.Integer, nullable = False)
date_added = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('gymobj.id'))
#Create a String
def __repr__(self):
return '<Username %r>' % self.username
class GymObj(db.Model):
id = db.Column(db.Integer, primary_key=True)
gym_obj = db.Column(db.String)
users = db.relationship('User', backref='user')
console
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship GymObj.users - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
I attempted to make a migration and push the migration in the Shell as well however the same error appeared. Any help would be much appreciated.

How do I associate two foreign keys to separate instances of the same parent model in SQLAlchemy?

I am working on a user to user 'Challenge' model in SQLAlchemy that needs to be linked to both the sending player, as well as the receiving player. I.e one user sends a challenge to another user, and users can see all of their sent challenges along with their received challenges. I initially attempted to solve this using an association table with no luck. I have since realized that an association table is unnecessary, however, I am unable to join the two tables as desired without receiving this error: Could not determine join condition between parent/child tables on relationship User.sent_challenges - there are multiple foreign key paths linking the tables.
I have read through the documentation and all similar problems that I could find through these forums but none seem to fix my problem. Below is the current implementation of my code. It is far from the only attempt I have made, however, I believe that it most accurately portrays what I am attempting to accomplish.
Challenge model:
class Challenge(db.Model):
__tablename__ = 'challenges'
id = Column(Integer, primary_key=True)
time = Column(Integer)
player_1_score = Column(Integer, nullable=True)
player_2_score = Column(Integer, nullable=True)
sender_id = Column(Integer, ForeignKey('users.id'), nullable=False)
receiver_id = Column(Integer, ForeignKey('users.id'), nullable=False)
sender = relationship("User", back_populates="sent_challenges")
receiver = relationship("User", back_populates="received_challenges")
User model:
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
user_name = Column(String(14), nullable=False)
level = Column(Integer)
high_score = Column(Integer)
points = Column(Integer)
sent_challenges = relationship("Challenge", back_populates="sender", cascade="all, delete")
received_challenges = relationship("Challenge", back_populates="receiver", cascade="all, delete")
Any insight would be greatly appreciated.
Add the foreign_keys param so sqlalchemy knows which foreign key belongs to which relationship:
sender = relationship("User", foreign_keys=[sender_id], back_populates="sent_challenges")
receiver = relationship("User", foreign_keys=[receiver_id], back_populates="received_challenges")
This is explained here with addresses that mirror your sender/receiver ambiguity:
handling-multiple-join-paths

Flask-SQLAlchemy: One-To-Many between separate moduls (NoReferencedTableError)

I have two model classes in separate files, created a One-To-Many relationship between them.
user.py:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), index=True, unique=True, nullable=False)
password = db.Column(db.String(128), nullable=False)
projects = db.relationship('Project', backref='owner', lazy='dynamic')
project.py:
class Project(db.Model):
__tablename__ = 'projects'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), index=True, unique=True, nullable=False)
owner_id = db.Column(db.Integer, db.ForeignKey('User.id'))
This is how I create my app:
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
db = SQLAlchemy(app)
#app.before_first_request
def create_tables():
db.create_all()
app.run(debug=True)
However when I create a request I get the following error:
sqlalchemy.exc.NoReferencedTableError:
Foreign key associated with column 'projects.owner_id' could not find
table 'User' with which to generate a foreign key to target column 'id'
I understand this is a duplicate of this question asked before, however I tried that and did not work:
#app.before_first_request
def create_tables():
from projectx.models.auth.user import User
from projectx.models.private_data.project import Project
db.create_all()
I also tried giving the same __table_args__ = {"schema": "testdb"} args to both models (I manually created the db with this name) and refer to db.ForeignKey('testdb.User.id) to no avail, the error is the same.
What am I missing here?
It seems I misunderstood some concepts about how this relationship abstraction works.
The foreign key reference is to the tablename and not the class name naturally.
I should have written db.ForeignKey('users.id').

Flask Admin edit able user change their data

I'm using Flask-Admin to manage my CRUD.
There are three roles in my app, which is superuser, operator and client.
In this app, operators must ask superuser to register their account, to change their data and others.
But for the client which is uncounted numbers, I want they can register their account or editable their account information by own.
For now, the client has can register by own, but now I want the client can editable their information individually without through superuser.
So far, I just can edit the account information by superuser, like this screenshot:
So for now, I want client can edit their name, email, password or other information by their own, but also separate their data with the other clients.
Here is the snippet of my model:
roles_users = db.Table(
'roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
)
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
class Operator(User):
__tablename__ = 'operator'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
class Client(User):
__tablename__ = 'client'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
So, how to do that with Flask-Admin..?
Flask-Security comes with a built-in form and view for password change. I would recommend using that. https://pythonhosted.org/Flask-Security/customizing.html
to edit user info via Flask-Admin view, you can override these methods by doing the following. Don't forget to add 'client' as accepted role in your flask-admin User class.
The custom filter has to filter on current_user_id, so no other user profile can be editted.
def get_query(self)
if "superuser" in current_user.roles:
return self.session.query(self.model) # as original source code
else: # for all other roles
return self.session.query(self.model).filter(
< insert custom filter here> )
def get_count_query(self):
if "superuser" in current_user.roles:
return self.session.query(func.count('*')).select_from(self.model) # as original source code
else: # for all other roles
return self.session.query(func.count('*')).filter(
<insert custom filter here> )
An alternative solution would be so build a custom view (without using flask-admin) and call it /myprofile.

SQLAlchemy lazy=dynamic with m2m relationship using association object pattern

I have a simple m2m relationship between users and roles tables:
users_roles = db.Table('users_roles',
db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
db.Column('role_id', db.Integer, db.ForeignKey('roles.id')),
db.Column('is_primary', db.Boolean)
)
class User(db.Model):
__tablename__ = 'users'
id = db.Column('id', db.Integer, primary_key=True)
roles = db.relationship('Role', secondary=users_roles, lazy='dynamic', backref=db.backref('users', lazy='dynamic'))
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column('id', db.Integer, primary_key=True)
users = db.relationship('User', secondary=users_roles, lazy='dynamic', backref=db.backref('roles', lazy='dynamic'))
To add a record to the users_roles table, I have to do something like this:
role = Role.get(1)
user = User()
user.roles.append(role)
db.session.add(user)
db.session.commit()
That is okay, but I have a column named is_primary in the users_roles table that should also be populated.
I changed my code to use the Association Object Pattern as described in the SQLAlchemy documentation.
Now my code looks like this:
class User(db.Model):
__tablename__ = 'users'
id = db.Column('id', db.Integer, primary_key=True)
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column('id', db.Integer, primary_key=True)
class UserRole(db.Model):
__tablename__ = 'users_roles'
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'), primary_key=True)
is_primary = db.Column(db.Boolean)
user = db.relationship(User, backref="users_roles")
role = db.relationship(Role, backref="users_roles")
User.roles = association_proxy("users_roles", "role")
Role.users = association_proxy("users_roles", "user")
It works nice, but I still have a problem.
Is it possible that User.roles (added with the association proxy) returns an AppenderBaseQuery that I can add more filters, e.g. User.query.get(1).roles.filter_by(...)?
I was used to do that with the plain many-to-many relationship using lazy=dynamic in the relationship declaration, but after giving a class mapping to the association table it seems that I cannot do it anymore.
Is there a way to achieve that?
#IfLoop I followed your recommendation in this post. Your help would be much appreciated.
Well, I ended up filtering roles using the following code:
roles = Role.query.filter_by(...).join(UserRole).join(User).filter_by(id=1)
I still want to be able to do something like this:
roles = User.query.get(1).roles.filter_by(...).all()
Anyway if I get no answers in a few days I will accept this as an answer.
Way too late for helping you but I asked myself the same question, and this paragraph of the docs shed some light on what is possible to do from proxy associations:
https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html#querying-with-association-proxies
In summary from what I understood: this is not explicitly possible but For association proxies where the immediate target is a related object or collection, relationship-oriented operators can be used instead, such as .has() and .any()
I'm not sure this will help me, but I'm laying this there if it can ever point someone to their solution

Categories

Resources