SQLAlchemy configuring many-to-many relationship to self using Association - python

I am having problems configuring a many to many relationship to a model itself. I can configure a self-many-to-many relation when I use anormal relationship configuration i.e. one not using an Association object.
In this scenario I have to record some extra information in the many-to-many table itself so I'm trying to implement relationship using an Association object (PageLink).
Here are the models.
class PageLink(Base):
'''
Association table.
'''
__tablename__ = 'page_links'
id = Column(Integer,primary_key=True)
page_from = Column(Integer,ForeignKey('page.id'),primary_key=True)
page_to = Column(Integer,ForeignKey('page.id'),primary_key=True)
extra_col1 = Column(String(256),nullable=False)
class Page(Base):
'''
main table
'''
__tablename__ = 'page'
id = Column(Integer,primary_key=True)
name = Column(String(56),nullable=False)
linked = relationship('PageLinks',backref='parent_page',
primaryjoin=id==PageLink.page_from,
secondaryjoin=id==PageLink.page_to)
This approach does not work. I have tried removing 'secondaryjoin' keyword but it wouldn't work.
Would greatly appreciate any help or advice on this matter.
Thank you for reading.

The association object pattern is not a sepecialization of the many-to-many relationship, but rather a special case of one-to-many relationships where you have a left_table - Many-To-One - association_table - One-To-Many - right_table set up. In short, you need two relationships, neither of which should have a secondary/secondaryjoin.
class PageLink(Base):
'''
Association table.
'''
__tablename__ = 'page_links'
id = Column(Integer,primary_key=True)
page_from = Column(Integer,ForeignKey('page.id'),primary_key=True)
page_to = Column(Integer,ForeignKey('page.id'),primary_key=True)
extra_col1 = Column(String(256),nullable=False)
class Page(Base):
'''
main table
'''
__tablename__ = 'page'
id = Column(Integer,primary_key=True)
name = Column(String(56),nullable=False)
linked_to = relationship('PageLinks',backref='parent_page',
primaryjoin=id==PageLink.page_from)
linked_from = relationship('PageLinks',backref='child_page',
primaryjoin=id==PageLink.page_to)
which means, to access the extra column for the 'to' links from some page p, you have to do: p.linked_to[0].extra_col1, or to get the actual linked page, p.linked_to[0].page_to
As an aside, it's often a great idea to use either an autoincrement primary key or (left/right) foreign key pair as the primary key in associations, but almost never useful to have both in the primary key. An alternative that combines both ideas would be to use an autoincrement integer as the only column in the primary key, and have an additional unique constraint on the left/right foreign key columns.

Related

SqlAlchemy doubly linked tables [duplicate]

I'm trying to model the following situation: A program has many versions, and one of the versions is the current one (not necessarily the latest).
This is how I'm doing it now:
class Program(Base):
__tablename__ = 'programs'
id = Column(Integer, primary_key=True)
name = Column(String)
current_version_id = Column(Integer, ForeignKey('program_versions.id'))
current_version = relationship('ProgramVersion', foreign_keys=[current_version_id])
versions = relationship('ProgramVersion', order_by='ProgramVersion.id', back_populates='program')
class ProgramVersion(Base):
__tablename__ = 'program_versions'
id = Column(Integer, primary_key=True)
program_id = Column(Integer, ForeignKey('programs.id'))
timestamp = Column(DateTime, default=datetime.datetime.utcnow)
program = relationship('Filter', foreign_keys=[program_id], back_populates='versions')
But then I get the error: Could not determine join condition between parent/child tables on relationship Program.versions - 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.
But what foreign key should I provide for the 'Program.versions' relationship? Is there a better way to model this situation?
Circular dependency like that is a perfectly valid solution to this problem.
To fix your foreign keys problem, you need to explicitly provide the foreign_keys argument.
class Program(Base):
...
current_version = relationship('ProgramVersion', foreign_keys=current_version_id, ...)
versions = relationship('ProgramVersion', foreign_keys="ProgramVersion.program_id", ...)
class ProgramVersion(Base):
...
program = relationship('Filter', foreign_keys=program_id, ...)
You'll find that when you do a create_all(), SQLAlchemy has trouble creating the tables because each table has a foreign key that depends on a column in the other. SQLAlchemy provides a way to break this circular dependency by using an ALTER statement for one of the tables:
class Program(Base):
...
current_version_id = Column(Integer, ForeignKey('program_versions.id', use_alter=True, name="fk_program_current_version_id"))
...
Finally, you'll find that when you add a complete object graph to the session, SQLAlchemy has trouble issuing INSERT statements because each row has a value that depends on the yet-unknown primary key of the other. SQLAlchemy provides a way to break this circular dependency by issuing an UPDATE for one of the columns:
class Program(Base):
...
current_version = relationship('ProgramVersion', foreign_keys=current_version_id, post_update=True, ...)
...
This design is not ideal; by having two tables refer to one another, you cannot effectively insert into either table, because the foreign key required in the other will not exist. One possible solution in outlined in the selected answer of
this question related to microsoft sqlserver, but I will summarize/elaborate on it here.
A better way to model this might be to introduce a third table, VersionHistory, and eliminate your foreign key constraints on the other two tables.
class VersionHistory(Base):
__tablename__ = 'version_history'
program_id = Column(Integer, ForeignKey('programs.id'), primary_key=True)
version_id = Column(Integer, ForeignKey('program_version.id'), primary_key=True)
current = Column(Boolean, default=False)
# I'm not too familiar with SQLAlchemy, but I suspect that relationship
# information goes here somewhere
This eliminates the circular relationship you have created in your current implementation. You could then query this table by program, and receive all existing versions for the program, etc. Because of the composite primary key in this table, you could access any specific program/version combination. The addition of the current field to this table takes the burden of tracking currency off of the other two tables, although maintaining a single current version per program could require some trigger gymnastics.
HTH!

SQLAlchemy one-to-many relationship (Single table with join table)

I have db that I cannot modify, it has two tables 'people' and 'relation'. The table 'people' has names, ids and the column parent (yes/no). The table 'relation' contains a foreign key 'people.id' for parent and a 'people.id' for its child. I want to join columns in the people table so I can
People.query.filter_by(id='id of the parent')
to get the name of the parent and it's childs. This is my code:
class People(db.model):
__tablename__ = 'people'
id = db.Column(db.integer(), primary_key=True
name = db.Column(db.String())
parent = db.Column(db.Integer()) ##0 for no 1 for yes
parent_id=db.relationship('Link',backref=db.backref('Link.parent_id')
class Link(db.Model):
_tablename__ = 'link'
parent_id=db.Column(db.Integer(),db.ForeignKey('people.id'),primary_key=True)
id = db.Column(db.Integer(), db.ForeignKey('people.id'), primary_key=True)
dateofbirth = db.Column(db.Integer())
SQLAlchemy tells me:
ArgumentError: relationship 'parent_id' expects a class or a mapper argument (received: <class 'sqlalchemy.sql.schema.Table'>)
Excuse me if I messed up, but it's my first question here (and also the first steps with SQLAlchemy)
Typically you would want to set up the foreign key and backref in the same table, like this:
class Link(db.Model):
_tablename__ = 'link'
parent_id = db.Column(db.Integer(),db.ForeignKey('people.id'),primary_key=True)
parent = db.relationship('People', backref='links')
Now you can access each Link entries parent via Link.parent, and you can get a list of each People entries links via People.links (assuming this is a one-to-many relationship).
Also, if People.parent is supposed to represent a boolean value then:
1.) you should follow the standard naming convention and call it something like is_parent
2.) you should declare People.parent as a db.Boolean type, not a db.Integer. In most (probably all) database implementations, using booleans instead of integers (when appropriate) is more memory efficient.
I hope this helped.

Sqlacodegen generates mixed models and tables

Executing this command:
sqlacodegen <connection-url> --outfile db.py
The db.py contains generated tables:
t_table1 = Table(...)
and classes too:
Table2(Base):
__tablename__ = 'table2'
The problem is that a table is generated in one way only - either a table or a class.
I would like to make it generate models (classes) only but in the provided flags I couldn't find such an option. Any idea?
It looks like what you're describing is a feature itself. sqlacodegenwill not always generate class models.
It will only form model classes for tables that have a primary key and are not association tables, as you can see in the source code:
# Only form model classes for tables that have a primary key and are not association tables
if noclasses or not table.primary_key or table.name in association_tables:
model = self.table_model(table)
else:
model = self.class_model(table, links[table.name], self.inflect_engine, not nojoined)
classes[model.name] = model
Furthermore, in the documentation it is stated that
A table is considered an association table if it satisfies all of the
following conditions:
has exactly two foreign key constraints
all its columns are involved in said constraints
Although, you can try a quick and dirty hack. Locate those lines in the source code (something like /.../lib/python2.7/site-packages/sqlacodegen/codegen.py) and comment out the first three code lines (and fix indentation):
# Only form model classes for tables that have a primary key and are not association tables
# if noclasses or not table.primary_key or table.name in association_tables:
# model = self.table_model(table)
# else:
model = self.class_model(table, links[table.name], self.inflect_engine, not nojoined)
classes[model.name] = model
I have tried this for one specific table that was generated as a table model. It went from
t_Admin_op = Table(
'Admin_op', metadata,
Column('id_admin', Integer, nullable=False),
Column('id_op', Integer, nullable=False)
)
to
class AdminOp(Base):
__tablename__ = 'Admin_op'
id_admin = Column(Integer, nullable=False)
id_op = Column(Integer, nullable=False)
You can also open an issue about this as a feature request, in the official tracker.
Just in case, if you want the opposite (only table models), you could do so with the --noclasses flag.

How do you specify the foreign key column value in SQLAlchemy?

One of my models has the following relationship:
class User(Base):
account = relationship("Account")
I would like to set the account id manually.
My first attempt was this:
class User(Base):
account = relationship("Account")
accounts_id = Column(Integer, ForeignKey("accounts.id"), nullable=True)
#classmethod
def from_json(cls, json):
appointment = Appointment()
appointment.account_id = json["account_id"]
return appointment
The above dosen't work. We can't refer to this column because SQLAlchemy throws a fit. This is the exception:
sqlalchemy.exc.InvalidRequestError: Implicitly combining column users.accounts_id with column users.accounts_id under attribute 'accounts_id'. Please configure one or more attributes for these same-named columns explicitly.
I've tried hunting through the docs and expermiented with getting to the attribute numerous ways but I haven't been able to find, much less set it.
print(self.account.account_id)
print(self.account.relationhip)
print(self.account.properties)
print(self.account.primaryjoin)
Any ideas?
[Edit- added exception above]
Use the Account class to define the relationship, and add the backref keyword argument:
from sqlalchemy.orm import relationship
class User(Base):
accounts_id = Column(Integer, ForeignKey('account.id'))
class Account(Base):
users = relationship('User', backref='account')
When the backref keyword is used on a single relationship, it’s exactly the same as if the above two relationships were created individually using back_populates on each.
References
Linking Relationships with Backref
Controlling Cascade on Backrefs
SQLAlchemy ORM Examples

Django templates foreign key traversal

I saw multiple questions on this but non worked for me, so I'll query about my specific issue
I have a table with several foreign keys pointing to other tables, looks like:
class EventCodes(models.Model):
code_name = models.CharField(max_length=25)
class Severities(models.Model):
severity_name = models.CharField(max_length=15)
class Systems(models.Model):
system_id = models.CharField(max_length=15)
class Events(models.Model):
system_id_fk = models.ForeignKey(Systems)
severity_fk = models.ForeignKey(Severities)
code_fk = models.ForeignKey(EventCodes)
I also have a template that allows a user to define a search criteria per system_id and/or severity_name and/or code_name -> this will give me the relevant records from the Events table
The only problem is that now when I present them in the results template I present their IDs and not their values (the Django ORM added the id PK for each of these tables)
QUESTION: How do i allow the results html which got the queryset of the Events to access the values on the System, Severities, EventCodes tables so I can present their string value and not their non human readable ID
You can span relationships by accessing the property by using. Django covers this in the documentation
an_event = Events.objects.all()[0]
an_event.system_id_fk.system_id
Your naming convention could be a little confusing because for ForeignKey Fields django automatically creates an _id field on the model:
system_id_fk = models.ForeignKey(Systems)
The above creates a column named system_id_fk_id in the events table in your db. But when you access an_event.system_id_fk it will use the system_id_fk_id column to query the related objeCT!!!

Categories

Resources