2013-07-17 73 views
12

是否sqlalchemy有類似django的GenericForeignKey?是否使用通用的外國領域?sqlalchemy通用外鍵(如在Django的ORM)

我的問題是:我有幾個模型(例如,郵政,項目,空缺,沒有什麼特別的),我想添加評論給他們每個人。我只想使用一個評論模型。它值得嗎?或者我應該使用PostComment,ProjectComment等?兩種方式的優點/缺點?

謝謝!

回答

13

我最常使用的最簡單的模式是,實際上每個關係都有單獨的註釋表。這起初看起來很可怕,但它不會產生任何額外的代碼,而不是使用任何其他方法 - 表格是自動創建的,並且使用Post.CommentProject.Comment等模式引用模型。Comment的定義保持在一個地方。從參考角度來看,這種方法是最簡單和高效的,也是DBA最友好的方式,因爲不同類型的評論可以保存在各自的表中,而這些表可以單獨調整大小。

要使用的另一種模式是單個註釋表,但具有不同的關聯表。這種模式提供了一種用例,您可能需要將評論一次鏈接到多種類型的對象(例如同時發佈Post和Project)。這種模式仍然相當有效。

第三,有多態關聯表。此模式使用固定數量的表來表示集合和相關類,而不犧牲參照完整性。這種模式試圖儘可能地接近Django風格的「通用外鍵」,同時仍然保持參照完整性,雖然它不像前兩種方法那麼簡單。

模仿ROR/Django使用的模式,其中沒有使用真正的外鍵,並且使用應用程序邏輯來匹配行也是可能的。

前三種模式在examples/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) 
+0

謝謝!我不喜歡第一種模式 - 我認爲,它不是乾的。如果我想爲每條評論添加一些信息(可能是「編輯」標誌),我應該使用所有模型/表格。我正在考慮我的'Tag'模型的第二種模式。它可以同時鏈接到「Project」和「Post」。第三個似乎是我的'評論'所需要的。 ROR/Django似乎並不那麼簡單,所以我會「研究」它。 – krasulya

+1

它完全乾燥。幹意味着,「不要重複自己」。如果你看看這個模式是如何工作的,那麼你根本就不會重複自己。只是因爲DB中有很多類似的表並不意味着你在重複自己;他們的創作是自動化的,還有一些像「編輯」(使用像Alembic這樣的工具)的新專欄。這是最節省時間和DBA的方法(因爲不同表格的存儲可以獨立配置)。這太糟糕了,我很難說服這些人。 – zzzeek

+0

我喜歡你的第一個解決方案的概念,但我不清楚你如何實際執行它。你能舉個簡單的例子嗎? – aquavitae

0

我知道這可能是做這一個可怕的方式,但它是一個快速解決我。

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