can Foreign Key connect to multiple tables? - python

i have sqlalchemy models like this :
class Person(db.Model):
..
# the photos of the person
photos = db.relationship('Photos', lazy='dynamic')
class Photos(db.Model):
..
#link to the photo
link = db.Column(db.TEXT)
of course this raise error that i must include Foreign Key in Photos table connect to Person.
the problem is i want Photos table to host photos of other objects like Cars and stuff like that .
how to make Photos table connect to Options of tables.
Note that Photos must connect to only one table if Person no other table will connect to it .
i hope this make sense

The strictly relational database answer to that question is here, already on StackOverflow. Strictly speaking, it is not considered "correct" design to have a single foreign key that could point to multiple tables and it typically isn't directly supported by the database itself.
Having said that, it's still a somewhat common pattern and for your question specifically, you might look into a library called SQLAlchemy-Utils, which includes a feature called Generic Relationships. Consider this example, from the docs:
from sqlalchemy_utils import generic_relationship
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True)
class Customer(Base):
__tablename__ = 'customer'
id = sa.Column(sa.Integer, primary_key=True)
class Event(Base):
__tablename__ = 'event'
id = sa.Column(sa.Integer, primary_key=True)
# This is used to discriminate between the linked tables.
object_type = sa.Column(sa.Unicode(255))
# This is used to point to the primary key of the linked row.
object_id = sa.Column(sa.Integer)
object = generic_relationship(object_type, object_id)
# Some general usage to attach an event to a user.
user = User()
customer = Customer()
session.add_all([user, customer])
session.commit()
ev = Event()
ev.object = user
session.add(ev)
session.commit()
# Find the event we just made.
session.query(Event).filter_by(object=user).first()
# Find any events that are bound to users.
session.query(Event).filter(Event.object.is_type(User)).all()
The event.object attribute points to either a User or Customer object (or other object). The library "finds" the other object by combining the object_type column, to tell it what other table to query, and the object_id column.
There are some downsides to this approach, especially in terms of querying efficiency and indexing, but also in whether the design "feels right." Read up on that answer I linked to above for a discussion of that.

Related

SQLAlchemy one-to-many relationship - how to properly get the 'many' collection

I'm not sure I properly understand how to get the collection part of the one-to-many relationship.
class ProjectReport(db.Model):
__tablename__ = "project_reports"
id = db.Column(UUID, primary_key=True, default=uuid.uuid4)
project_id = db.Column(UUID, db.ForeignKey("projects.id"), nullable=False)
entries = db.relationship("ProducerEntry", backref="project_report", lazy="dynamic")
class ProducerEntry(Entry):
__tablename__ = "producer_entries"
__mapper_args__ = {"polymorphic_identity": "Entry"}
id = db.Column(UUID, db.ForeignKey("entries.id"), primary_key=True)
project_id = db.Column(UUID, db.ForeignKey("projects.id"), nullable=False)
project_report_id = db.Column(UUID, db.ForeignKey("project_reports.id"), nullable=True)
My problem is that I can't just access the entries field.
for entry in self.entries:
do_something(entry)
This returns NotImplementedError
I managed to get the data via hybrid property but that seems a bit of an overkill since already have the relationship, also it'd get a bit complex for further logic later on.
#hybrid_property
def entries(self):
return ProducerEntry.query.filter_by(project_report_id=self.id)
Ab additional information is that the ProjectReport is basically the common columns of the Entry and Project models, and the project_report_id is nullable, because the entries and projects are generated first and then I can generate the project reports from them. This is how I create the reports:
...
project_report = ProjectReport(date_order=entry.date_order, project_id=entry.project.id)
project_report.entries.append(entry)
...
As far as I know I don't have to add the project_report_id to the producer entry after this.
What am I missing here?
Well yeah, that relationship field returns a query, so I simply should have called:
self.entries.all()
Or anything else which is handling a query.

SQLalchemy built relationship between classes

I am running into a conceptual problem I do not know how to approach, which might be due my lack of knowledge with SQLalchemy. I have two classes: People and Person and I want them each to have a column to share their respective id's with each other using the relationship function.
Now, I have an endpoint in views.py which instantiates those two classes and establishes a Child / Parent relationship. Looking at the database results however, only People, the parent class has the id stored in its respective table, while the Person table in column people is None.
I know the id in person is only generated after the commit() statement and thus None for Person, and was wondering if there is a way to solve this elegantly, or do I need to first query the current people instance, retreive its id, set the id in the person table and then commit() again?
I hope my question makes sense,thank you.
'''
model.py
'''
class People(Model):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
person = relationship('Person', back_populates='people')
person_id = Column(Integer, ForeignKey('people.id'))
class Person(Model):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
people = relationship('People', uselist=False, back_populates='person')
'''
views.py
'''
#main.route('/', methods=['GET', 'POST'])
def index():
people = People()
person = Person(people_id = ?)
people.person = person
session.add(person)
session.add(people)
session.commit()
I regret that I have not yet understood your question. However, since your code contains some errors, I will first write you my corrected variant.
class People(Model):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
person = relationship('Person', back_populates='people')
person_id = Column(Integer, ForeignKey('person.id'))
class Person(Model):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
people = relationship('People', back_populates='person')
def index():
person = Person()
people = People()
people.person = person
session.add(person)
session.add(people)
session.commit()
The question of gittert seems justified to me. It makes no sense to save the ForeignKey in both tables on the referenced identifiers of the other model.
What do you want to achieve?
If you're looking for an actual column in your database for your 'relationships', you won't find them. Your .people and .person are virtual relationships created in Python without any interaction with the SQL database.

Flask-admin: How to change foreign key and update related records

I have two tables with a foreign key relationship and I am using flask-admin as admin GUI.
In some cases I need to modify the foreign-key. In this case all existing Records of the related table should be updated (with new foreign key). How would you implement this functionality?
I tried the on_model_change function of flask-admin. Although I run into the problem, that I only get the updated key from the function(?) -> (I need the old key to find the existing assessment-records and update them). Additionally I'm not able to commit the new key unless the Assessment records have been updated (foreign key constraint would not be met).
Can I achieve this functionality within the flask-admin GUI?
class Assessment(db.Model):
__tablename__ = "assesment_table"
id = db.Column(db.Integer, primary_key=True)
psr_id = db.Column(db.String(40), db.ForeignKey("psr_object_table.psr_id"))
class Unit(db.Model):
__tablename__ = "units_table"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))
psr_id = db.Column(db.String(40), unique=True)
#needed for Flask-Admin view
class UnitView(ModelView):
def on_model_change(self, form, model, is_created):
#get old_key
#query and update db with new foreign key
#commit everything
Any help is highly appreciated. Thank you
Use database side cascades:
class Assessment(db.Model):
...
psr_id = db.Column(db.String(40), db.ForeignKey("psr_object_table.psr_id",
onupdate="CASCADE"))

NoForeignKey error on relationship even though SQLAlchemy model specifies foreign key

I am having some difficulty setting up a one to one relationship between two models in my flask application. I have two models Employeeand `Photo'. An employee has only one photo associated with it and vice-versa.
This is the code that I have in my models.py file:
class Employee(db.Model):
__tablename__ = 'employees'
id = db.Column(db.Integer, primary_key=True)
photo = db.relationship("Photo", uselist=False, back_populates='employees')
class Photo(db.Model):
__tablename__ = 'photos'
id = db.Column(db.Integer, primary_key=True)
employee_id = db.Column(db.Integer, db.ForeignKey('employees.id'))
employee = db.relationship('Photo', back_populates='photo')
I've followed the instruction on the SQL Alchemy documentation found hereSQL Alchemy simple relationships. The error that I keep encountering is shown below:
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Photo.employee
- there are no foreign keys linking these tables.
Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
I clearly specify the foreign key right here employee_id = db.Column(db.Integer, db.ForeignKey('employees.id')) . I'm not sure what I'm doing wrong. Additionally, I was reading the documentation and it doesn't help that uselist, backref, and back_populates are so similar.
Can someone assist me with this? Help would be greatly appreciated.
One to One relationship stack overflow question
backref automatically adds the reverse relationship to the related model. You can pass a db.backref object to it to specify options to the relationship. back_populates tells SQLAlchemy to populate an existing reverse relationship, rather than creating it. uselist tells SQLAlchemy whether the relationship is a list or not, for cases where it can't determine that automatically.
In your example, you need one relationship, with one backref that is a single item.
You have two typos in your code. First, back_populates='employees' should refer to 'employee', which is what you called the property on the related model. Second, employee = relationship('Photo' is pointing at the wrong model, it should relate to Employee.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
db.engine.echo = True
class Photo(db.Model):
id = db.Column(db.Integer, primary_key=True)
class Employee(db.Model):
id = db.Column(db.Integer, primary_key=True)
photo_id = db.Column(db.ForeignKey(Photo.id))
photo = db.relationship(Photo, backref=db.backref('employee', uselist=False))
db.create_all()
db.session.add(Employee(photo=Photo()))
db.session.commit()
print(Employee.query.get(1).photo)

Query items from granchildren objects

I have a Flask python application that has a set of related tables that are chained together through foreign keys. I would like to be able to return an aggregate list of records from one table that are related to a distant table. However, I am struggling to understand how sqlalchemy does this through object relationships.
For example, there are three objects I'd like to join (challenge and badge) with two tables (talent_challenge and badge) to be able to query for all badges related to a specific challenge. In SQL, this would look something like:
SELECT b.id, b.name
FROM badge b
INNER JOIN talent_challenge tc ON tc.talent_id = b.talent_id
WHERE tc.challenge_id = 21
The 'talent' and 'challenge' tables are not needed in this case, since I only need the talent and challenge IDs (in 'talent_challenge') for the relationship. All of the interesting detail is in the badge table.
I am able to use sqlalchemy to access the related talent from a challenge using:
talents = db.relationship('TalentModel', secondary='talent_challenge')
And I can then reference talent.badges for each of those talents to get the relevant badges related to my initial challenge. However, there can be redundancy, and this list of badges isn't contained in a single object.
A stripped-down version of the three models are:
class TalentModel(db.Model):
__tablename__ = 'talent'
# Identity
id = db.Column(db.Integer, primary_key=True)
# Relationships
challenges = db.relationship('ChallengeModel', secondary='talent_challenge',)
# badges (derived as backref from BadgeModel)
class ChallengeModel(db.Model):
__tablename__ = 'challenge'
# Identity
id = db.Column(db.Integer, primary_key=True)
member_id = db.Column(db.Integer, db.ForeignKey('member.id'))
# Relationships
talents = db.relationship('TalentModel', secondary='talent_challenge', order_by='desc(TalentModel.created_at)')
class BadgeModel(db.Model):
__tablename__ = 'badge'
# Identity
id = db.Column(db.Integer, primary_key=True)
talent_id = db.Column(db.Integer, db.ForeignKey('talent.id'))
# Parents
talent = db.relationship('TalentModel', foreign_keys=[talent_id], backref="badges")
I also have a model for the associative table, 'talent_challenge':
class TalentChallengeModel(db.Model):
__tablename__ = 'talent_challenge'
# Identity
id = db.Column(db.Integer, primary_key=True)
talent_id = db.Column(db.Integer, db.ForeignKey('talent.id'))
challenge_id = db.Column(db.Integer, db.ForeignKey('challenge.id'))
# Parents
talent = db.relationship('TalentModel', uselist=False, foreign_keys=[talent_id])
challenge = db.relationship('ChallengeModel', uselist=False, foreign_keys=[challenge_id])
I would like to better understand sqlalchemy (or specifically, flask-sqlalchemy) to allow me to construct this list of badges from the challenge object. Is db.session.query of BadgeModel my only option?
UPDATED 1/23/2015:
My blocker on my project was solved by using the following:
#property
def badges(self):
from app.models.sift import BadgeModel
from app.models.relationships.talent import TalentChallengeModel
the_badges = BadgeModel.query\
.join(TalentChallengeModel, TalentChallengeModel.talent_id==BadgeModel.talent_id)\
.filter(TalentChallengeModel.challenge_id==self.id)\
.all()
return the_badges
Wrapping the query in a function got around the issues I was having with the name BadgeModel not being defined and not being able to be imported in the model otherwise. The #property decorator allows me to just reference this as challenge.badges later in the view.
However, I am still interested in understanding how to do this as a relationship. Some searching elsewhere led me to believe this would work:
badges = db.relationship('BadgeModel',
secondary="join(BadgeModel, TalentChallengeModel, BadgeModel.talent_id == TalentChallengeModel.talent_id)",
secondaryjoin="remote([id]) == foreign(TalentChallengeModel.challenge_id)",
primaryjoin="BadgeModel.talent_id == foreign(TalentChallengeModel.talent_id)",
viewonly=True,
)
Because of other unresolved issues in my application environment, I can't fully test this (e.g., adding this code breaks Flask-User in my site) but would like to know if this is correct syntax and if there is any disadvantage to this over the query-in-function solution.

Categories

Resources