sqlalchemy通用外键(如django ORM)

kra*_*lya 15 python django sqlalchemy generic-foreign-key

sqlalchemy有像django的GenericForeignKey吗?是否使用通用外国字段是正确的.

我的问题是:我有几个模型(例如,Post,Project,Vacancy,没什么特别的),我想为每个模型添加注释.我只想使用一个评论模型.值得吗?或者我应该使用PostComment,ProjectComment等?两种方式的利弊?

谢谢!

zzz*_*eek 17

我最常使用的最简单的模式是你实际上为每个关系都有单独的Comment表.最初,这可能显得令人恐惧,但它不产生任何额外的代码与使用任何其他的方法-该表被自动创建的,并且这些模型被称为使用该图案Post.Comment,Project.Comment等评论的定义被维持在一个地方.从参考的角度来看,这种方法是最简单和有效的,以及最友好的DBA,因为不同类型的注释保存在它们自己的表中,可以单独调整大小.

另一种使用的模式是单个Comment表,但是不同的关联表.此模式提供了一个用例,您可能希望一次链接到多种对象的注释(例如同时发布帖子和项目).这种模式仍然相当有效.

第三,有多态关联表.此模式使用固定数量的表来表示集合和相关类,而不会牺牲参照完整性.这种模式试图最接近Django风格的"通用外键",同时仍保持参照完整性,尽管它不像前两种方法那么简单.

模仿ROR/Django使用的模式,其中没有使用真正的外键并且使用应用程序逻辑匹配行也是可能的.

前三个模式以示例/ generic_associations /下的SQLAlchemy发行版中的现代形式说明.

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)
Run Code Online (Sandbox Code Playgroud)

  • 它是完全干燥的。DRY 的意思是“不要重复自己”。如果你看看这个模式是如何运作的,你就根本不会重复自己。仅仅因为数据库中有很多类似的表并不意味着您在重复自己;它们的创建是自动化的,添加一些新列如“编辑”(使用 Alembic 等工具)也是自动化的。它是空间/时间效率最高且对 DBA 友好的方法(因为不同表的存储可以独立配置)。太糟糕了,我很难说服前 djangoers 相信这一点。 (2认同)
  • 这里所有模式的示例,包括或多或少执行 ROR/Django 通用外键的模式,位于 SQLA 发行版的 Examples/generic_associations 下,从 0.9 开始,它们已更新:https://github.com/zzzeek /sqlalchemy/tree/master/examples/generic_associations (2认同)