FastAPI - SQLModel, Relationship and Insertion of New Row leading to Insertion of New Row in Another Table - postgresql

Suppose I have two tables, Email and VStatus ('V' stands for 'Verification').
class Email(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
email: str
vstatus_id: Optional[int] = Field(default=None, foreign_key='VStatus.id')
vstatus: Optional["VStatus"] = Relationship()
class VStatus(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
vstatus: StatusEnum = Field(default=StatusEnum.Unverified) # InProgress, Verified, Unverified
vdesc: str = Field(default="") # Verification Description
How should I structure my SQLModel tables such that whenever I create a new email via the API say '/create_email', with just a single field email, it will automatically created a new row in the VStatus table via the Relationship defined in the Email table?
Example:
POST /create_email
{
'email': 'example#example.com'
}
I will get a response that looks like:
response = {
'email': 'example#example.com',
'vstatus_id': '1001', # example
}
And in the VStatus table, there will be a new row that looks like:
id
vstatus
vdesc
1001
Unverified
Null

Related

How to stop SqlAlchemy from generating SQL that inserts or updates the primary key?

I have a model:
class Contact(db.Model):
__tablename__ = 'contact'
contactid = db.Column(db.BigInteger, primary_key=True, server_default=db.text("nextval('contact_contactid_seq'::regclass)"))
firstname = db.Column(db.Text)
lastname = db.Column(db.Text)
and a form
class ContactForm(FlaskForm):
contactid = HiddenField('contactid')
firstname = StringField('First Name', validators=[Optional()], filters = [lambda x: x or None])
lastname = StringField('Last Name', validators=[Optional()], filters = [lambda x: x or None])
submit = SubmitField('Save')
I use code similar to the following to insert and update records in the database using the above model and form
#blueprint.route('/create', methods=['GET', 'POST'])
def create():
form = ContactForm()
if form.validate_on_submit():
r = Contact()
form.populate_obj(r)
db.session.add(r)
db.session.commit()
flash("saved new record", "success")
return root()
return render_template('contact/create.html', form=form)
#blueprint.route('/edit/<id>', methods=['GET', 'POST'])
def edit(id: int):
r = Contact.query.get_or_404(id)
form = ContactForm(obj=r)
if form.validate_on_submit():
form.populate_obj(r)
db.session.commit()
flash("updated record", "success")
return redirect(url_for('blueprint.root'))
return render_template('contact/edit.html', form=form)
The Problem
SQLAlchemy appears to be generating SQL like this:
INSERT INTO contact (contactid, firstname, lastname) VALUES (%(contactid)s, %(firstname)s, %(lastname)s)
UPDATE contact set contactid=%(contactid)s, firstname=%(firstname)s, lastname=%(lastname)s WHERE contactid = %(contactid)s
I want it to do it like this:
INSERT INTO contact (firstname, lastname) VALUES (%(firstname)s, %(lastname)s)
SELECT last_contactid_somehow
UPDATE contact set firstname=%(firstname)s, lastname=%(lastname)s WHERE contactid = %(contactid)s
What am I doing wrong?
I would like a solution where SqlAlchemy:
handles the auto-incrementing primary key without sticking it into the insert statement
doesn't set the primary key in the update statement
I am using Postgres
I think the first problem will be solved by setting autoincrement=True
class Contact(db.Model):
__tablename__ = 'contact'
contactid = db.Column(db.BigInteger, primary_key=True, autoincrement=True, server_default=db.text("nextval('contact_contactid_seq'::regclass)"))
...
the second problem is actually working properly because you defined the form class like that.
In addition, the query you want and the query generated by SQLAlchemy are the same because the contractid in the where clause and the contractid in the set clause is same.
Nevertheless, if you want to change the query, change the value of the model object.
r = Contact.query.get_or_404(id)
form = ContactForm(obj=r)
if form.validate_on_submit():
r.firstname = form.firstname.data
r.lastname = form.lastname.data
db.session.commit()
...

How to Initialise & Populate a Postgres Database with Circular ForeignKeys in SQLModel?

Goal:
I'm trying to use SQLModel (a wrapper that ties together pydantic and sqlalchemy) to define and interact with the back-end database for a cleaning company. Specifically, trying to model a system where customers can have multiple properties that need to be cleaned and each customer has a single lead person who has a single mailing property (to contact them at). Ideally, I want to be able to use a single table for the mailing properties and cleaning properties (as in most instances they will be the same).
Constraints:
Customers can be either individual people or organisations
A lead person must be identifiable for each customer
Each person must be matched to a property (so that their mailing address can be identified)
A single customer can have multiple properties attached to them (e.g. for a landlord that includes cleaning as part of the rent)
The issue is that the foreign keys have a circular dependency.
Customer -> Person based on the lead_person_id
Person -> Property based on the mailing_property_id
Property -> Customer based on the occupant_customer_id
Code to reproduce the issue:
# Imports
from typing import Optional, List
from sqlmodel import Session, Field, SQLModel, Relationship, create_engine
import uuid as uuid_pkg
# Defining schemas
class Person(SQLModel, table=True):
person_id: uuid_pkg.UUID = Field(default_factory=uuid_pkg.uuid4, primary_key=True, index=True, nullable=True)
first_names: str
last_name: str
mailing_property_id: uuid_pkg.UUID = Field(foreign_key='property.property_id')
customer: Optional['Customer'] = Relationship(back_populates='lead_person')
mailing_property: Optional['Property'] = Relationship(back_populates='person')
class Customer(SQLModel, table=True):
customer_id: uuid_pkg.UUID = Field(default_factory=uuid_pkg.uuid4, primary_key=True, index=True, nullable=True)
lead_person_id: uuid_pkg.UUID = Field(foreign_key='person.person_id')
contract_type: str
lead_person: Optional['Person'] = Relationship(back_populates='customer')
contracted_properties: Optional[List['Property']] = Relationship(back_populates='occupant_customer')
class Property(SQLModel, table=True):
property_id: uuid_pkg.UUID = Field(default_factory=uuid_pkg.uuid4, primary_key=True, index=True, nullable=True)
occupant_customer_id: uuid_pkg.UUID = Field(foreign_key='customer.customer_id')
address: str
person: Optional['Person'] = Relationship(back_populates='mailing_property')
occupant_customer: Optional['Customer'] = Relationship(back_populates='contracted_properties')
# Initialising the database
engine = create_engine(f'postgresql://{DB_USERNAME}:{DB_PASSWORD}#{DB_URL}:{DB_PORT}/{DB_NAME}')
SQLModel.metadata.create_all(engine)
# Defining the database entries
john = Person(
person_id = 'eb7a0f5d-e09b-4b36-8e15-e9541ea7bd6e',
first_names = 'John',
last_name = 'Smith',
mailing_property_id = '4d6aed8d-d1a2-4152-ae4b-662baddcbef4'
)
johns_lettings = Customer(
customer_id = 'cb58199b-d7cf-4d94-a4ba-e7bb32f1cda4',
lead_person_id = 'eb7a0f5d-e09b-4b36-8e15-e9541ea7bd6e',
contract_type = 'Landlord Premium'
)
johns_property_1 = Property(
property_id = '4d6aed8d-d1a2-4152-ae4b-662baddcbef4',
occupant_customer_id = 'cb58199b-d7cf-4d94-a4ba-e7bb32f1cda4',
address = '123 High Street'
)
johns_property_2 = Property(
property_id = '2ac15ac9-9ab3-4a7c-80ad-961dd565ab0a',
occupant_customer_id = 'cb58199b-d7cf-4d94-a4ba-e7bb32f1cda4',
address = '456 High Street'
)
# Committing the database entries
with Session(engine) as session:
session.add(john)
session.add(johns_lettings)
session.add(johns_property_1)
session.add(johns_property_2)
session.commit()
Results in:
ForeignKeyViolation: insert or update on table "customer" violates foreign key constraint "customer_lead_person_id_fkey"
DETAIL: Key (lead_person_id)=(eb7a0f5d-e09b-4b36-8e15-e9541ea7bd6e) is not present in table "person".
This issue is specific to Postgres, which unlike SQLite (used in the docs) imposes constraints on foreign keys when data is being added. I.e. replacing engine = create_engine(f'postgresql://{DB_USERNAME}:{DB_PASSWORD}#{DB_URL}:{DB_PORT}/{DB_NAME}') with engine = create_engine('sqlite:///test.db') will let the database be initialised without causing an error - however my use-case is with a Postgres DB.
Attempted Solutions:
Used link tables between customers/people and properties/customers - no luck
Used Session.exec with this code from SO to temporarily remove foreign key constraints then add them back on - no luck
Used primary joins instead of foreign keys as described in this SQLModel Issue - no luck

How to insert foreign keys into a table based on current user

I am building a web app in Flask-SQLAlchemy with 3 main types of users (carrier, broker, shipper) who can each access different parts of the site. I am wanting to connect employees and other components to their organizations through foreign keys. My tables look like this:
carrier_org:
id = db.Column(db.Integer, primary_key=True)
org_name = db.Column(db.String(50), nullable=False)
email = db.Column(db.String(120), nullable=False)
contact:
id = db.Column(db.Integer, primary_key=True)
carrier_org = db.Column(db.Integer, db.ForeignKey('carrier_org.id'),
nullable=False)
When the user signs up, it creates both a newUser and newCarrier object.
When I am signed in as a carrier, I have a method addContact that looks like this:
#app.route('/add_contact', methods = ['POST', 'GET'])
#required_roles('carrier')
#login_required
def addContact():
if request.method == 'POST':
f_name = request.form['f_name']
l_name = request.form['l_name']
title = request.form['title']
phone = request.form['phone']
email = request.form['email']
newContact = contact(f_name = f_name, l_name = l_name, phone = phone, email = email, title =
title)
db.session.add(newContact)
db.session.commit()
return redirect(url_for('carrierProfile'))
return render_template('addContact.html')
My question is this: How do I insert a carrier_org FK into contact to be able to track which organization employees belong to? I was thinking something along the lines of
carrier_id = ("SELECT carrier_org.id FROM carrier_org, users WHERE carrier_org.email = users.current_user.id")
Then my newContact will be
newContact = contact(carrier_id = carrier_id, f_name = f_name, l_name = l_name, phone = phone, email = email, title =
title)
But I am not sure how to tie this all together.

Flask Translate Choice in Form To ForeignKey Pointing to Another Table

This part of the app works, buts it's ugly and not sustainable. Need a more evolved solution.
PROBLEM I AM TRYING TO SOLVE:
This part of the application enables users to access a form to enter purchases they've made and store them in a Postgres DB. I am using Flask SQLAlchemy ORM.
Within my purchase table exists a field store_id, that has a ForeignKey relationship to my store table. I don't want my user to select a store ID # in the form, so I am using a SelectField to enable them to choose the store name instead. However, I can't seem to find a sustainable way to translate the store name back to its associated ID. Right now I am using the ugly IF statements seen below.
What is a better way to map/translate store name to ID which is already housed in the "store" table in my DB?
MY MODEL:
class Purchase(db.Model):
id = db.Column(db.Integer, primary_key=True)
item = db.Column(db.String(80))
quantity = db.Column(db.Integer)
unit_cost = db.Column(db.Integer)
total_cost= db.Column(db.Integer)
date = db.Column(db.DateTime)
store_id = db.Column(db.Integer, db.ForeignKey('store.id'))
MY FORM:
class CreatePurchase(FlaskForm):
item = StringField('Item', validators=[DataRequired()])
quantity = IntegerField('Quantity', validators=[DataRequired()])
unit_cost = IntegerField('Unit Cost', validators=[DataRequired()])
total_cost = IntegerField('Total Cost', validators=[DataRequired()])
store_id = SelectField("Store Selector", choices=[('0','Select Store'),('1','Furgesons'), ('2','Ocean State'), ('3','Chewy'), ('4','Amazon'), ('5', 'Rumford')])
date = DateField('Purchase Date', validators=[DataRequired()])
submit = SubmitField('Enter')
MY ROUTE:
#main.route('/enter_purchases', methods=['GET', 'POST'])
def enter_purchase():
form = CreatePurchase()
x = str(form.store_id) # this is a store name from our form
p = 0
if "Ocean State" in x:
p = 2
elif "Amazon" in x:
p = 4
elif "Furgesons" in x:
p = 1
elif "Chewy" in x:
p = 3
elif "Rumford" in x:
p = 5
if form.validate_on_submit():
purchase = Purchase(item=form.item.data, quantity=form.quantity.data, unit_cost=form.unit_cost.data,
total_cost=form.total_cost.data, date=form.date.data,store_id=p)
db.session.add(purchase)
db.session.commit()
flash('New purchase added successfully')
return redirect(url_for('main.success'))
return render_template('enter_purchases.html', form=form)
You have a store table, with a numeric id (as the PK) and a name attribute:
class Store(db.Model):
store_id = ..
store_name = ..
You populate your form with all of the unique values from the store_name. This needs to be a dynamically generated form, so instead of using a form that is statically created do something like:
def CreatePurchase()
class TempForm(FlaskForm):
item = StringField('Item', validators=[DataRequired()])
quantity = IntegerField('Quantity', validators=[DataRequired()])
unit_cost = IntegerField('Unit Cost', validators=[DataRequired()])
total_cost = IntegerField('Total Cost', validators=[DataRequired()])
date = DateField('Purchase Date', validators=[DataRequired()])
submit = SubmitField('Enter')
choices = ## some SQLalchemy code to get all unique store_names from the table
TempForm.store_id = SelectField("Store Selector", choices=choices)
return TempForm()
Then form.store_id will provide the right id, but display the string name of the store in the form.
The key in this setup is making sure you use the right SQLalchemy code to populate the SelectField dynamically. Basically for each store in the table you can just iterate through something like this:
choices = list()
for store in Store.query.all(): # <- assumes store_id and store_names are unique
choices.append((store.store_id, store.store_name))

sqlalchemy, violation foreign key constraint

I am new to sqlalchemy. I want to create a class which has two foreign key for different tables. Why I get next error?
sqlalchemy.exc.IntegrityError: (IntegrityError) insert or update on table "event" violates foreign key constraint "event_user_fkey"
DETAIL: Key (user)=(U) is not present in table "user".
'INSERT INTO event (id, "user", item) VALUES (%(id)s, %(user)s, %(item)s)' {'item': 'I', 'user': 'U', 'id': 'E'}
My code is next:
class User(Base):
__tablename__ = 'user'
id = Column(String, primary_key=True)
def __init__(self, id):
self.id = id
class Item(Base):
__tablename__ = 'item'
id = Column(String, primary_key=True)
def __init__(self, id):
self.id = id
class Event(Base):
__tablename__ = 'event'
id = Column(String, primary_key=True)
user = Column(String, ForeignKey('user.id'))
item = Column(String, ForeignKey('item.id'))
def __init__(self, id, user_id, item_id):
self.id = id
self.user = user_id
self.item = item_id
I use postgresql as a back end.
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
usr = User('U')
it = Item('I')
event = Event('E', usr.id, it.id)
session.add(usr)
session.add(it)
session.add(event)
The error seems pretty clear:
Key (user)=(U) is not present in table "user".
So it's trying to insert the Event row before the User has been committed to the database, which breaks the ForeignKey constraint, causing this error. Try committing the User and Item to the database before committing the Event, which depends on them and the problem should evaporate.