Flask/SQLAlchemy: Create relations between multiple databases without foreign key constraints? - python-3.x

A StackOverflow question/answer (sqlalchemy: create relations but without foreign key constraint in db?) looked interesting, but what if you have two databases and join through a secondary table?
FKs can't map to external databases by design, making this a more realistic case if you can't load the remote table in your database.
Below is a setup where if we didn't set the __bind_key__ and the tables shared a database, it would work. However, we do want the multiple database setup. To keep things easier both databases have a schema called api where tables are stored.
Default Database
class Store(db.Model):
'''api.stores'''
schema = 'api'
__tablename__ = 'stores'
__table_args__ = { 'schema': schema }
id = db.Column(db.Integer, primary_key=True)
product = db.relationship('Product'
secondary=Inventory.__table__,
back_populates='stores'
)
Bind Database (second_db)
class Product(db.Model):
'''second_db.api.products'''
schema = 'api'
__bind_key__ = 'second_db'
__tablename__ = 'products'
__table_args__ = { 'schema': schema }
id = db.Column(db.Integer, primary_key=True)
stores = db.relationship('Store',
secondary=Inventory.__table__,
back_populates='products'
)
class Inventory(db.Model):
'''second_db.api.inventory'''
schema = 'api'
__bind_key__ = 'second_db'
__tablename__ = 'inventory'
__table_args__ = { 'schema': schema }
product_id = db.Column(db.Integer, db.ForeignKey(f'{schema}.products.id'), primary_key=True)
# below won't work with multiple DBs as a FK cannot reference external databases
store_id = db.Column(db.Integer, db.ForeignKey(f'{schema}.stores.id'), primary_key=True)
Per the accepted answer it seems primaryjoin may be used to build out the association and its answer doesn't use a secondary, so I'm not quite sure where something like this would live in the example above.
a = relationship('A',
foreign_keys=[a_id],
primaryjoin='A.a_id == C.a_id'
)
Another answer (https://stackoverflow.com/a/56683671/10408280) demonstrates how one might reference the bind database, however what is the key to the default app database/engine set by SQLALCHEMY_DATABASE_URI?
id = db.Column(db.Integer, primary_key=True)
people_id = db.Column(db.Integer, db.ForeignKey('db1.people.id'))
dogs_id = db.Column(db.Integer, db.ForeignKey('db2.dogs.id'))

Related

Why some fields are not created while making migrations?

I have this two models in models.py
class Bid(models.Model):
bid = models.IntegerField(default = 0)
user = models.ForeignKey(User, on_delete = models.CASCADE, related_name = "bid")
def __str__(self):
return f"Bid of {self.bid} from {self.user}"
class AuctionListings(models.Model):
name_of_item = models.CharField(max_length=32)
description = models.CharField(max_length=400)
owner = models.ForeignKey(User, on_delete = models.CASCADE, related_name="auctionlistings", default = None)**
bid = models.ForeignKey(Bid, on_delete = models.CASCADE, related_name = "auctionlistings", default = None)**
is_closed = models.BooleanField(default=False, blank=True, null=True)
url = models.CharField(max_length=800)
watchlist = models.ManyToManyField(User, blank=True, related_name="watch_listings")
category = models.CharField(max_length=50)
When i makemigrations:
operations = [
migrations.CreateModel(
name='AuctionListings',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name_of_item', models.CharField(max_length=32)),
('description', models.CharField(max_length=400)),
('is_closed', models.BooleanField(blank=True, default=False, null=True)),
('url', models.CharField(max_length=800)),
('category', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='Bid',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bid', models.IntegerField(default=0)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bid', to=settings.AUTH_USER_MODEL)),
],
),
My Question is: Why Django did not create the fields: "bid" and "user" specified in models.py.
The key to understand this behaviour is the method generate_created_models used when Django tries to figure out the operations needed when generating the migrations. In this method's docs we can read the following:
"""
Find all new models (both managed and unmanaged) and **make create
operations for them as well as separate operations to create any
foreign key or M2M relationships (these are optimized later, if
possible)**.
Defer any model options that refer to collections of fields that might
be deferred (e.g. unique_together, index_together).
"""
So for fields that refer to Foreign Keys or Many to Many relationships, Django will try to optimize the operations. The most common options are:
Include the field when creating the model with migrations.CreateModel (as you can see, you have an example in your code for the Bid.user field).
Create the model with migrations.CreateModel without the field. After the creation, Django adds each new field with migrations.AddField (this is what is happening to your AuctionListings.owner, AuctionListings.bid and AuctionListings.watchlist fields, which aren't included while calling migrations.CreateModel in your code).
All in all, an important thing to note is that fields are not created. Actually, models are created and this models contain fields, which can be specified during creation or added later.
With that into account, in your migrations file, just below the lines of code that you posted you should see some operations using migrations.AddField and account for the addition of the fields that are not included in migrations.CreateModel.

How to create an interface table in sqlAlchemy (relation many-to-many)

I'm using SqlAlchemy to interact with an existing Mariadb database. I need to access data organized in a many-to-many relationship. According to the source site documentation (sqlAlchemy), I created a multi-to-multiple relationship, but after entering the flask db migrate command, I get an error.
It should be noted that the tables of posts and categories have been created.
Documents for creating the interface table:
from sqlalchemy import Column, String, Text, Integer, Table, ForeignKey
from app import db
posts_categories = Table('posts_categories', db.metadata,
Column('post_id', Integer, ForeignKey('posts.id', ondelete='cascade')),
Column('category_id', Integer, ForeignKey('categories.id', ondelete='cascade'))
)
class Category(db.Model):
__tablename = 'categories'
id = Column(Integer, primary_key=True)
name = Column(String(128), nullable=False, unique=True)
description = Column(String(256), nullable=True, unique=False)
slug = Column(String(128), nullable=False, unique=True)
posts = db.relationship('Post', secondary=posts_categories, back_populates='categories')
class Post(db.Model):
__tablename = 'posts'
id = Column(Integer, primary_key=True)
title = Column(String(128), nullable=False, unique=True)
summary = Column(String(256), nullable=True, unique=False)
content = Column(Text, nullable=False, unique=False)
slug = Column(String(128), nullable=False, unique=True)
categories = db.relationship('Category', secondary=posts_categories, back_populates='posts')

sqlalchemy.exc.ArgumentError while running migrations

I am facing an Argument error while trying to run migrations, but I have failed to figure out where the problem is, What am trying to implement is a relationship between two models. Below is my model class :
class TourPackages(db.Model):
__tablename__ = 'tour_package'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
description = db.Column(db.TEXT)
price = db.Column(db.Float)
destination = db.relationship('Destinations', backref='tourpackages', lazy=True)
capacity = db.Column(db.Integer)
#property
def serialize(self):
return {
'name': self.name,
'description': self.destination,
'price': self.price,
'destination': self.destination,
'capacity': self.capacity
}
class Destinations(db.Model):
__tablename__ = 'destination'
id = db.Column(db.Integer)
location = db.Column(db.String(50))
danger_type = db.Column(db.String(50))
When I run migrations with the command below:
flask db migrate -m "Initial migration."
I get this error :
sqlalchemy.exc.ArgumentError: Mapper mapped class Destinations->destination could not assemble any primary key columns for mapped table 'destination'
What am I doing wrong here
In your Destinations model, you need to change the id column to be a primary key
id = db.Column(db.Integer, primary_key=True)
and add a foreign key column for the relationship, again in Destinations:
tourpackages_id = db.Column(db.Integer, db.ForeignKey('tour_package.id'))
See the documentation.

Sqlalchemy get data from relationship

I have this models
class Post(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True)
content = Column(Text)
author = Column(Integer, ForeignKey('users.id'))
to_topic = Column(Integer, ForeignKey('topics.id'))
def __init__(self, content: str, author: int, to_topic: int) -> None:
self.content = content
self.author = author
self.to_topic = to_topic
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String(30))
email = Column(String(40))
password = Column(String(255))
registred_at = Column(DateTime, default=datetime.datetime.now)
last_logged_in = Column(DateTime)
last_login_attempt = Column(DateTime)
avatar = Column(Integer, ForeignKey('files.id'))
role = Column(String(20))
email_confirm = Column(Boolean, default=False)
children_forum = relationship('Forum')
children_topic = relationship('Topic')
children_post = relationship('Post')
And I am trying to get query that will contain Post.content, Post.author.username but how do i do this in sessions?
I tried
posts = db_session.query(Post, User).filter(
Post.to_topic == topic_id).with_entities(Post.content, Post.author.username)
but that doesnt work because author is just integer (id) so I expect that I need somehow get the author object and post object in one query, but I dont know how. In sql that would be easy just 2 queries but here I dont know how it´s done.
query_results = db_session.query(Post, User).\
join(User, Post.author == User.id).\
filter(
Post.to_topic == topic_id
).all()
I've never used with_entities, but I know this would give you a list of tuples where query_results[0] would be your Post instance, and query_results[1] would be your User instance.
EDIT: I believe you don't have to include the Post.author == User.id bit, but it's more legible if you're explicit with your joins.

Flask-SQLalchemy one-to-one relationship: associate child to already existing parent?

first time I use sqlalchemy and flask and I miss something
I'm trying to do a one-to-one relationship between two tables with Flask-SQLalchemy.
This is my first table (the parent)
class User(db.Model):
__tablename__ = 'users'
user_id = Column(Integer, primary_key=True, autoincrement=True)
user_email = Column(String(150), nullable=False)
user_password = Column(String(60), nullable=False)
child_employee = relationship('Employee', uselist=False, backref='users')
def __init__(self, user_email, user_password):
self.user_email = user_email
self.user_password = user_password
And the child
class Employee(User):
__tablename__ = 'employees'
employee_id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey('users.user_id'), nullable=False)
employee_lastname = Column(String(30), nullable=False)
employee_firstname = Column(String(30), nullable=False)
employee_comment = Column(String)
def __init__(self, user_email, user_password, employee_lastname, employee_firstname, employee_comment=None):
super().__init__(user_email, user_password)
self.employee_lastname = employee_lastname
self.employee_firstname = employee_firstname
self.employee_comment = employee_comment
Know when I create an user, I have a user in my table "users" and nothing in my table "employees" : That make sense, perfect
Then if I create an employee, I have a new entry in my table "employees" and a new entry in my table "users". They are linked to each other : that make also sense, perfect
Now the question :
I created an user so there is no employee linked to it.
Now I want to create an employee and I want to link it to the already existing user, how can I do it ?
I tried
# creation of the new employee
new_employee = Employee(exisiting_user.user_email,
exisiting_user.user_password, "email", "lastname", "firstname")
#trying to associate the new employee to the already existing user
exisiting_user.child_employee = new_employee
# save the employee to the database
db.session.add(new_employee)
db.session.commit()
but I get this error
sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) duplicate key value violates unique constraint "users_pkey"
DETAIL: Key (user_id)=(2) already exists.
[SQL: 'INSERT INTO users (user_id, user_email, user_password) VALUES (%(user_id)s, %(user_email)s, %(user_password)s)'] [parameters: {'user_id': 2, 'user_email': 'email', 'user_password': 'password'}]
Thanks
----------------First try to resolve it-------------------------
So as if I understand correctly, when I create an instance of "Employee" I also have an instance of the super class "User". Then whend I db.session.add(new_employee), SQLalchemy create an INSERT for the employee and then for the user, but the user already exist so I have the error.
I change my Employee table :
class Employee(User):
__tablename__ = 'employees'
employee_id = Column(Integer, ForeignKey(User.user_id), primary_key=True)
employee_lastname = Column(String(30), nullable=False)
employee_firstname = Column(String(30), nullable=False)
employee_comment = Column(String)
parent = relationship("User", back_populates="child_employee")
def __init__(self, employee_id, employee_lastname, employee_firstname, employee_comment=None):
self.employee_id = employee_id
self.employee_lastname = employee_lastname
self.employee_firstname = employee_firstname
self.employee_comment = employee_comment
and to add only an employee i'm doing
emp = Employee(1, "lastname", "firstname", "comment")
db.session.execute("INSERT INTO employees VALUES("emp.employee_id",'"emp.employee_lastname+"', '"+emp.employee_firstname+"', '"+emp.employee_comment+"')")
Because of the Id who is also a foreign key, the employee and the user are automatically linked
It works but I would prefer to use something more simple like db.session.add(emp), I still miss something

Resources