Set relation to many-to-many relationship - python-2.7

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.

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

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

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

Flask-SQLAlchemy: How to make multiple one-to-one relationships between the same classes?

How do you make multiple a one-to-one relationships between the same classes?
For example, I want to have a class "Location" which has two different type of "LoadDetail" which I have named "pickup_details" and "delivery_details". Can someone explain?
Here is what I so far-
class Location(db.Model):
id = db.Column(db.Integer, primary_key=True)
pickup_detail = db.relationship("LoadDetail", foreign_keys='LoadDetail.pickup_id', backref='pickup_location')
delivery_detail = db.relationship("LoadDetail", foreign_keys='LoadDetail.delivery_id', backref='delivery_location')
class LoadDetail(db.Model):
id = db.Column(db.Integer, primary_key=True)
pickup_id = db.Column(db.Integer, db.ForeignKey('location.id'))
delivery_id = db.Column(db.Integer, db.ForeignKey('location.id'))
Thanks!
You'll want to set the primaryjoin of the relationship. The example in the documentation sounds almost exactly like what you're trying to do.

How can I define relationships for my models in flask

I am new to flask and ORM technology and am doing one sample project for my learning.
In my application, a trainer can teach one or more technologies, so I define my tables as below:
Trainer:
id
name
phone
email
Technology:
id
tech_name
Assert:
id
trainer_id
technology_id
Can anyone help me on how to convert the above table definitions into models with proper relationships?
What you want is a Many to Many relationship. The Flask-SQLAlchemy documentation supplies an example of that here.
You have the right idea about having a table to link them together, but you don't really require the id column in there, below is the example bent to your models.
technologies = db.Table('technologies',
db.Column('trainer_id', db.Integer, db.ForeignKey('trainer.id')),
db.Column('tech_id', db.Integer, db.ForeignKey('tech.id'))
)
class Trainer(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
phone = db.Column(db.String)
email = db.Column(db.String)
technologies = db.relationship('Tech', secondary=technologies,
backref=db.backref('trainers', lazy='dynamic'))
class Tech(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
So now you can add Trainers, and Techs, then assign them as required:
nano_tech = Tech(name='Nano')
mega_tech = Tech(name='Mega')
bob = Trainer(name='Bob', email='Whatever', technologies=[nano_tech, mega_tech])
Or you could add existing technologies to an existing trainer
trainer = Trainer.query.filter_by(name='Alice').first()
tech = Tech.query.filter_by(name='Super').first()
trainer.technologies.append(tech)
Or any combination therein.