sqlalchemy constraint in models inheritance - python

I have two simple models:
class Message(Backend.instance().get_base()):
__tablename__ = 'messages'
id = Column(Integer, primary_key=True, autoincrement=True)
sender_id = Column(Integer, ForeignKey('users.id'))
content = Column(String, nullable=False)
class ChatMessage(Message):
__tablename__ = 'chat_messages'
id = Column(Integer, ForeignKey('messages.id'), primary_key=True)
receiver_id = Column(Integer, ForeignKey('users.id'))
How to define constraint sender_id!=receiver_id?

This doesn't seem to work with joined table inheritance, I've tried and it complains that the column sender_id from Message doesn't exist when creating the constraint in ChatMessage.
This complaint makes sense, since sender_id wouldn't be in the same table as receiver_id when the tables are created, so the foreign key relationship would need to be followed to check the constraint.
One option is to make ChatMessage a single table.
Use CheckConstraint, placed in table args.
class ChatMessage(Base):
__tablename__ = 'chat_messages'
id = sa.Column(sa.Integer, primary_key=True)
sender_id = sa.Column(sa.Integer, sa.ForeignKey(User.id))
receiver_id = sa.Column(sa.Integer, sa.ForeignKey(User.id))
content = sa.Column(sa.String, nullable=False)
__table_args__ = (
sa.CheckConstraint(receiver_id != sender_id),
)

Related

Deleting many tables when one specific table is deleted in FLASK SQLAlchemy

I have an SQLAlchemy VolunteerClient Model which is a join table:
class VolunteerClient(Base):
__tablename__ = 'volunteer_client'
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
volunteer_id = Column(Integer, ForeignKey('provider_user.user_id', onupdate='CASCADE', ondelete='RESTRICT'), unique=True)
client_id = Column(Integer, ForeignKey('user.id', onupdate='CASCADE', ondelete='RESTRICT'), unique=True)
and a VolunteerReport model:
class VolunteerReport(Base):
__tablename__ = 'volunteer_report'
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
volunteer_id = Column(Integer, ForeignKey('volunteer_client.volunteer_id', cascade="all, delete"))
client_id = Column(Integer, ForeignKey('volunteer_client.client_id', cascade="all, delete"))
report = Column(String(255), nullable=False)
report_category = Column(String(255), nullable=False)
If I was to delete a VolunteerClient table, which essentially unassignes a volunteer from a client but does not actually delete the users which they represent. with a specific volunteer_id and client_id set,
Example: I delete VolunteerClient table where: volunteer_id = 1, & client_id = 1,
I want any and all VolunteerReports where: volunteer_id = 1, & client_id = 1 to be deleted as well. Have I set this up correctly with the FK references to volunteer_client and the cascade='all, delete'?
Any advice would be awesome.
To automatically delete child rows when a parent is deleted, you need to set ondelete='CASCADE' on the foreign key defined in the child table. In this case, the foreign key is a composite key as it consists of volunteer_id and client_id. This means you need a unique constraint over the columns in the parent as well. This simplified version of your models show how it would work (I've removed the FK definitions from VolunteerClient the tables are defined in the question).
class VolunteerClient(Base):
__tablename__ = 'volunteer_client'
id = sa.Column(sa.Integer, primary_key=True)
volunteer_id = sa.Column(sa.Integer)
client_id = sa.Column(sa.Integer)
__table_args__ = (sa.UniqueConstraint(volunteer_id, client_id),)
class VolunteerReport(Base):
__tablename__ = 'volunteer_report'
id = sa.Column(sa.Integer, primary_key=True)
volunteer_id = sa.Column(sa.Integer)
client_id = sa.Column(sa.Integer)
__table_args__ = (
sa.ForeignKeyConstraint(
[volunteer_id, client_id],
[VolunteerClient.volunteer_id, VolunteerClient.client_id],
ondelete='CASCADE',
),
)
You can also configure delete cascades on SQLAlchemy relationships, which give you more control over what happens when a parent row is deleted. As you don't seem to be using relationships, and the database cascade does what you want I won't cover that in this answer.

SQLAlchemy different tables for relationship?

Suppose I have the following tables/relationships defined:
class Post(Base):
__table_name__ = "post"
id = Column(Integer, primary_key=True)
text = Column(String(100))
class Comment(Base):
__table_name__ = "comment"
id = Column(Integer, primary_key=True)
post_id = Column(Integer, ForeignKey("post.id"), nullable=False)
text = Column(String(100))
Now, I want to have notifications of events, like "You were tagged in a comment" or "you were tagged in a post". Is there some way to have a foreign key relationship in SQLAlchemy that can point to either a comment or a post (or several other tables in reality)? Something like:
class Notification(Base):
__table_name__ = "notification"
id = Column(Integer, primary_key=True)
target = relationship(??either post or comment??)
user_id = Column(Integer, ForeignKey("users.id")
created_date = Column(Datetime, default=datetime.utcnow)
I suppose you could just put foreign keys to all the different types and make all but one null, but that seems ugly. I'd also rather not have multiple tables for each type of notification; as in comment_notification and post_notification. Any ideas?

SQLAlchemy One-To-One and One-To-Many at the same time (AmbiguousForeignKeysError)

I'm working with SQLAlchemy and I try to achieve one-to-one and one-to-many relationships on the same parent class.
This is for the simplicity of keeping track of the main child entity.
Unfortunately I'm getting an error:
AmbiguousForeignKeysError: Could not determine join condition between
parent/child tables on relationship Customer.contact - 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.
Am I doing something wrong or it is not possible?
Here is a code example:
class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
contact_id = Column(Integer, ForeignKey('contact.id'))
address_id = Column(Integer, ForeignKey('address.id'))
contact = relationship('Contact', backref=backref("contact", uselist=False))
address = relationship('Address', backref=backref("address", uselist=False))
contact_list = relationship('Contact')
address_list = relationship('Address')
class Contact(Base):
__tablename__ = 'contact'
id = Column(Integer, primary_key=True)
customer_id = Column(Integer, ForeignKey(
'customer.id',
use_alter=True, name='fk_contact_customer_id_customer',
onupdate='CASCADE', ondelete='SET NULL'
))
first_name = Column(String(32))
last_name = Column(String(32))
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
customer_id = Column(Integer, ForeignKey(
'customer.id',
use_alter=True, name='fk_address_customer_id_customer',
onupdate='CASCADE', ondelete='SET NULL'
))
label = Column(String(32))
Thanks
Apparently the solution was later in the documentation:
SQLAlchemy does not know which foreign key to use, so you have to specify those as Column objects in relationship(foreign_keys=[]) like so:
class Contact(Base):
# ...
customer_id = Column(Integer, ForeignKey(
'customer.id',
use_alter=True, name='fk_contact_customer_id_customer',
onupdate='CASCADE', ondelete='SET NULL'
))
# ...
class Customer(Base):
# ...
contact_id = Column(Integer, ForeignKey('contact.id'))
#...
contact = relationship('Contact', uselist=False, foreign_keys=[contact_id])
contact_list = relationship('Contact', foreign_keys=[Contact.customer_id])
#...

NoForeignKeysError: Could not determine join condition ... there are no foreign keys linking these tables

Im using sqlalchemy to design a forum style website. I started knocking out the design but everytime I try to test it with a few inserts, it dumps a brick;
NoForeignKeysError: Could not determine join condition between parent/child
tables on relationship Thread.replies - there are no foreign keys linking
these tables. Ensure that referencing columns are associated with a
ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
Here are my "models"
from sqlalchemy import Integer, Column, String, create_engine, ForeignKey
from sqlalchemy.orm import relationship, sessionmaker, backref
from .database import Base # declarative base instance
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
username = Column(String, unique=True)
email = Column(String, unique=True)
threads = relationship("Thread", backref="user")
posts = relationship("Post", backref="user")
class Post(Base):
__tablename__ = "post"
id = Column(Integer, primary_key=True)
name = Column(String)
body = Column(String)
author = Column(Integer, ForeignKey("user.id"))
class Thread(Base):
__tablename__ = "thread"
id = Column(Integer, primary_key=True)
name = Column(String)
desc = Column(String)
replies = relationship("Post", backref="thread")
author_id = Column(Integer, ForeignKey("user.id"))
board_id = Column(Integer, ForeignKey("board.id"))
class Board(Base):
__tablename__ = "board"
id = Column(Integer, primary_key=True)
name = Column(String)
desc = Column(String)
threads = relationship("Thread", backref="board")
category_id = Column(Integer, ForeignKey("category.id"))
class Category(Base):
__tablename__ = "category"
id = Column(Integer, primary_key=True)
name = Column(String)
desc = Column(String)
threads = relationship("Board", backref="category")
engine = create_engine('sqlite:///:memory:', echo=True)
Base.metadata.create_all(engine)
session_factory = sessionmaker(bind=engine)
session = session_factory()
Your Post model has no thread reference. Add a column to Post referencing the Thread a post belongs to:
class Post(Base):
__tablename__ = "post"
id = Column(Integer, primary_key=True)
name = Column(String)
body = Column(String)
author = Column(Integer, ForeignKey("user.id"))
thread_id = Column(Integer, ForeignKey('thread.id'))
We can't use the name thread because that's what the Post.replies relationship will add to retrieved Thread instances.
This is a One to Many relationship as documented in the SQLAlchemy Relationship Configuration documentation.
You should add a field in the Post model that reads:
thread_id = Column(Integer, ForeignKey("thread.id"), nullable=True, default=None)
How is SQLAlchemy supposed to know how this relationship you defined supposed to link a thhread to a post? That's why you should have a foreign key from a post to its thread. You can allow it to be null, if it does not belong to a thread, it depends on your use case.

SQLAlchemy Polymorphic Loading

I've this model in SQLAlchemy:
class User(Base):
__tablename = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
type = Column(Text, nullable=False)
user_name = Column(Text, unique=True, nullable=False)
__mapper_args__ = {'polymorphic_on': type}
class Client(User):
__tablename__ = 'clients'
__mapper_args__ = {'polymorphic_identity': 'client'}
id = Column(Integer, ForeignKey('users.id'), primary_key=True)
client_notes = Column(Text)
This is a joined table inheritance. The problem is when I'm querying User:
self.session.query(User).all()
all I get is records from clients, while what I want is all record on User without Client. How do I solve this problem?
Edit: I'm using SQLAlchemy 0.7.4 and Pyramid 1.3a3
session.query(User) does not perform any filtering based on the value of type column.
However, the SQL SELECT statement it generates selects only data from the users table (unless with_polymorphic(...) is used).
But you can add the filter explicitely to achive the desired result:
session.query(User).filter(User.type=='user').all()

Categories

Resources