I'm quite new to relational databases and web programming in general and I'm facing a problem on how to structure my database for use of a voting system, similar to reddit.
I'm using sqlalchemy, the important bits are shown below:
class Vote(Base):
__tablename__ = 'votes'
id = Column(Integer, primary_key = True)
vote_type = Column(Integer, default = 0)
user_id = Column(Integer, ForeignKey('users.id'))
#article_id = Column(Integer, ForeignKey('article_items.id'))
#comment_id = Column(Integer, ForeignKey('comments.id'))
class ArticleItem(Base):
__tablename__ = 'article_items'
id = Column(Integer, primary_key = True)
vote_ups = Column(Integer, default = 0)
vote_downs = Column(Integer, default = 0)
article = relationship("Article",
uselist = False,
backref = 'article_item')
comments = relationship("Comment")
votes = relationship("Vote", cascade = "all")
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key = True)
data = Column(Text, nullable = False)
replies = relationship("Comment")
vote_ups = Column(Integer, default = 0)
vote_downs = Column(Integer, default = 0)
votes = relationship("Vote", cascade = "all")
parent_id = Column(Integer, ForeignKey('comments.id'))
article_id = Column(Integer, ForeignKey('article_items.id'))
As it stands now, I have to include two foreign keys in class Vote, but only one of the two keys is used at any time.
Is there any easier/recommended way to do this? Should I keep two different vote types instead; one for comments and one for articles? Or should I maybe merge ArticleItem and Comment into one class Voteable?
Related
This question already has an answer here:
The foreign key associated with column 'x.y' could not ... generate a foreign key to target column 'None'
(1 answer)
Closed 4 years ago.
I've got three tables: users, funds, and fund types. Each fund has a fund type, each user has a list of funds, and each user can also have a list of fund types that they have created.
Schema:
class Fund(Base):
__tablename__ = 'fds_funds'
fds_fund_id = Column(Integer, primary_key=True)
fds_name = Column(String(128))
fds_symbol = Column(String(5))
fds_fdt_fund_type_id = Column(Integer, ForeignKey('fdt_fund_type_id'))
fund_type = relationship('FundType', backref=backref('fds_funds', uselist=False))
class FundType(Base):
__tablename__ = 'fdt_fund_types'
fdt_fund_type_id = Column(Integer, primary_key=True)
fdt_type_name = Column(String(128))
fdt_usr_user_id = Column(Integer, ForeignKey('usr_user_id'), nullable=True)
user_funds = Table('usf_user_funds', Base.metadata,
Column('usf_usr_user_id', Integer, ForeignKey('usr_users.usr_user_id')),
Column('usf_fds_fund_id', Integer, ForeignKey('fds_funds.fds_fund_id'))
)
class User(Base):
"""
Application's user model.
"""
__tablename__ = 'usr_users'
usr_user_id = Column(Integer, primary_key=True)
usr_email = Column(Unicode(50))
_usr_password = Column('password', Unicode(64))
fund_types = relationship('FundType', foreign_keys='FundType.fdt_usr_user_id')
funds = relationship('Fund', secondary=user_funds)
I'm following the documentation here, and it appears to very clearly say that the first argument to the foreign key designation should be the column name, not the table name, but I'm getting this error when I run the initialize_DB script:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'fdt_fund_types.fdt_usr_user_id' could not find table
'usr_user_id' with which to generate a foreign key to target column
'None'
Have I misread the documentation?
I'm not really sure what fixed it, but I did some rearranging and messing with the references. For posterity, this works:
user_funds = Table('usf_user_funds', Base.metadata,
Column('usf_usr_user_id', Integer, ForeignKey('usr_users.usr_user_id')),
Column('usf_fds_fund_id', Integer, ForeignKey('fds_funds.fds_fund_id'))
)
class User(Base):
"""
Application's user model.
"""
__tablename__ = 'usr_users'
usr_user_id = Column(Integer, primary_key=True)
usr_email = Column(Unicode(50))
_usr_password = Column('password', Unicode(64))
fund_types = relationship('FundType', foreign_keys='FundType.fdt_usr_user_id')
funds = relationship('Fund', secondary=user_funds)
class FundType(Base):
__tablename__ = 'fdt_fund_types'
fdt_fund_type_id = Column(Integer, primary_key=True)
fdt_type_name = Column(String(128))
fdt_usr_user_id = Column(Integer, ForeignKey('usr_users.usr_user_id'), nullable=True)
class Fund(Base):
__tablename__ = 'fds_funds'
fds_fund_id = Column(Integer, primary_key=True)
fds_name = Column(String(128))
fds_symbol = Column(String(5))
fds_fdt_fund_type_id = Column(Integer, ForeignKey('fdt_fund_types.fdt_fund_type_id'))
fund_type = relationship('FundType', backref=backref('funds', uselist=False))
I created a Table a Bmarks which has two foreign keys which have relation with same table Url_hash
class Hashed(Base):
__tablename__ = "url_hash"
hash_id = Column(Unicode(22), primary_key=True)
url = Column(UnicodeText)
clicks = Column(Integer, default=0)
def __init__(self, url):
cleaned_url = str(unidecode(url))
self.hash_id = unicode(generate_hash(cleaned_url))
self.url = url
class Bmark(Base):
__tablename__ = "bmarks"
bid = Column(Integer, autoincrement=True, primary_key=True)
hash_id = Column(Unicode(22), ForeignKey('url_hash.hash_id'))
clean_hash_id = Column(Unicode(22), ForeignKey('url_hash.hash_id'))
description = Column(UnicodeText())
extended = Column(UnicodeText())
stored = Column(DateTime, default=datetime.utcnow)
updated = Column(DateTime, onupdate=datetime.utcnow)
clicks = Column(Integer, default=0)
inserted_by = Column(Unicode(255))
username = Column(Unicode(255), ForeignKey('users.username'),
nullable=False,)
tag_str = Column(UnicodeText())
hashed = relation(Hashed,
foreign_keys="Bmark.hash_id",
backref="bmark",
uselist=False
)
clean_hashed = relation(Hashed,
foreign_keys="Bmark.clean_hash_id",
backref="bmark",
uselist=False
)
I am trying to store url after cleaning it a little bit like removing headers,utm parameters etc for indexing purposes
Error is occurring while creating the database
sqlalchemy.exc.ArgumentError: Error creating backref 'bmark' on relationship 'Bmark.clean_hashed': property of that name exists on mapper 'Mapper|Hashed|url_hash'
Actually the error message is very informative.
Just rename one of your backref="bmark" to something else like backref="my_clean_bmark".
I created a many to many relationship with sqlalchemy like this:
subject_books = Table('subject_books', Base.metadata,
Column('subject_id', Integer, ForeignKey('subjects.id')),
Column('book_id', Integer, ForeignKey('books.id')),
Column('group', Integer)
)
class Subject(Base):
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True)
value = Column(Unicode(255), unique=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255))
isbn = Column(Unicode(24))
subjects = relationship('Subject', secondary=subject_books, collection_class=attribute_mapped_collection('group'), backref='books')
after that I created a test like following:
book = Book(title='first book',isbn='test')
book.subjects[0] = Subject(value='first subject')
book.subjects[1] = Subject(value='second subject')
session.add(book)
transaction.commit()
and it works fine. But what I really want is to store more than one subject with the same group value, so I tried the following test:
book = Book(title='first book',isbn='test')
book.subjects[0] = [Subject(value='first subject'),Subject(value='second subject')]
book.subjects[1] = [Subject(value='third subject'),Subject(value='forth subject')]
session.add(book)
transaction.commit()
but it does not work.
Can this be done using sqlalchemy?
Thanks in Advance
Razi
I think you are constructing wrong relation ship.
Your relation ship must be
book M2M subject
subject M2M group
So you have to create one more model for group and that must be assign as m2m in Subject
Your models will be like.
subject_books = Table('subject_books', Base.metadata,
Column('subject_id', Integer, ForeignKey('subjects.id')),
Column('book_id', Integer, ForeignKey('books.id')),
)
subject_group = Table('subject_groups', Base.metadata,
Column('group_id', Integer, ForeignKey('groups.id')),
Column('subject_id', Integer, ForeignKey('subjects.id')),
)
class Subject(Base):
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True)
value = Column(Unicode(255), unique=True)
groups = relationship('Groups', secondary=subject_groups, backref='subjects')
class Groups(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255))
isbn = Column(Unicode(24))
subjects = relationship('Subject', secondary=subject_books, backref='books')
I also check the docs for attribute_mapped_collection. But each time I found that each key is associated with only one object not more then one. If you read anywhere then please provide the link so I can check that how it will be fit in your code.
I think this will be help you.
I’ve looked all over the SQLAlchemy tutorial and other similar questions but I seem to be struggling to get this join to work:
The scenario: I have a pages table represented by the Page model. Pages can be created by an user and edited by an user, but not necessarily the same one. My Page model looks like this (abridged):
class Page(Base):
__tablename__ = 'pages'
id = Column(Integer, primary_key = True)
slug = Column(Text)
title = Column(Text)
direct_link = Column(Text)
body = Column(Text)
category_id = Column(Integer, ForeignKey('categories.id'))
published_on = Column(DateTime)
publishing_user_id = Column(Integer, ForeignKey('users.id'))
last_edit_on = Column(DateTime)
last_edit_user_id = Column(Integer, ForeignKey('users.id'))
# Define relationships
publish_user = relationship('User', backref = backref('pages', order_by = id), primaryjoin = "Page.publishing_user_id == User.id")
edit_user = relationship('User', primaryjoin = "Page.last_edit_user_id == User.id")
category = relationship('Category', backref = backref('pages', order_by = id))
My users are stored in the users table represented by the User model. As I said I’ve been all over the SQLAlchemy docs looking for this, I’ve tried to make it look as similar to their example as possible, but no to no avail. Any help would be greatly appreciated.
As of version 0.8, SQLAlchemy can resolve the ambiguous join using only the foreign_keys keyword parameter to relationship.
publish_user = relationship(User, foreign_keys=[publishing_user_id],
backref=backref('pages', order_by=id))
edit_user = relationship(User, foreign_keys=[last_edit_user_id])
Documentation at http://docs.sqlalchemy.org/en/rel_0_9/orm/join_conditions.html#handling-multiple-join-paths
I think you almost got it right; only instead of Model names you should use Table names when defining primaryjoin. So instead of
# Define relationships
publish_user = relationship('User', backref = backref('pages', order_by = id),
primaryjoin = "Page.publishing_user_id == User.id")
edit_user = relationship('User',
primaryjoin = "Page.last_edit_user_id == User.id")
use:
# Define relationships
publish_user = relationship('User', backref = backref('pages', order_by = id),
primaryjoin = "pages.publishing_user_id == users.id")
edit_user = relationship('User',
primaryjoin = "pages.last_edit_user_id == users.id")
Try foreign_keys option:
publish_user = relationship(User, foreign_keys=publishing_user_id,
primaryjoin=publishing_user_id == User.id,
backref=backref('pages', order_by=id))
edit_user = relationship(User, foreign_keys=last_edit_user_id,
primaryjoin=last_edit_user_id == User.id)
The example in this documentation
http://docs.sqlalchemy.org/en/rel_0_9/orm/join_conditions.html#handling-multiple-join-paths isn't for one-to-many.... I think.
In the one-to-many case here's what worked for me:
class Pipeline(Base):
__tablename__ = 'pipelines'
id = Column(UUID(as_uuid=True), primary_key=True, unique=True, default=uuid.uuid4)
...
input_resources = relationship("Resource", foreign_keys="Resource.input_pipeline_id")
output_resources = relationship("Resource", foreign_keys="Resource.output_pipeline_id")
...
class Resource(Base):
__tablename__ = 'resources'
id = Column(UUID(as_uuid=True), primary_key=True, unique=True, default=uuid.uuid4)
....
input_pipeline_id = Column(UUID(as_uuid=True), ForeignKey("pipelines.id"))
output_pipeline_id = Column(UUID(as_uuid=True), ForeignKey("pipelines.id"))
...
I have read the SQLAlchemy documentation and tutorial about building many-to-many relation but I could not figure out how to do it properly when the association table contains more than the 2 foreign keys.
I have a table of items and every item has many details. Details can be the same on many items, so there is a many-to-many relation between items and details
I have the following:
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
My association table is (It's defined before the other 2 in the code):
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
In the documentation, it's said that I need to use the "association object". I could not figure out how to use it properly, since it's mixed declarative with mapper forms and the examples seem not to be complete. I added the line:
details = relation(ItemDetail)
as a member of Item class and the line:
itemDetail = relation('Detail')
as a member of the association table, as described in the documentation.
when I do item = session.query(Item).first(), the item.details is not a list of Detail objects, but a list of ItemDetail objects.
How can I get details properly in Item objects, i.e., item.details should be a list of Detail objects?
From the comments I see you've found the answer. But the SQLAlchemy documentation is quite overwhelming for a 'new user' and I was struggling with the same question. So for future reference:
ItemDetail = Table('ItemDetail',
Column('id', Integer, primary_key=True),
Column('itemId', Integer, ForeignKey('Item.id')),
Column('detailId', Integer, ForeignKey('Detail.id')),
Column('endDate', Date))
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail, backref='Detail')
Like Miguel, I'm also using a Declarative approach for my junction table. However, I kept running into errors like
sqlalchemy.exc.ArgumentError: secondary argument <class 'main.ProjectUser'> passed to to relationship() User.projects must be a Table object or other FROM clause; can't send a mapped class directly as rows in 'secondary' are persisted independently of a class that is mapped to that same table.
With some fiddling, I was able to come up with the following. (Note my classes are different than OP's but the concept is the same.)
Example
Here's a full working example
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)
# Make the DeclarativeMeta
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
projects = relationship('Project', secondary='project_users', back_populates='users')
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary='project_users', back_populates='projects')
class ProjectUser(Base):
__tablename__ = "project_users"
id = Column(Integer, primary_key=True)
notes = Column(String, nullable=True)
user_id = Column(Integer, ForeignKey('users.id'))
project_id = Column(Integer, ForeignKey('projects.id'))
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add users
usr1 = User(name="bob")
session.add(usr1)
usr2 = User(name="alice")
session.add(usr2)
session.commit()
# add projects
prj1 = Project(name="Project 1")
session.add(prj1)
prj2 = Project(name="Project 2")
session.add(prj2)
session.commit()
# map users to projects
prj1.users = [usr1, usr2]
prj2.users = [usr2]
session.commit()
with Session(bind=engine) as session:
print(session.query(User).where(User.id == 1).one().projects)
print(session.query(Project).where(Project.id == 1).one().users)
Notes
reference the table name in the secondary argument like secondary='project_users' as opposed to secondary=ProjectUser
use back_populates instead of backref
I made a detailed writeup about this here.
Previous Answer worked for me, but I used a Class base approach for the table ItemDetail. This is the Sample code:
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True, index=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail.__table__, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail.__table__, backref='Detail')