sqlalchemy 有类似 django 的 GenericForeignKey 的东西吗?使用通用外国字段是否正确。
我的问题是:我有几个模型(例如,Post、Project、Vacancy,没什么特别的),我想为每个模型添加评论。而且我只想使用一个评论模型。值得吗?或者我应该使用 PostComment、ProjectComment 等?两种方式的优缺点?
谢谢!
sqlalchemy 有类似 django 的 GenericForeignKey 的东西吗?使用通用外国字段是否正确。
我的问题是:我有几个模型(例如,Post、Project、Vacancy,没什么特别的),我想为每个模型添加评论。而且我只想使用一个评论模型。值得吗?或者我应该使用 PostComment、ProjectComment 等?两种方式的优缺点?
谢谢!
我最常使用的最简单的模式是,实际上每个关系都有单独的 Comment 表。起初这可能看起来很可怕,但与使用任何其他方法相比,它不会产生任何额外的代码 - 表是自动创建的,模型是使用模式Post.Comment
,Project.Comment
等引用的。 Comment 的定义保留在一个地方. 从参考的角度来看,这种方法是最简单、最有效的,也是对 DBA 最友好的,因为不同类型的评论保存在自己的表中,可以单独调整大小。
另一种要使用的模式是单个 Comment 表,但有不同的关联表。这种模式提供了一个用例,您可能希望一次将评论链接到一种以上的对象(例如同时链接到一个帖子和一个项目)。这种模式仍然相当有效。
第三,多态关联表。此模式使用固定数量的表来表示集合和相关类,而不会牺牲引用完整性。这种模式试图最接近 Django 风格的“通用外键”,同时仍然保持引用完整性,尽管它不像前两种方法那么简单。
模仿 ROR/Django 使用的模式,其中没有使用真正的外键并且使用应用程序逻辑匹配行,也是可能的。
前三种模式以现代形式在 SQLAlchemy 发行版中的examples/generic_associations/下进行了说明。
ROR/Django 模式,因为它经常被问到,我也会添加到 SQLAlchemy 示例中,即使我不太喜欢它。我使用的方法与 Django 所做的并不完全相同,因为它们似乎使用“contenttypes”表来跟踪类型,这对我来说似乎有点多余,但是整数列的一般概念指向基于鉴别器列的任意数量的表。这里是:
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy import create_engine, Integer, Column, \
String, and_
from sqlalchemy.orm import Session, relationship, foreign, remote, backref
from sqlalchemy import event
class Base(object):
"""Base class which provides automated table name
and surrogate primary key column.
"""
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer, primary_key=True)
Base = declarative_base(cls=Base)
class Address(Base):
"""The Address class.
This represents all address records in a
single table.
"""
street = Column(String)
city = Column(String)
zip = Column(String)
discriminator = Column(String)
"""Refers to the type of parent."""
parent_id = Column(Integer)
"""Refers to the primary key of the parent.
This could refer to any table.
"""
@property
def parent(self):
"""Provides in-Python access to the "parent" by choosing
the appropriate relationship.
"""
return getattr(self, "parent_%s" % self.discriminator)
def __repr__(self):
return "%s(street=%r, city=%r, zip=%r)" % \
(self.__class__.__name__, self.street,
self.city, self.zip)
class HasAddresses(object):
"""HasAddresses mixin, creates a relationship to
the address_association table for each parent.
"""
@event.listens_for(HasAddresses, "mapper_configured", propagate=True)
def setup_listener(mapper, class_):
name = class_.__name__
discriminator = name.lower()
class_.addresses = relationship(Address,
primaryjoin=and_(
class_.id == foreign(remote(Address.parent_id)),
Address.discriminator == discriminator
),
backref=backref(
"parent_%s" % discriminator,
primaryjoin=remote(class_.id) == foreign(Address.parent_id)
)
)
@event.listens_for(class_.addresses, "append")
def append_address(target, value, initiator):
value.discriminator = discriminator
class Customer(HasAddresses, Base):
name = Column(String)
class Supplier(HasAddresses, Base):
company_name = Column(String)
engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
session = Session(engine)
session.add_all([
Customer(
name='customer 1',
addresses=[
Address(
street='123 anywhere street',
city="New York",
zip="10110"),
Address(
street='40 main street',
city="San Francisco",
zip="95732")
]
),
Supplier(
company_name="Ace Hammers",
addresses=[
Address(
street='2569 west elm',
city="Detroit",
zip="56785")
]
),
])
session.commit()
for customer in session.query(Customer):
for address in customer.addresses:
print(address)
print(address.parent)
我知道这可能是一种糟糕的方法,但这对我来说是一个快速的解决方法。
class GenericRelation(object):
def __init__(self, object_id, object_type):
self.object_id = object_id
self.object_type = object_type
def __composite_values__(self):
return (self.object_id, self.object_type)
class Permission(AbstractBase):
#__abstract__ = True
_object = None
_generic = composite(
GenericRelation,
sql.Column('object_id', data_types.UUID, nullable=False),
sql.Column('object_type', sql.String, nullable=False),
)
permission_type = sql.Column(sql.Integer)
@property
def object(self):
session = object_session(self)
if self._object or not session:
return self._object
else:
object_class = eval(self.object_type)
self._object = session.query(object_class).filter(object_class.id == self.object_id).first()
return self._object
@object.setter
def object(self, value):
self._object = value
self.object_type = value.__class__.__name__
self.object_id = value.id