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?
Related
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).
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.
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
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.
I am using Flask-admin and SQLAlchemy.
I want to limit the choices of a foreign key based on a choice of the foreign key of the parent table.
class City(Base):
__tablename__ = 'city'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
def __unicode__(self):
return self.name
class Street(Base):
__tablename__ = 'street'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
city = Column(Integer, ForeignKey(City.id), nullable=False)
city_ref = relationship(City)
def __unicode__(self):
return self.name
class Adress(Base):
__tablename__ = 'adress'
id = Column(Integer, primary_key=True)
familiyname = Column(String, nullable=False)
street = Column(Integer, ForeignKey(Street.id), nullable=False)
street_ref = relationship(Street)
city_ref = relationship("City",
secondary="join(Street,City,Street.city==City.id)",
primaryjoin="and_(Adress.street==Street.id)",
secondaryjoin="City.id == Street.city")
def __unicode__(self):
return self.name
Now I want to add an adress for a family. But first I want to select the city and based on that choice I want to filter the available streets.
How can this be done?
admin.add_view(sqla.ModelView(Adress, db.session))
This also shows the city but the street column is not filtered when a city is chosen.