How can I define relationships for my models in flask - 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.

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

Run filter on sqlalchemy Model relationship before passing to Marshmellow

I have two sqlalchemy models with a one-to-many relationship using lazy="dynamic like so:
class ProjectModel(db.Model):
__tablename__ = "projects"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(1000), nullable=False, unique=True)
jobs = db.relationship("JobModel", backref="project", cascade="all,delete", lazy="dynamic")
class JobModel(db.Model):
id = db.Column(db.Integer, primary_key=True)
When I deserialize a project to serve on my viewhandler, I want to run a filter on the jobs, something like so (which results in an error):
project = db.session.query(ProjectModel.first())
project.jobs = project.jobs.filter(JobModel.id == "36")
project_dump = ProjectSchema().dump(project)
The filter itself works fine, but I can't replace the jobs key in the Model object, is there another way I can run this filter so it applies when passed into a Marshmellow schema?

Flask Admin One to One Relationship and Edit Form

I am building an admin dashboard for my web app using Flask-Admin. For the user/address relationship, I am using a one to one relationship. On the user edit form, I'd like to be able to edit the individual components of the address (i.e. street address, city or zip) similar to what inline_models provides. Instead, flask-admin generates a select field and only allows me to select a different addresses.
I tried using inline_models = ['address'] in the UserModelView definition. However, I got the address object not iterable error due to the user/address relationship being configured to uselist=False. Switching uselist to True would affect other parts of my code, so I'd prefer to leave it as False.
From looking in flask-admin/contrib/sqla/forms, within the function get_forms, its being assigned a one to many tag which is what drives the use of a select field.
Before diving in further, I figured it best to see if anyone else has come across this or has a recommended fix/workaround.
models.py
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64))
address = db.relationship("Address", backref="user",
cascade="all, delete-orphan", lazy=False,
uselist=False, passive_deletes=True)
class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
line1 = db.Column(db.String(128))
zip = db.Column(db.String(20), index=True)
city = db.Column(db.String(64), index=True, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey("user.id",
ondelete="CASCADE"))
admin.py
class UserModelView(ModelView):
column_list = [User.username, 'address']
form_columns = (User.username, 'address')
admin = Admin(name='Ask', template_mode='bootstrap3')
admin.add_view(UserModelView(User, db.session))
You can create 2 relations
# Relation for flask admin inline model
address_cms_relationsip = db.relationship(
"Address", backref="user", cascade="all, delete-orphan", lazy=False,
uselist=True, passive_deletes=True)
address_relationship = db.relationship(
"Address", cascade="all, delete-orphan", lazy=False,
uselist=False, passive_deletes=True)
#property
def address(self):
return self.address_relationship
In your code you can use property address
user: User # some User object
user.address.city

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.