Flask SQLAlchemy reverse inclusion a many to many query - flask

I'm quite new to SQLAlchemy.
For this project I'm using flask and sqlalchemy.
One of the routes I need is to make is to add users to a certain group. So I need to create a list of users which are not included in this group.
I made an SQL statement and tried it in my database, which gives me the result I need, I'm just not good at converting this into SQLAlchemy
select id, email from users where id not in ( select user_id from user_group where group_id = 1);
my models look like this
UserGroup = db.Table(
"user_group",
db.Model.metadata,
db.Column("user_id", db.Integer, db.ForeignKey("users.id")),
db.Column("group_id", db.Integer, db.ForeignKey("groups.id")),
)
class Group(db.Model, RestrictionsMixin):
__tablename__ = "groups"
__versioned__ = {}
__restrictions__ = {}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False, unique=True)
class User(db.Model):
__tablename__ = "users"
__versioned__ = {}
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(250), unique=True, nullable=False)
_password = db.Column(db.LargeBinary(60), nullable=False)
authenticated = db.Column(db.Boolean, default=False)
active = db.Column(db.Boolean, default=True, nullable=False)
email_confirmation_sent_on = db.Column(db.DateTime, nullable=True)
email_confirmed = db.Column(db.Boolean, nullable=True, default=False)
email_confirmed_on = db.Column(db.DateTime, nullable=True)
registered_on = db.Column(db.DateTime, nullable=True)
last_logged_in = db.Column(db.DateTime, nullable=True)
groups = db.relationship("Group", secondary="user_group", backref="users")

You could do something like this:
user_group_subquery = (
db.session.query(UserGroup.c.user_id).filter(UserGroup.c.group_id == 1).subquery()
)
db.session.query(User.id, User.email).filter(~User.id.in_(user_group_subquery))
The tilde (~) returns the negation of a clause.

Related

Flask SqlAlchemy Many to Many Auto Populate

I have manager employee tables modeled like this using Flask SQLAlchemy:
manager_employee = db.Table('manager_employee',
db.Column('manager_id', db.Integer, db.ForeignKey('manager.manager_id'), primary_key=True),
db.Column('employee_id', db.Integer, db.ForeignKey('employee.employee_id'), primary_key=True),
)
class Manager(db.Model):
manager_id = db.Column(db.Integer, primary_key=True)
manager_employee_id = db.Column(db.Integer, db.ForeignKey('employee.employee_id'))
created_date = db.Column(db.DateTime, default=datetime.utcnow)
updated_date = db.Column(db.DateTime, index=True, nullable=True, default=None)
deleted_date = db.Column(db.DateTime, index=True, nullable=True, default=None)
employees = db.relationship('Employee', secondary=manager_employee, lazy='dynamic', backref=db.backref('manager', lazy='dynamic'))
class Employee(db.Model):
employee_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(60), index=True, nullable=False)
last_name = db.Column(db.String(60), index=True, nullable=False)
start_date = db.Column(db.Date, default=date.today())
is_admin = db.Column(db.Boolean, default=False, nullable=False)
created_date = db.Column(db.DateTime, default=datetime.utcnow)
updated_date = db.Column(db.DateTime, index=True, nullable=True, default=None)
deleted_date = db.Column(db.DateTime, index=True, nullable=True, default=None)
user_id = db.Column(db.Integer, db.ForeignKey('user.user_id'))
manager_id = db.Column(db.Integer, db.ForeignKey('manager.manager_id'))
When I add employees and managers (managers are also employees), I want the linking table (manager_employee) to auto populate with the correct id's but this is not happening. Any ideas what is wrong?

flask sqlalchemy one-to-one relationship issues

I am trying to set up a one-to-one relationship in flask. I have a script that runs db.drop_all() when I want to clear my DB. But I am getting cascade errors when doing that. I have played with many combinations using cascade and single_parent arguments in the model relationships and nothing is working. Any help would be appreciated to help set up a one-to-one relationship that will allow me to use db.drop_all() without error. Below is my latest iteration.
from sqlalchemy.sql import func
from project import db, bcrypt
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
email = db.Column(db.String(150), nullable=False)
password = db.Column(db.String(250), nullable=False)
active = db.Column(db.Boolean(), default=True, nullable=False)
created_date = db.Column(db.DateTime, default=func.now(), nullable=False)
profile = db.relationship("Profile", cascade="all, delete, delete-orphan")
def __init__(self, email, password):
self.email = email
self.password = bcrypt.generate_password_hash(password).decode('utf-8')
class Profile(db.Model):
__tablename__ = 'profile'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
first_name = db.Column(db.String(150), nullable=True)
last_name = db.Column(db.String(250), nullable=True)
github_url = db.Column(db.String(250), nullable=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
user = db.relationship("User")
from sqlalchemy.sql import func
from project import db, bcrypt
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
email = db.Column(db.String(150), nullable=False)
...
def __init__(self, email, password):
self.email = email
self.password = bcrypt.generate_password_hash(password).decode('utf-8')
class Profile(db.Model):
__tablename__ = 'profile'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
first_name = db.Column(db.String(150), nullable=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
...
user = db.relationship(
'User', cascade='all', backref=db.backref('profile', cascade='all'))
note how relationship between the two models is defined in profile model only.

flask-sqlalchemy query association table having additional columns

I have created many-to-many relationship as per below code, but i have added additional columns to the association table. I want to retrieve those columns by specific user id. Could someone advise how can query it? I have read many posts but none of them has additional columns.
If I do:
u = Users.query.filter_by(id='8').first()
u.subscriptions[0].id
I can see data from Subscription table, but if i do:
u.subscriptions[0].subscription_id
OR
u.subscriptions[0].User_Subscription
I am getting 'Subscription' object has no attribute, as I am trying to get data of columns in User_Subscription association table.
User_Subscription = db.Table('user_subscription',
db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
db.Column('subscription_id', db.Integer, db.ForeignKey('subscription.id')),
db.Column('stripe_subscription_id', db.String(100), nullable=True),
db.Column('paypal_subscription_id', db.String(100), nullable=True)
)
class Subscription(db.Model):
__tablename__ = "subscription"
id = db.Column(db.Integer, primary_key=True)
subscription_name = db.Column(db.String(100), unique=True, nullable=False)
subscription_desc = db.Column(db.String(100), unique=False, nullable=False)
date_created = db.Column(db.DateTime, default=datetime.datetime.utcnow)
date_updated = db.Column(db.DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
subscriptions = db.relationship('Users', secondary=User_Subscription, backref= db.backref('subscriptions', lazy='dynamic'))
class Users(db.Model, UserMixin):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
firstname = db.Column(db.String(50), unique=False, nullable=False)
lastname = db.Column(db.String(50), unique=False, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
date_created = db.Column(db.DateTime, default=datetime.datetime.utcnow)
date_updated = db.Column(db.DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
user_status_id = db.Column(db.String(2), db.ForeignKey('user_status.id'))
You've got the error ('Subscription' object has no attribute) because it is an relationship object, not table.
In this line of code:
u.subscriptions[0].User_Subscription
you are trying to access attribute of the object User_Subcription which is the relationship object.
If you want to create new association table, create same table in your database and define one more class UserSubscription:
class UserSubscription(db.Model)
__tablename__ = 'user_subscription'
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
subscription_id = db.Column(db.Integer, db.ForeignKey('subscription.id'))
stripe_subscription_id = db.Column(db.String(100), nullable=True)
paypal_subscription_id = db.Column(db.String(100), nullable=True)
user = db.relationship('Users', backref='user_subscription')
And then you can access attribute of UserSubscription class:
u.user_subscription.subscription_id

Flask-Admin with Flask Security log in to admin backend

I updated my user model from:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(255))
last_name = db.Column(db.String(255))
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmation = db.Column(db.Boolean)
email_confirmation_sent_on = db.Column(db.DateTime, nullable=True)
confirmed_at = db.Column(db.DateTime, nullable=True)
registered_on = db.Column(db.DateTime, nullable=True)
last_login_at = db.Column(db.DateTime, nullable=True)
current_login_at = db.Column(db.DateTime, nullable=True)
last_login_ip = db.Column(db.String)
current_login_ip = db.Column(db.String)
login_count = db.Column(db.Integer)
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
To:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(255))
last_name = db.Column(db.String(255))
email = db.Column(db.String(255), unique=True)
_password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmation = db.Column(db.Boolean)
email_confirmation_sent_on = db.Column(db.DateTime, nullable=True)
confirmed_at = db.Column(db.DateTime, nullable=True)
registered_on = db.Column(db.DateTime, nullable=True)
last_login_at = db.Column(db.DateTime, nullable=True)
current_login_at = db.Column(db.DateTime, nullable=True)
last_login_ip = db.Column(db.String)
current_login_ip = db.Column(db.String)
login_count = db.Column(db.Integer)
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
#hybrid_property
def password(self):
return self._password
#password.setter
def set_password(self, plaintext):
self._password = hash_password(plaintext)
So, that I could hash passwords from the User model. However, now I can't login to the user backend. I think it is because it doesn't recognize _password on the user built-in forms? Is there a way I can take advantage of the #password.setter decorator and still use Flask-security?
I'm not super familiar with Flask-Security, so this is a bit of a guess:
Flask-Security is probably using your password property, but your own password hashing and Flask-Security's are probably interfering with each other.
You could test this by just redefining set_password to simply set the plaintext:
#password.setter
def set_password(self, plaintext):
self._password = plaintext
If that fixes your login issue, you might be able to get what you want from both flask-admin and flask-security by creating a second property/decorator (both operating on ._password, and point flask-admin at a different one. The hitch is that you'll need to make sure your password hashing algorithms match (so you may want to re-use Flask-Security's in both locations). This might look like:
# models.py
class User(db.Model, UserMixin):
...
_password = db.Column(db.String(255))
...
#hybrid_property
def password(self):
return self._password
#password.setter
def set_password(self, hashed):
self._password = hashed
#hybrid_property
def override_password(self):
return self._password
#override_password.setter
def override_password(self, plaintext):
self._password = hash_password(plaintext)
# admin.py
class UserView(MyModelView):
form_columns = ('email', 'override_password', ...)
It may also be possible to get what you want by customizing/configuring Flask-Security or the underlying packages that handle login flow and password hashing.

one-one relation sqlalchemy flask

I am trying to implement 1-1 relation for User and Profile Tabels like below.
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))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime(), default=datetime.now())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
def __repr__(self):
return '<User {self.email}>'.format(self=self)
class CustomerProfile(db.Model):
__tablename__ = 'customer_profiles'
id = db.Column(db.Integer(), primary_key=True)
full_name = db.Column(db.String(200), nullable=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), unique=True)
user = db.relationship('User', backref=db.backref("CustomerProfile", uselist=False))
def __repr__(self):
return '<CustomerProfile {self.full_name}>'.format(self=self)
My question is:
Is this the correct representation of 1-1 mapping? Cause when I try to reverse engineer the database with MySQL Workbench, it does show me one to many mapping
If this is the correct representation, then inserting a duplicate row is being allowed unless I give unique=True in user_id. I would have expected that since I told MySql about the mapping, it should not allow duplicate row...Is that not true?
after having a look at http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#one-to-one
I changed my code to below:
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))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime(), default=datetime.now())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
customer_profiles = db.relationship('CustomerProfile', uselist=False, back_populates="user")
def __repr__(self):
return '<User {self.email}>'.format(self=self)
class CustomerProfile(db.Model):
__tablename__ = 'customer_profiles'
id = db.Column(db.Integer(), primary_key=True)
full_name = db.Column(db.String(200), nullable=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
user = db.relationship('User', back_populates='customer_profiles')
def __repr__(self):
return '<CustomerProfile {self.full_name}>'.format(self=self)
but still, I am able to insert a duplicate row into profiles....
Since I am mapping it at the DB level, I am assuming that DB will stop me from inserting a duplicate row even though I do not give unique=True.. Is my understanding correct?