SQL db relationship between two models - flask

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).

Related

Flask SQLAlchemy join 2 tables

I have 2 related tables (Product, OrderItem) that I want to retrieve data from but can't seem to get my way around. I can only fetch data from the first table but not from the second one.
Here's my query in my route to fetch data from the 2 tables:
#admin.route('/generate/invoice/<order_number>')
def generate_invoice(order_number):
order = Order.query.filter_by(order_number=order_number).first()
products = Product.query.join(OrderItem, (Product.id==OrderItem.product_id)).filter_by(order_id=order.id).all()
rendered = render_template('pdfs/invoice_pdf.html', order=order, products=products)
So in my html I am able to get data from the first table using product.name but I fail to get data from the second table. I actually want to get the quantity of the product from OrderItem table using product.order_items.quantity. Here's the html I have to iterate over the results.
{% for product in products %}
<tr>
<td>{{ product.name }}</td>
<td>{{ product.order_items.quantity}}</td>
</tr>
{% endfor %}
The Product model is as follow
class Product(db.Model):
__tablename__ = 'products'
__searchable__ = ['name', 'description']
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(200))
description = db.Column(db.Text)
price = db.Column(db.Float(10, 2))
discount = db.Column(db.Float(10, 2))
quantity = db.Column(db.Integer)
image = db.Column(db.Text)
product_code = db.Column(db.Text, unique=True)
def __repr__(self):
return '<Product %r>' % self.name
The other related models
class Order(db.Model):
__tablename__ = 'orders'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True)
grand_total = db.Column(db.Float(10, 2), nullable=False)
status = db.Column(db.Enum('Pending', 'Completed', 'Cancelled', 'Dispatched'), nullable=False, default='Pending')
created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
payment_type = db.Column(db.String(256), nullable=False)
order_number = db.Column(db.String(256), nullable=False)
user = db.relationship('User', primaryjoin='Order.user_id == User.id', backref='orders')
def __repr__(self):
return '<Order %r>' % self.id
class OrderItem(db.Model):
__tablename__ = 'order_items'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
order_id = db.Column(db.ForeignKey('orders.id', ondelete='CASCADE'), nullable=False, index=True)
product_id = db.Column(db.Integer, nullable=False)
item_price = db.Column(db.Float(10, 2), nullable=False)
quantity = db.Column(db.Integer, nullable=False)
order = db.relationship('Order', primaryjoin='OrderItem.order_id == Order.id', backref='order_items')
def __repr__(self):
return '<OrderItem %r>' % self.id
What could be wrong with my query or models because I get the following error when I run the code:
jinja2.exceptions.UndefinedError: 'core.models.Product object' has no attribute 'order_items'

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.

SQL Alchemy foreignkey reference on three tables

I am creating a website using flask with sql-alchemy database. I have three tables with foreign keys referencing to each other. How can I get the username and phone from the first table by referencing two foreign keys? My code is below:
models.py
class User(db.Model, UserMixin):
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), unique=True, nullable=False)
phone = db.Column(db.Integer, nullable=True)
user_role = db.Column(db.String(20), nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
pgs = db.relationship('PGInfo', backref='owner', lazy=True)
bookedpgs = db.relationship('PGBooked', backref='customer', lazy=True)
def __repr__(self):
return f"User('{self.username}', '{self.email}')"
class PGInfo(db.Model):
id = db.Column(db.Integer, primary_key=True)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
pg_name = db.Column(db.String(200), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
location_info = db.Column(db.String(120), unique=True, nullable=False)
body = db.Column(db.Text, nullable=False)
price = db.Column(db.Integer, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
def __repr__(self):
return f"PGInfo('{self.pg_name}', '{self.date_posted}', '{self.location_info}', '{self.price}', '{self.image_file}')"
class PGBooked(db.Model):
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
pg_id = db.Column(db.Integer, db.ForeignKey(PGInfo.id), nullable=False)
def __repr__(self):
return f"PGBooked('{self.name}')"
routes.py
def book_pg(pg_id):
bookedpg = PGBooked(customer=current_user, owner=PGInfo.owner_id.username, pg_name=PGInfo.pg_name, location=PGInfo.location_info, phone=PGInfo.owner_id.phone)
db.session.add(bookedpg)
db.session.commit()
flash("You have booked the pg!", 'success')
return render_template('bookpg.html', title='Book PG')
bookpg.html
{% extends "layout.html" %}
{% block content %}
{{ bookedpg.customer }}
{{ bookedpg.owner }}
{{ bookedpg.pg_name }}
{{ bookedpg.location }}
{{ bookedpg.phone }}
{% endblock content %}
error
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with PGInfo.owner_id has an attribute 'username'
So how can I get the username according to the above relationships? Thank you in advance!
The mistake was in the models.py file. The correct code is as below:
models.py
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(200), unique=True, nullable=False)
email = db.Column(db.String(200), unique=True, nullable=False)
password = db.Column(db.String(60), unique=True, nullable=False)
phone = db.Column(db.Integer, nullable=True)
user_role = db.Column(db.String(20), nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
pgs = db.relationship('PGInfo', backref='owner', lazy=True)
bookedpgs = db.relationship('PGBooked', backref='customer', lazy=True)
def __repr__(self):
return f"User('{self.username}', '{self.email}')"
class PGInfo(db.Model):
id = db.Column(db.Integer, primary_key=True)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
pg_name = db.Column(db.String(200), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
location_info = db.Column(db.String(200), nullable=False)
body = db.Column(db.Text, nullable=False)
price = db.Column(db.Integer, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
pg = db.relationship('PGBooked', backref='pg', lazy=True)
def __repr__(self):
return f"PGInfo('{self.pg_name}', '{self.date_posted}', '{self.location_info}', '{self.price}', '{self.image_file}')"
class PGBooked(db.Model):
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer, db.ForeignKey('user.id'))
pg_id = db.Column(db.Integer, db.ForeignKey(PGInfo.id))
name = db.Column(db.String(200), nullable=False)
location_info = db.Column(db.String(200), nullable=False)
owner = db.Column(db.String(200), nullable=False)
phone = db.Column(db.Integer)
def __repr__(self):
return f"PGBooked('{self.name}')"
I had to create a relationship between PGInfo table and PGBooked table.
routes.py
#app.route("/pg/<int:pg_id>/book_pg", methods=['GET','POST'])
#login_required
def book_pg(pg_id):
if current_user.is_authenticated:
pg = PGInfo.query.get_or_404(pg_id)
bookedpg = PGBooked(customer=current_user, name=pg.pg_name, location_info=pg.location_info, owner=pg.owner.username, phone=pg.owner.phone)
db.session.add(bookedpg)
db.session.commit()
# db.session.delete(pg)
# db.session.commit()
flash("You have booked the pg!", 'success')
return redirect(url_for('display_booked_pgs'))
#app.route("/booked_pgs", methods=['GET', 'POST'])
#login_required
def display_booked_pgs():
bookedpgs = PGBooked.query.filter_by(customer_id=current_user.id).all()
# bookedpgs_dict = dict((col, getattr(bookedpgs, col)) for col in bookedpgs.__table__.columns.keys())
return render_template('bookedpg.html', title='Booked PGs', bookedpgs=bookedpgs)
Then I can display in the template by looping over as follows:
bookpg.html
{% extends "layout.html" %}
{% block content %}
<div>
{% for item in bookedpgs %}
<p> {{ item.__dict__ }} </p>
{% endfor %}
</div>
{% endblock content %}

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?