Flask-Sqlalchemy many-to-one on same model - python

I'm developing a website with dashboard which admins and moderators can create users. I want to separate users created by moderator and admin. Moderator can only edit the users that him/her created but admin can edit all the users.
I created my user model like this:
class User(UserMixin, ResourceMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# User details
name = db.Column(db.String(50), index=True)
phone = db.Column(db.String(24))
address = db.Column(db.String(255))
email = db.Column(db.String(255), unique=True, index=True, nullable=False,
server_default='')
password = db.Column(db.String(128), nullable=False, server_default='')
# Relationships
created_id = db.Column(db.Integer,
db.ForeignKey('users.id', onupdate='CASCADE', ondelete='SET NULL'),
index=True)
updated_id = db.Column(db.Integer,
db.ForeignKey('users.id', onupdate='CASCADE', ondelete='SET NULL'),
index=True)
Most of the situations this code works like charm. But when moderator or admin tries to edit self, program throws CircularDependency error. What might cause the problem?
Thanks

Related

Best way to inherit from User table (SQLAlchemy & UserMixin)

I have a SQLALchemy table:
class User(db.Model, UserMixin):
user_id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(100), nullable=False, unique=True)
password_hash = db.Column(db.String(40), nullable=False)
But I want users to be able login and register using either by creating a new account with username and password, or using a social media account (google/twitter/vk.com). My idea is to have a base User table that will have abstract methods like get_name() that will have to be defined in the derived tables for each social media.
Pseudocode for this:
Base user
class User(db.Model, UserMixin):
user_id = db.Column(db.Integer, primary_key=True)
# Overridden method from UserMixin
def get_id(self):
return self.user_id
def get_name(self): raise NotImplementedError
Google user
class GoogleUser(User):
first_name = db.Columnn(db.String(40), nullable=False)
last_name = db.Columnn(db.String(40), nullable=False)
def get_name(self):
return '{} {}'.format(self.first_name, self.last_name)
E-mail user
class EmailUser(User):
username = db.Column(db.String(100), nullable=False, unique=True)
password_hash = db.Column(db.String(40), nullable=False)
def get_name(self):
return self.username
The question is what is the best way to implement it with SQLAlchemy & UserMixin?
Thanks for any help in advance.
What I usually do is create a User model with all the information that is needed by the application e.g.
class User(db.Model, UserMixin):
user_id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(100), nullable=False, unique=True)
password_hash = db.Column(db.String(40), nullable=True)
Then for third party services authentication, I create a table which stores token and other relevant data regarding that service that the application requirese.g.
class GoogleUser(db.Model):
first_name = db.Column(db.String(40), nullable=False)
last_name = db.Column(db.String(40), nullable=False)
google_token = db.Column(db.String(200), nullable=False)
I connect this new table to the User model via foreign key:
google_user_id = db.Column(db.Integer, db.ForeignKey("google_user.id"))
This way the user can easily connect their third party accounts and have an account on your application.
Facebook Login's documentation also recommends this
Hope it clears things up!

Flask-SQLAlchemy 'NoForeignKeysError'

I am working on a Flask app, using Flask-SQLAlchemy extension for database interactions. Since I have multiple apps writing on the same DB, I was getting concurrency issues with SQLite and I wanted to switch to PostgreSQL instead. I am able to create the tables on new database without a problem and pgAdmin displays the tables and columns.
# working
def createTables():
with app.app_context():
from models import User, Invoice
db.create_all()
But when it comes to adding a user, I am now getting an error: sqlalchemy.exc.NoForeignKeysError Although, I think, I declared one-to-many relationship in my models, based on the documentation, I get an error states that "there are no foreign keys linking these tables."
# not working
def create_test_user():
with app.app_context():
user = User(
username="Bob",
email="bob#email.com",
password="test"
)
db.session.add(user)
db.session.commit()
The full error message:
""" NoForeignKeysError: Could not determine join condition between parent/child tables on relationship User.invoices
- 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 can't figure out what causes the error. What is missing with my models?
# models.py
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
invoices = db.relationship('Invoice', backref='user', lazy=True)
class Invoice(db.Model):
__tablename__ = "invoice"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
amount = db.Column(db.Integer, nullable=False)
Solved
Your code works for me. Maybe you need to re-create your tables or something similar. To be sure that we have the identical code: I have tested the following code:
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
invoices = db.relationship('Invoice', backref='user', lazy=True)
class Invoice(db.Model):
__tablename__ = "invoice"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
amount = db.Column(db.Integer, nullable=False)
In the route:
user = User(
username="Bob",
email="bob#email.com",
password="test"
)
db.session.add(user)
db.session.commit()
print(user)
I finally solved the problem and it was not where I was looking for. I was getting NoForeignKeysError due to importing a wrong model file during initializing the app. One of my imported modules was calling a wrong/old version of the model. It was causing the table relationship in the actual model to break I guess.
When I went through step by step create_test_user() I noticed that the error occurs actually during the class creation, before even it hits to db.session.add and I replicated the error even without a DB. I went through all my modules that are calling the models and caught wrong model import.

Self-reference not working on Flask (SQLAlchemy)

I'm using Flask and SQLAlchemy to create a web app and I'm having troubles with self-reference. On my project I have companies (model), a company can have a provider (another company) or not. If one company is a provider, that company should have a list of clients (every company that it provides services).
Using SQLAlchemy Documentation I've tried to make this self-reference relationship.
This are my models:
from flask_login import UserMixin
from . import db
class Organization(db.Model):
id = db.Column(db.Integer, primary_key=True)
rut = db.Column(db.String(10), unique=True)
name = db.Column(db.String(100))
users = db.relationship('User', backref='organization', lazy=True)
class Company(db.Model):
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(100), unique=True)
name = db.Column(db.String(100))
provider_id = db.Column(db.Integer, db.ForeignKey('company.id'), nullable=True)
clients = db.relationship('Company', remote_side=[id])
users = db.relationship('User', backref='company', lazy=False)
class User(UserMixin, db.Model):
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))
last_name = db.Column(db.String(1000))
organization_id = db.Column(db.Integer, db.ForeignKey('organization.id'), nullable=False)
company_id = db.Column(db.Integer, db.ForeignKey('company.id'), nullable=True)
is_admin = db.Column(db.Boolean, default=False)
is_active = db.Column(db.Boolean, default=False)
The problem is that, even though I can register the provider_id (provider company) I cannot relate this with the clients field on the provider object. So If I have this company #1 which is company #2's provider, and I check the company #1's clients it's empty. Any ideas of what am I doing wrong?
This is my first time working with Flask and SQLAlchemy.
And I need to set up the list of clients because to delete companies I want to be able to delete just the ones with no clients.

Flask-SQLAlchemy -- One model having two different relationships

I have three models: Role, User, and Post. Role -> User is one to many, and User -> Post is one to many. When I just had User and Post, everything worked. However, my website will need varying degrees of authorization.
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
users = db.relationship('User', backref='role', lazy=True)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
Here is the error message I get:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError)
no such column: user.role_id
Thanks!
You don't need to explicitly define the role_id field - the relationship defined in Role (and the backref parameter you specified) has already created a backwards relationship on User objects for you. (The SQLAlchemy documentation around backrefs may be helpful to you.)
As such, if you have an instantiated User object u, you should be able to get details about the user's role via u.role (which will give you a Role instance) and the ID of the role as u.role.id.
Thus, your full set of model definitions only needs to look like this:
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
users = db.relationship('User', backref='role', lazy=True)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
posts = db.relationship('Post', backref='author', lazy=True)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
If you do this, then for a Post object p, you can get the id of the role of the author of the post as p.author.role.id (for example).

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

Categories