简体   繁体   English

sqlalchemy通用外键(如django ORM)

[英]sqlalchemy generic foreign key (like in django ORM)

Does sqlalchemy have something like django's GenericForeignKey? sqlalchemy有像django的GenericForeignKey吗? And is it right to use generic foreign fields. 是否使用通用外国字段是正确的。

My problem is: I have several models (for example, Post, Project, Vacancy, nothing special there) and I want to add comments to each of them. 我的问题是:我有几个模型(例如,Post,Project,Vacancy,没什么特别的),我想为每个模型添加注释。 And I want to use only one Comment model. 我只想使用一个评论模型。 Does it worth to? 值得吗? Or should I use PostComment, ProjectComment etc.? 或者我应该使用PostComment,ProjectComment等? Pros/cons of both ways? 两种方式的利弊?

Thanks! 谢谢!

The simplest pattern which I use most often is that you actually have separate Comment tables for each relationship. 我最常使用的最简单的模式是你实际上为每个关系都有单独的Comment表。 This may seem frightening at first, but it doesn't incur any additional code versus using any other approach - the tables are created automatically, and the models are referred to using the pattern Post.Comment , Project.Comment , etc. The definition of Comment is maintained in one place. 这一开始可能看起来很可怕,但与使用任何其他方法相比,它不会产生任何额外的代码 - 表是自动创建的,模型是使用Post.CommentProject.Comment等模式Post.Comment的。评论保持在一个地方。 This approach from a referential point of view is the most simple and efficient, as well as the most DBA friendly as different kinds of Comments are kept in their own tables which can be sized individually. 从参考的角度来看,这种方法是最简单和有效的,以及最友好的DBA,因为不同类型的注释保存在它们自己的表中,可以单独调整大小。

Another pattern to use is a single Comment table, but distinct association tables. 另一种使用的模式是单个Comment表,但是不同的关联表。 This pattern offers the use case that you might want a Comment linked to more than one kind of object at a time (like a Post and a Project at the same time). 此模式提供了一个用例,您可能希望一次链接到多种对象的注释(例如同时发布帖子和项目)。 This pattern is still reasonably efficient. 这种模式仍然相当有效。

Thirdly, there's the polymorphic association table. 第三,有多态关联表。 This pattern uses a fixed number of tables to represent the collections and the related class without sacrificing referential integrity. 此模式使用固定数量的表来表示集合和相关类,而不会牺牲参照完整性。 This pattern tries to come the closest to the Django-style "generic foreign key" while still maintaining referential integrity, though it's not as simple as the previous two approaches. 这种模式试图最接近Django风格的“通用外键”,同时仍保持参照完整性,尽管它不像前两种方法那么简单。

Imitating the pattern used by ROR/Django, where there are no real foreign keys used and rows are matched using application logic, is also possible. 模仿ROR / Django使用的模式,其中没有使用真正的外键并且使用应用程序逻辑匹配行也是可能的。

The first three patterns are illustrated in modern form in the SQLAlchemy distribution under examples/generic_associations/. 前三个模式以示例/ generic_associations /下的SQLAlchemy发行版中的现代形式说明。

The ROR/Django pattern, since it gets asked about so often, I will also add to the SQLAlchemy examples, even though I don't like it much. ROR / Django模式,因为它经常被问到,我也会添加SQLAlchemy的例子,即使我不喜欢它。 The approach I'm using is not exactly the same as what Django does as they seem to make use of a "contenttypes" table to keep track of types, that seems kind of superfluous to me, but the general idea of an integer column that points to any number of tables based on a discriminator column is present. 我正在使用的方法与Django所做的不完全相同,因为它们似乎使用“contenttypes”表来跟踪类型,这对我来说似乎有点多余,但是整数列的一般概念是指向基于鉴别器列的任意数量的表。 Here it is: 这里是:

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)

I know this is probably a terrible way to do this, but it was a quick fix for me. 我知道这可能是一种可怕的方法,但对我来说这是一个快速解决方案。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM