I am trying to create a relationship between two tables; event and location.
The thing I am trying to accomplish is to create a one to many relationship. A event can be given at a location, so that location must be able to hold various Events. I have been fiddling with foreign keys for hours and I cannot figure out how to get it working.
My location.py looks like this:
from datetime import datetime
from sqlalchemy import Column, Integer, ForeignKey, Float, Date, String
from sqlalchemy.orm import relationship
from model import Base
class Location(Base):
__tablename__ = 'location'
id = Column(Integer, primary_key=True, nullable=False)
title = Column(String, unique=True)
description = Column(String, nullable=False)
founder_id = Column(Integer, ForeignKey('users.id'))
latitude = Column(Float)
longtitude = Column(Float)
creation_date = Column(Date, default=datetime.now)
# Relationships
creator = relationship('User', foreign_keys=founder_id)
events = relationship('Event', back_populates='location')
and the event.py looks like this:
from datetime import datetime
from sqlalchemy import Column, Integer, Date, Float, String, ForeignKey
from sqlalchemy.orm import relationship
from model import Base
from model.event_contestant import EventContestant
from model.location import Location
class Event(Base):
__tablename__ = 'event'
id = Column(Integer, primary_key=True)
title = Column(String, nullable=False, unique=True)
date = Column(Date, nullable=False)
location_id = Column(Integer),
longtitude = Column(Float, nullable=False)
latitude = Column(Float, nullable=False)
description = Column(String, nullable=False)
difficulty = Column(Integer, nullable=False, default=3)
creation_date = Column(Date, nullable=False, default=datetime.now)
event_creator = Column(Integer, ForeignKey('users.id'), nullable=False)
event_type = Column(String, nullable=False, default='Freestyle')
# Relationships
creator = relationship("User")
contestants = relationship(EventContestant, back_populates="event")
location = relationship('Location', foreign_key=location_id)
The stactrace I get from sqlalchemy looks like the following:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper|Location|location'. Original exception was: Could not determine join condition between parent/child tables on relationship Location.events - 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'm truly puzzled and can't find out why this one to many should just work.
Event needs a ForeignKey pointing to Location in order to construct the relationship. Specifically, you want to change the definition of Event.location_id to add ForeignKey('location.id') (and get rid of that trailing comma; Event.location_id is currently a tuple).
Related
I have a table of games and I want to save the moves made in each game within this table. The only way I can think of doing this(without using pickles) is to have another table for each game to save the moves set. My problem is that I'm not sure how to do that within Sqlalchemy and I could not find an answer elsewhere. Am I forced to use pickle types or is there a better way I'm not seeing?
Using Python 3.8.5 and the latest version of Sqlalchemy.
import datetime
from sqlalchemy import Column, DateTime, Integer, String, Date, ForeignKey, Float, Boolean, DateTime, PickleType
from sqlalchemy.ext.declarative import declarative_base
from flask_login import UserMixin
Base = declarative_base()
class Move(Base):
__tablename__ = 'moves'
id = Column(Integer, primary_key = True)
move = Column(String)
start_time = Column(Integer)
end_time = Column(Integer)
white = Column(Boolean)
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key = True)
white_id = Column(Integer)
black_id = Column(Integer)
board = Column(PickleType)
move_table_name = Column(String)
class User(Base , UserMixin):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(15), unique=True)
email = Column(String(50), unique=True)
password = Column(String(80))
This(^^^) is my database mode.
This is a question more about the basic database design. You could think how to design this kind of requirements with just database without sqlalchemy? Relationships between tables, here Game->Move, is a one-to-many relationship which should be declared in the database table also. See https://database.guide/the-3-types-of-relationships-in-database-design.
With SqlAlchemy relationships can be designed as follows:
class Move(Base):
__tablename__ = 'moves'
id = Column(Integer, primary_key = True)
game_id = Column(Integer, ForeignKey('games.id'))
move = Column(String)
start_time = Column(Integer)
end_time = Column(Integer)
white = Column(Boolean)
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key = True)
white_id = Column(Integer, ForeignKey('users.id'))
black_id = Column(Integer, ForeignKey('users.id'))
board = Column(PickleType)
white = relationship("User", foreign_keys=[white_id])
black = relationship("User", foreign_keys=[black_id])
See more info: https://docs.sqlalchemy.org/en/14/orm/basic_relationships.html
I would like to create an association between a Dataset object and all Category objects through the Annotation table.
A Dataset contains a collection of Annotations. Each Annotation has a single Category. I want Dataset.categories to contain the unique set of Categories made up of all the Categories of all the Annotations in that Dataset instance. I have tried doing this with a double association table (dataset_categories), but it is not working. What is the right way to do this? Here is my code so far:
Base = declarative_base()
dataset_categories = Table('dataset_categories', Base.metadata,
Column('dataset_id', Integer, ForeignKey('datasets.id')),
Column('annotation_id', Integer, ForeignKey('annotations.id')),
Column('category_id', Integer, ForeignKey('categories.id')))
class Dataset(Base):
__tablename__ = 'datasets'
id = Column(Integer, primary_key=True)
annotations = relationship("Annotation")
categories = relationship("Category", secondary=dataset_categories)
class Annotation(Base):
__tablename__ = 'annotations'
id = Column(Integer, primary_key=True)
category_id = Column(Integer, ForeignKey('categories.id'), nullable=False)
category = relationship("Category")
dataset_id = Column(Integer, ForeignKey('datasets.id'))
class Category(Base):
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
dataset = relationship("Dataset", secondary=dataset_categories)
dataset_id = Column(Integer, ForeignKey('datasets.id'),
back_populates='categories')
Without the requirement that the association contain only the unique categories this would be as simple as using an association_proxy. One option is to define the collection class to use as set when defining the relationship:
class Dataset(Base):
__tablename__ = 'datasets'
id = Column(Integer, primary_key=True)
annotations = relationship("Annotation")
categories = relationship("Category", secondary="annotations", collection_class=set)
On the other hand the secondary table of a relationship does not have to be a base table, and so a simple select from annotations can be used:
class Dataset(Base):
__tablename__ = 'datasets'
id = Column(Integer, primary_key=True)
annotations = relationship("Annotation")
categories = relationship("Category",
secondary="""select([annotations.c.dataset_id,
annotations.c.category_id]).\\
distinct().\\
alias()""",
viewonly=True)
The example given in sqlalchemy documentation is,
from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(String)
billing_address_id = Column(Integer, ForeignKey("address.id"))
shipping_address_id = Column(Integer, ForeignKey("address.id"))
billing_address = relationship("Address", foreign_keys=[billing_address_id])
shipping_address = relationship("Address", foreign_keys=[shipping_address_id])
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
street = Column(String)
city = Column(String)
state = Column(String)
zip = Column(String)
I am trying a similar example (cannot place so much code here) it does not work if I do something similar to this:
from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
name = Column(String)
billing_address_id = Column(Integer, ForeignKey("address.id"))
shipping_address_id = Column(Integer, ForeignKey("address.id"))
billing_address = relationship("Address", foreign_keys=[billing_address_id], back_populates('bill_addr'))
shipping_address = relationship("Address", foreign_keys=[shipping_address_id], back_populates('ship_addr'))
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
street = Column(String)
city = Column(String)
state = Column(String)
zip = Column(String)
bill_addr = relationship("Customer", back_populates('billing_address'))
ship_addr = relationship("Customer", back_populates('shipping_address'))
I have two doubts:
Q1) Is the above relationship bidirectional?
Q2) How to establish a bidirectional relationship between tables with multiple join paths?
edit:
In my case I am getting the following error:
sqlalchemy.exc.AmbiguousForeignKeysError
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables
on relationship User.expenses - there are multiple foreign key paths linking the tables.
Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a
foreign key reference to the parent table.
It is working if I used 'backref' instead of 'back_populates'. I placed the 'backref' in the relationships on the side where both the foreign keys are present and removed the relationships on the other table.
I'm trying to create 2 tables using SQLAlchemy models that are identical in structure but distinct in purpose, the Whitelist and Blacklist which also reference a Magazine (not shown). They both fkey to an instance of a Campaign (say a political campaign) which in turn has an fkey to a Politician model (also not shown).
I'm picturing the Whitelist/Blacklist to be many:1 to the Campaign since there could be multiple white/blacklists for, but when running the migration I get sqlalchemy.exc.ProgrammingError: (ProgrammingError) there is no unique constraint matching given keys for referenced table "campaigns". There also needs to be a M:1 from Campaign to Politician.
Can someone please explain why this is causing a unique constraint error since the whitelist and blacklist are on separate tables? Also, how would I be able to make this relational schema work?
class Campaign(Base):
__tablename__ = "campaigns"
id = Column(Integer, primary_key=True, nullable=False)
politician_id = Column(Integer, ForeignKey('politician.id'), nullable=False)
description = Column(Text, nullable=True)
class Whitelist(Base):
__tablename__ = "whitelist"
id = Column(Integer, primary_key=True, nullable=False)
campaign_id = Column(Integer, ForeignKey('campaign.id'), nullable=False)
magazine_id = Column(Integer, ForeignKey('magazine.id'), nullable=False)
class Blacklist(Base):
__tablename__ = "blacklist"
id = Column(Integer, primary_key=True, nullable=False)
campaign_id = Column(Integer, ForeignKey('campaign.id'), nullable=False)
magazine_id = Column(Integer, ForeignKey('magazine.id'), nullable=False)
It would seem that the relationship is indeed illegal because of the M:1 from the Whitelist/Blacklist to Campaign, while Campaign itself is a M:1 to Politician. I instead scrapped that approach and the models now look like:
class Campaign(Base):
__tablename__ = "campaigns"
id = Column(Integer, primary_key=True, nullable=False)
politician_id = Column(Integer, ForeignKey('politician.id'), nullable=False)
description = Column(Text, nullable=True)
class Listing(Base):
__tablename__ = "listings"
id = Column(Integer, primary_key=True, nullable=False)
campaign_id = Column(Integer, ForeignKey('campaign.id'), nullable=False)
magazine_id = Column(Integer, ForeignKey('magazine.id'), nullable=False)
listing_type = Column(String, nullable=False)
The original approach was intended to fit a little nicer into Flask-Admin but this way is more performant since it will reduce the additional query and join necessary in the previous incarnation. For those that may be thinking of this type of approach to use with Flask-Admin, you could use the inline_model() to make the UI a little cleaner and easier to manage.
I'm struggling with creating a database model for my Flask application.
Let's consider I have few product types and due to nature of my application I want to have separate tables for each product, while keeping generic properties in a common table:
Example:
products table
id type name price
1 'motorcycle' 'Harley' 10000.00
2 'book' 'Bible' 9.99
motorcycles table
id manufacturer model max_speed
1 'Harley-Davidson' 'Night Rod Special' 150
books table
id author pages
2 'Some random dude' 666
Things to consider:
all tables have one-to-one relationship
having motorcycle_id, book_id, etc_id in products table is not an option
having product_id in product tables is acceptable
two-way relationship
How can I declare such a relationship?
You are looking for joined table inheritance. The base class and each subclass each create their own table, each subclass has a foreign key primary key pointing to the base table. SQLAlchemy will automatically handle the joining whether you query the base or sub class.
Here is a working example for some products:
from decimal import Decimal
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Numeric
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///:memory:', echo=True)
session = sessionmaker(bind=engine)()
Base = declarative_base(bind=engine)
class Product(Base):
__tablename__ = 'product'
id = Column(Integer, primary_key=True)
type = Column(String, nullable=False)
name = Column(String, nullable=False, default='')
price = Column(Numeric(7, 2), nullable=False, default=Decimal(0.0))
__mapper_args__ = {
'polymorphic_on': type, # subclasses will each have a unique type
}
class Motorcycle(Product):
__tablename__ = 'motorcycle'
# id is still primary key, but also foreign key to base class
id = Column(Integer, ForeignKey(Product.id), primary_key=True)
manufacturer = Column(String, nullable=False, default='')
model = Column(String, nullable=False, default='')
max_speed = Column(Integer, nullable=False, default=0)
__mapper_args__ = {
'polymorphic_identity': 'motorcycle', # unique type for subclass
}
class Book(Product):
__tablename__ = 'book'
id = Column(Integer, ForeignKey(Product.id), primary_key=True)
author = Column(String, nullable=False, default='')
pages = Column(Integer, nullable=False, default=0)
__mapper_args__ = {
'polymorphic_identity': 'book',
}
Base.metadata.create_all()
# insert some products
session.add(Book())
session.add(Motorcycle())
session.commit()
print(session.query(Product).count()) # 2 products
print(session.query(Book).count()) # 1 book