是否sqlalchemy有類似django的GenericForeignKey?是否使用通用的外國領域?sqlalchemy通用外鍵(如在Django的ORM)
我的問題是:我有幾個模型(例如,郵政,項目,空缺,沒有什麼特別的),我想添加評論給他們每個人。我只想使用一個評論模型。它值得嗎?或者我應該使用PostComment,ProjectComment等?兩種方式的優點/缺點?
謝謝!
是否sqlalchemy有類似django的GenericForeignKey?是否使用通用的外國領域?sqlalchemy通用外鍵(如在Django的ORM)
我的問題是:我有幾個模型(例如,郵政,項目,空缺,沒有什麼特別的),我想添加評論給他們每個人。我只想使用一個評論模型。它值得嗎?或者我應該使用PostComment,ProjectComment等?兩種方式的優點/缺點?
謝謝!
我最常使用的最簡單的模式是,實際上每個關係都有單獨的註釋表。這起初看起來很可怕,但它不會產生任何額外的代碼,而不是使用任何其他方法 - 表格是自動創建的,並且使用Post.Comment
,Project.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)
我知道這可能是做這一個可怕的方式,但它是一個快速解決我。
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
謝謝!我不喜歡第一種模式 - 我認爲,它不是乾的。如果我想爲每條評論添加一些信息(可能是「編輯」標誌),我應該使用所有模型/表格。我正在考慮我的'Tag'模型的第二種模式。它可以同時鏈接到「Project」和「Post」。第三個似乎是我的'評論'所需要的。 ROR/Django似乎並不那麼簡單,所以我會「研究」它。 – krasulya
它完全乾燥。幹意味着,「不要重複自己」。如果你看看這個模式是如何工作的,那麼你根本就不會重複自己。只是因爲DB中有很多類似的表並不意味着你在重複自己;他們的創作是自動化的,還有一些像「編輯」(使用像Alembic這樣的工具)的新專欄。這是最節省時間和DBA的方法(因爲不同表格的存儲可以獨立配置)。這太糟糕了,我很難說服這些人。 – zzzeek
我喜歡你的第一個解決方案的概念,但我不清楚你如何實際執行它。你能舉個簡單的例子嗎? – aquavitae