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'
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 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 %}
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?
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.