2011-04-13 136 views
13

我已经搜索了周围的堆栈溢出来解答这个(可能很简单)的问题,但是我看到的大多数解决方案似乎过于复杂和难以理解。Django抽象基类的模型字段

我有一个模型“后”,这是一个抽象的基类。模型“公告”和“事件”从Post继承。

现在我正在保存其他模型中的事件和公告的相关列表。例如,我在另一个模型中有“removed_events”和“removed_announcements”字段。

但是,在我的项目中,“removed_events”和“removed_announcements”的处理方式完全相同。没有必要消除“删除的事件”和“删除的公告”之间的歧义。换句话说,跟踪“removed_posts”的字段就足够了。

我不知道如何(或者不能)创建一个字段“removed_posts”,因为Post是抽象的。然而,现在我觉得我在代码中重复自己(并且必须做很多混乱 - 一些检查来确定我正在看的帖子是一个事件还是一个公告,并将它添加到适当的位置删除字段)。

这里最好的选择是什么?我可以使帖子不是抽象的,但Post对象本身不应该创建,我不认为我可以在非抽象对象上强制执行此操作。

我对数据库的理解很薄弱,但我觉得Post非抽象会使数据库由于连接而变得复杂。这是一件大事吗?

最后,还有其他模型中的其他字段,我想将相当于event_list和announcement_list的东西压缩到post_list中,但这些字段确实需要进行消歧。我可以根据帖子类型来过滤post_list,但是对filter()的调用将比分别直接访问事件和公告列表要慢,是不是?这里有什么建议?

非常感谢您阅读本文。

回答

2

通用关系和外键是你的朋友在你的成功之路。定义一个通用的中间模型,然后另一端会得到一个多态模型的相关列表。它只比标准的m2m连接模型复杂一点,因为泛型端有两列,一个是ContentType(实际上是FK),另一个是实际链接模型实例的PK。您还可以使用标准FK参数来限制模型链接。 你会很快使用它。

(现在我得到一个实际的键盘来写,这里就有例子:)

class Post(models.Model): 
    class Meta: abstract = True 
    CONCRETE_CLASSES = ('announcement', 'event',) 
    removed_from = generic.GenericRelation('OwnerRemovedPost', 
     content_type_field='content_type', 
     object_id_field='post_id', 
    ) 

class Announcement(Post): pass 

class Event(Post): pass 

class Owner(models.Model): 

    # non-polymorphic m2m 
    added_events = models.ManyToManyField(Event, null=True) 

    # polymorphic m2m-like property 
    def removed_posts(self): 
     # can't use ManyToManyField with through. 
     # can't return a QuerySet b/c it would be a union. 
     return [i.post for i in self.removed_post_items.all()] 

    def removed_events(self): 
     # using Post's GenericRelation 
     return Event.objects.filter(removed_from__owner=self) 


class OwnerRemovedPost(models.Model): 
    content_type = models.ForeignKey(ContentType, 
     limit_choices_to={'name__in': Post.CONCRETE_CLASSES}, 
    ) 
    post_id = models.PositiveIntegerField() 
    post = generic.GenericForeignKey('content_type', 'post_id') 
    owner = models.ForeignKey(Owner, related_name='removed_post_items') 

    class Meta: 
     unique_together = (('content_type', 'post_id'),) # to fake FK constraint 

不能渗入到相关的集合像一个典型的多到很多,但与在Owner的适当方法,巧妙地使用具体班级的管理人员,你可以随心所欲。

+0

对于这样的解决方案,您可能只需要模型上的通知/事件的GenericForeignKey,而不需要使用中介。 – 2011-04-13 22:35:54

+0

取决于原始多态建模是否具有FK而不是m2m,为true。无论如何,当不再需要时就很难删除外键,而不仅仅是将m2m视为一个FK加一个反(一对一)的反转(软)。如果要使模型具有足够的通用性,则可以使用m2m或monkey修补,如显式的field.contribute_to_class()... – rewritten 2011-04-13 22:43:05

11

在Django中有两种模型子类 - 抽象基类;和多表继承。

抽象基类本身并不是自己使用的,也没有数据库表或任何形式的标识。它们只是缩短代码的一种方法,通过将代码为的通用字段集合分组,而不是在数据库中。

例如:

class Address(models.Model): 
    street = ... 
    city = ... 

    class Meta: 
     abstract = True 


class Employee(Address): 
    name = ... 

class Employer(Address): 
    employees = ... 
    company_name = ... 

这是一个人为的例子,但你可以看到,一个Employee不是Address,也不是一个Employer。它们都包含与地址有关的字段。这个例子中只有两个表。 EmployeeEmployer - 并且它们都包含Address的所有字段。雇主地址不能与数据库级的员工地址相比较 - 地址没有自己的密钥。

现在,使用多表继承(从地址中删除abstract = True),地址确实有自己拥有一个表。这将导致3个不同的表格; AddressEmployerEmployee。雇主和员工都将有一个唯一的外键(OneToOneField)返回到地址。

您现在可以引用一个地址,而不用担心它是什么类型的地址。

for address in Address.objects.all(): 
    try: 
     print address.employer 
    except Employer.DoesNotExist: # must have been an employee 
     print address.employee 

每个地址都会有自己的主键,这意味着它可以被保存在第四台自身:

class FakeAddresses(models.Model): 
    address = models.ForeignKey(Address) 
    note = ... 

多表继承是你以后,如果你需要使用Post类型的对象,而不必担心它是什么类型的Post。如果访问子类中的任何Post域,将会有一个连接的开销;但开销很小。这是一个独特的索引连接,应该是非常快速的。

只要确保,如果您需要访问Post,那么您在查询集上使用select_related

Events.objects.select_related(depth=1) 

这将避免额外的查询来获取父数据,但会导致发生连接。所以只有使用选择相关如果你需要邮政。

两个最后的笔记;如果一篇帖子既可以是一个公告又可以是一个活动,那么你需要做传统的事情,并通过ForeignKey链接到发布。在这种情况下,子类化不会起作用。

最后一件事是,如果连接在父代和子代之间的性能至关重要,则应该使用抽象继承;并使用泛型关系来引用表中的抽象帖子,这对性能非常关键。

通用的关系本质上存储的数据是这样的:

class GenericRelation(models.Model): 
    model = ... 
    model_key = ... 


DeletedPosts(models.Model): 
    post = models.ForeignKey(GenericRelation) 

这将是一个复杂得多的SQL(Django的帮助你与)的加入,但它也不太高性能的不是一个简单的OneToOne加盟。如果OneToOne加入,你只需要沿着这条路线走下去就可能严重损害你的应用程序的性能。

+0

感谢您的支持 - 第一种方案是ForeignKey引用多表继承树的基础,这正是我希望使用的。它给出了一个错误:“错误:一个或多个模型未验证:ordering.orderinvoicelog:'partner'与模型Partner有关系,该模型未安装或者是抽象的。”我通过更改外键关系声明来直接引用基类,而不是通过字符串中的名称来修复这个问题。即使用ForeignKey(MyClass)而不是ForeignKey('MyClass')。后者在其他情况下工作,但不在这里。 – 2011-09-22 18:05:09

+0

如果我有4个子类,该怎么办? try catch块的外观如何? – iankit 2013-07-27 13:26:50

+0

@iankit寻找可以帮助你的东西,比如https://github.com/carljm/django-model-utils – 2013-07-27 14:03:55