SQLAlchemy Join two tables in (1 to 0..1 ) relationship - python-2.7

I have these two tables in (1 to 0..1) relationship:
models.py
# --------------------------------------------------
class Person(db.Model):
__tablename__ = 'person'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
address = db.Column(db.String, nullable=False)
# --------------------------------------------------
class Gift(db.Model):
__tablename__ = 'gift'
id = db.Column(db.Integer, primary_key=True)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
person = relationship("Person", uselist=False, backref="gift")
gift_idea = db.Column(db.String, nullable=False)
# --------------------------------------------------
Questions:
Is the structure of my one to one tables correct? I know that in the SQLAlchemy documentation, the relationship code line (person = relationship...) is in the parent class but I need it to be in the child class, is that OK?!
My goal is to display (gift_idea from Gift table) and (person_name, person_address from Person table), if there's a gift_idea for that person. In my search I found people use query.join to get that and I have tried many codes but this following statement at least get me half the content I
need:
views.py
a = session.query(Gift).join(Person)
Could you please assist me with my questions? Thank you for any effort.

As per sqlalchemy One to One relationship pattern definition ...
One To One is essentially a bidirectional relationship with a scalar
attribute on both sides. To achieve this, the uselist flag indicates
the placement of a scalar attribute instead of a collection on the
“many” side of the relationship
In your case you've disabled "uselist" for Gifts by giving "uselist=False" (Actually by default it gets disabled). But you didn't disabled "uselist" for Persons. because of this Person can still hold list of Gifts (One person - Many Gifts).
You can get one to one mapping in two ways here..
1: By using backref function which provides arguments for the reverse side. keep your person as it is.
class Gift(db.Model):
__tablename__ = 'gift'
id = db.Column(db.Integer, primary_key=True)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
person = relationship("Person", backref=backref("gift", uselist=False))
gift_idea = db.Column(db.String, nullable=False)
2: Adding scalar attribute on both the sides instead of using backref property.
class Gift(db.Model):
__tablename__ = 'gift'
id = db.Column(db.Integer, primary_key=True)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
gift_idea = db.Column(db.String, nullable=False)
person = relationship("Person")
# -----------------------------------------------
class Person(db.Model):
__tablename__ = 'person'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
address = db.Column(db.String, nullable=False)
gift = relationship("Gift", uselist=False)
Solution for Question-2:
#val: Gave you straightforward solution for this.. you could also try using lazy loading instead of clearly writing everything over orm query.
list_of_gift_object = session.query(Gift).join(Person).all()
may be you can iterate over the list and use Gift person attribute to get associated person data.
for gift in list_of_gift_object:
print gift.gift_idea, gift.person.name, gift.person.address

In order to define proper 1-to-[0..1] relationship from the child side, you must make sure that the backref has uselist=False set, while the uselist from child to parent will be False by default:
person = db.relationship(
"Person",
backref=db.backref("gift", uselist=False),
)
Then the query is very straightforward to do:
a = (session
.query(
Gift.gift_idea,
Person.name.label("person_name"),
Person.address.label("person_address"),
)
# .select_from(Gift) # #note: optional, but will need it if the first column in the `.query(...)` above will be from table `Person`
.join(Person)
)

Related

Relative Primary Key in Flask_SQLAlchemy

Context/Minimal Example: I'm relatively new to Database design and trying to design a small app in Flask/Flask_SQLAlchemy that tracks inventory.
I have a User table:
class Users(db.Model):
user_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(25))
items = db.relationship('Item', lazy="dynamic")
and a Item table:
class Item(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'), index=True)
sku = db.Column(db.Integer, index=True, autoincrement=True)
name = db.String(10)
I would like to create a system where the Item.sku is unique... but only based on the user.id.
E.g. two users can have items of the same Sku, but one user may not have multiple items of the same sku. (And preferably have the sku automatically increment itself).
To me, this is a constraint that makes sense- sku+user_id should always be a unique combination, so I can save space and simplicity by using it as a primary key, as well as increasing the ?normalization? of the database.
However, I've spent a fair amount of time now reading and trying to figure out how to do this and I keep running into problems. Is there an easy way of accomplishing this, or is there something wrong with my logic that has lead to this design? Are there downsides to this I'm missing?
So far I've tried:
Setting both user_id and sku to primary_key=true
Setting them both to index=True (as you can see here)
Adding a table_args = db.PrimaryKeyConstraint (As discussed here https://www.reddit.com/r/flask/comments/g3tje5/composite_key_for_flasksqlalchemy/)
From what I've read the term of what I'm trying to accomplish here is a compound primary key, and that flask_sqlalchemy does support it, but with all of these I get exceptions that a constraint is failing or a parameter is missing.
Thanks for any help or advice you can provide.
Yes, a composite PK on (user_id, sku) will work, as in this example using vanilla SQLAlchemy ORM:
import sqlalchemy as db
from sqlalchemy.orm import declarative_base, relationship, Session
engine = db.create_engine("sqlite://")
Base = declarative_base()
class Users(Base):
__tablename__ = "users"
user_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(25))
items = relationship('Item', lazy="dynamic")
class Item(Base):
__tablename__ = "item"
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'), primary_key=True)
sku = db.Column(db.Integer, index=True, primary_key=True)
name = db.String(10)
Base.metadata.create_all(engine)
with Session(engine) as sess:
gord = Users(first_name="Gord", items=[Item(sku=1)])
anne = Users(first_name="Anne", items=[Item(sku=1), Item(sku=2)])
sess.add_all([gord, anne])
sess.commit()
# okay so far
# now try to add a duplicate
gord.items.append(Item(sku=1))
sess.flush()
"""
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: item.user_id, item.sku
[SQL: INSERT INTO item (user_id, sku) VALUES (?, ?)]
[parameters: (1, 1)]
"""

Flask-SQLAlchemy - a model with many-to-many relationships with two other models

Thank you in advance! I am working on a client relationship management app and am trying to configure the service level agreement models for meetings and calls. Both of these will share a many-to-many relationship with the model Month which is just the months of the year. Below is my code and the errors I am getting below that.
months = db.Table(
"months",
db.Column("month_id", db.Integer, db.ForeignKey("month.id"), primary_key=True),
db.Column("slacall_id", db.Integer, db.ForeignKey("sla_call.id"), primary_key=True),
db.Column(
"slameeting_id", db.Integer, db.ForeignKey("sla_meeting.id"), primary_key=True
),
)
class SLACall(db.Model):
id = db.Column(db.Integer, primary_key=True)
per_year = db.Column(db.Integer, nullable=False)
months = db.relationship(
"Month",
secondary=months,
lazy="subquery",
backref=db.backref("slacalls", lazy=True),
)
relationship_id = db.relationship("Relationship", backref="sla_call", lazy=True)
class SLAMeeting(db.Model):
id = db.Column(db.Integer, primary_key=True)
per_year = db.Column(db.Integer, nullable=False)
months = db.relationship(
"Month",
secondary=months,
lazy="subquery",
backref=db.backref("slameetings", lazy=True),
)
relationship_id = db.relationship("Relationship", backref="sla_meeting", lazy=True)
class Month(db.Model):
id = db.Column(db.Integer, primary_key=True)
month_name = db.Column(db.String(9), unique=True, nullable=False)
And I am getting the following error when I boot up flask for both SLA models (only one of the errors shown):
/home/kendall/Python/pm_crm/.venv/lib/python3.9/site-packages/flask_sqlalchemy/init.py:550: SAWarning: relationship 'Month.slameetings' will copy column month.id to column months.month_id, which conflicts with relationship(s): 'Month.slacalls' (copies month.id to months.month_id), 'SLACall.months' (copies month.id to months.month_id). If this is not the intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. To silence this warning, add the parameter 'overlaps="months,slacalls"' to the 'Month.slameetings' relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx)
Is this not the correct way to set this up? Or are multiple many-to-many relationships to a single model not possible?
Thank you again,
Kendall

Flask-SQLAlchemy get rows with non-zero count

I have the following two models in my database:
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
websites = db.relationship('Website', backref='category', lazy='dynamic')
class Website(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
url = db.Column(db.String(1000), nullable=False)
class_name = db.Column(db.String(50), nullable=False)
What I now want to do is write an expression for loading all the categories that have one or more websites associated with them. In SQL I can achieve this with a join and having, but I'm having trouble translating it into SQLAlchemy.
I first tried to import and use func, but then realized that it's not defined in Flask-SQLAlchemy. My next try was Category.query.having(Category.websites.count() > 1).all() but this gives me an error that AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Category.websites has an attribute 'count'. What's the right way to do this?
Any help will be appreiciated!
I believe you can still use join with group_by() and the having() func. Here's an example taken from the docs for having() that might be what you're looking for:
q = session.query(Website.id).\
join(Website.category_id).\
group_by(Category.id).\
having(func.count(Website.url) > 0)

Flask, NoForeignKeysError despite model table correctly set unless otherwise

I thought I correctly define my data models where i want to link Community with their agenda however I am having the following error.
Can you please give a hint?
NoForeignKeysError: Could not determine join condition between parent/child tables on relationship CSCommunity.agenda - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
on the following code.
class Agenda(db.Model):
__tablename__ = 'communityagenda'
id = db.Column(db.Integer, primary_key=True)
csc = db.Column(db.Integer, db.ForeignKey('scorecards.id'))
cscommunity = db.Column(db.Integer, db.ForeignKey('cscommunities.id'))
meeting_date = db.Column(db.DateTime)
notes = db.Column(db.Text)
class CSCommunity(db.Model):
__tablename = 'cscommunities'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(150))
key_leaders = db.Column(db.Text)
subgroup = db.Column(db.Text)
#---------------------------
region = db.Column(db.Integer, db.ForeignKey('regions.id'))
district = db.Column(db.Integer, db.ForeignKey('districts.id'))
subdistrict = db.Column(db.Integer, db.ForeignKey('subdistricts.id'))
village = db.Column(db.Integer, db.ForeignKey('villages.id'))
agenda = db.relationship('Agenda', backref='cscommunity', cascade='all, delete-orphan', lazy='dynamic')
faclilitators = db.relationship('Facilitators', backref='cscommunities', cascade='all, delete-orphan', lazy='dynamic')
There are couple of issues with this code (if we skip the fact that regions, districts, subdistricts, villages and Facilitators are not defined in your snippet):
__tablename = 'cscommunities' should be __tablename__ = 'cscommunities' (notice you're missing second '__')
you created backref='cscommunity' (relationship) which conflicts with Agenda.cscommunity (column). Rename your backref.
I'm getting no errors after fixing both (SQLAlchemy==1.0.4)

Set relation to many-to-many relationship

I have this many-to-many relationship that works correctly. However, now I need to have another class with a relation to this many-to-many.
currencies = db.Table('currencies_many',
db.Column('id', db.Integer, primary_key=True),
db.Column('currency_id', db.Integer, db.ForeignKey('currencies.id')),
db.Column('bank_id', db.Integer, db.ForeignKey('banks.id'))
)
class Bank(db.Model):
__tablename__ = 'banks'
id = db.Column(db.Integer, primary_key=True)
bank_name = db.Column(db.String(300))
currencies = db.relationship('Currency', secondary=currencies,
backref=db.backref('banks', lazy='dynamic'))
class Currency(db.Model):
__tablename__ = 'currencies'
id = db.Column(db.Integer, primary_key=True)
currency_name = db.Column(db.String(300))
What I mean is, for example, an order, I need to have the association to many to many.
class Order(db.Model):
__tablename__ = 'orders'
id = db.Column(db.Integer, primary_key=True)
bank_currency_identification = db.Column(db.Integer, db.ForeignKey('currencies_many.id'))
How can I do that? In my example I don't have db.relationship for bank_currency_identification, it is correct?
So if I understand your question correctly, you want to reference the currencies_many table from your orders table. If so, you are correct in having a foreign key relationship with the currencies_many table.
However, down the road you may come into some trouble when you want to query orders from your banks table. I would suggest, although it seems redundant, to create a one-to-many relationship between Order and Bank as well as between Order and Currency.
bank_id = db.Column(db.Integer, db.ForeignKey('bank.id'))
currency_id = db.Column(db.Integer, db.ForeignKey('currency.id'))
And then in the Bank class
orders = db.relationship('Order', backref='bank')
This gives you a much cleaner querying interface.
bank_orders = bank.orders
As well as makes your data model cleaner. It would be awkward to have to query orders from an intermediate table that also houses the currency. Just my two cents, but having an easy to understand Data model is better than making awkward relationships to save some redundancy.