flask sqlalchemy one-to-one relationship issues - flask

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.

Related

SQL db relationship between two models

I'm currently making a website and having some troubles with showing user the {{ patient.name }} with out them writing it. The name can be just filter by the patient.id, if patient.id and detail.id has the same id, for example 1, then they are sharing the same name and information.
Although I'm just getting errors in
routes.py
#app.route("/add-patient-detail/<int:patient_id>", methods=['GET', 'POST'])
#login_required
def add_patient_detail(patient_id):
form = DetailForm()
if form.validate_on_submit():
detail = Detail(detail=patient.id, Symptom=form.Symptom.data, Initial_diagnosis=form.Initial_diagnosis.data,
Preliminary_treatment_plan=form.Preliminary_treatment_plan.data, Check_result=form.Check_result.data,
Patient_reason=form.Patient_reason.data, Formula=form.Formula.data) # detail=patient.id is the part with error
db.session.add(detail)
db.session.commit()
flash('此患者已被加入进数据库当中', 'success')
return redirect(url_for('home'))
return render_template('add-patient-detail.html', title='Add Patient Detail', form=form)
models.py
class Patient(db.Model, UserMixin):
__bind_key__ = 'patient'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(10), nullable=False)
number = db.Column(db.String(11), unique=False, nullable=False)
gender = db.Column(db.String(2), nullable=False)
birth = db.Column(db.String(10), nullable=False)
IDcard = db.Column(db.String(12), nullable=False)
create = db.Column(db.DateTime, nullable=False, default=datetime.now)
details = db.relationship('Detail', backref='detail', lazy=True)
class Detail(db.Model, UserMixin):
__bind_key__ = 'detail'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=False, nullable=False)
Symptom = db.Column(db.String(100), nullable=False)
Initial_diagnosis = db.Column(db.String(50), nullable=False)
Preliminary_treatment_plan = db.Column(db.String(100), nullable=False)
Check_result = db.Column(db.String(50), nullable=False)
Patient_reason = db.Column(db.String(100), unique=False, nullable=False)
Formula = db.Column(db.String(100), unique=False, nullable=False)
Doctor_name = db.Column(db.String(20), unique=False, nullable=False)
Date_of_diagnosis = db.Column(db.DateTime, nullable=False, default=datetime.now)
patient_id = db.Column(db.Integer, db.ForeignKey('patient.id'), nullable=False)
Two things i'm noticing: 1.) You are passing patient_detail to your endpoint, patient.id is not defined anywhere so you won't be able to use that.
2.) There is no column called detail on your Detail model. From looking at your model, the join would be on the patient_id column.
Try this:
#app.route("/add-patient-detail/<int:patient_id>", methods=['GET', 'POST'])
#login_required
def add_patient_detail(patient_id): #NOTE that you're passing patient_id not patient.id
form = DetailForm()
if form.validate_on_submit():
detail = Detail(patient_id=patient_id, Symptom=form.Symptom.data, Initial_diagnosis=form.Initial_diagnosis.data,
Preliminary_treatment_plan=form.Preliminary_treatment_plan.data, Check_result=form.Check_result.data,
Patient_reason=form.Patient_reason.data, Formula=form.Formula.data) # detail=patient.id is now patient_id=patient_id
db.session.add(detail)
db.session.commit()
flash('此患者已被加入进数据库当中', 'success')
return redirect(url_for('home'))
return render_template('add-patient-detail.html', title='Add Patient Detail', form=form)
Note where I changed what I mentioned in 1) and 2).

Flask SQLAlchemy reverse inclusion a many to many query

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.

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?