如何干燥我的模型

Posted

技术标签:

【中文标题】如何干燥我的模型【英文标题】:How to DRY up my models 【发布时间】:2014-05-08 05:50:43 【问题描述】:

我是一个制作问答网站。目前我有这样的模型

class Question(models.Model):
    title = models.CharField(max_length=150)
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    ...# some additional fields such as tags

class Answer(models.Model):
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    ...

class QuestionVote(models.Model):
    voter = models.ForeignKey(User)
    question = models.ForeignKey(Question)

#replicating what I did for QuestionVote
class AnswerVote(models.Model):
        voter = models.ForeignKey(User)
        question = models.ForeignKey(Question)

除了标题和标签外,问答模型相同。要向 Answers 添加投票功能,我必须将 QuestionVote 模型复制为 AnswerVote,并重复我为视图中的问题投票所做的一切。我看了一点模型继承,但如果我声明一个抽象基类并从中继承问答模型,那么我不能使用外键。那么避免这种重复的最佳方法是什么?

【问题讨论】:

@谁指出,我已经纠正了复制粘贴错误 【参考方案1】:

您可以反过来使用一对一关系:

class Vote(models.Model):
    voter = models.ForeignKey(User)
    ...# some additional fields

class Question(models.Model):
    title = models.CharField(max_length=150)
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    vote = models.OneToOneField(Vote)
    ...# some additional fields such as tags

class Answer(models.Model):
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    vote = models.OneToOneField(Vote)
    ...

【讨论】:

目前为止我最喜欢这种方法。这样我也可以使用继承,外键将进入继承的模型。我认为哪个应该有效。【参考方案2】:

我个人觉得 DRY 很重要,所以我愿意牺牲一些优雅来实现它。例如,在这种情况下,我可能会这样做:

class QuestionOrAnswer(models.Model):
    is_question = models.BooleanField()
    title = models.CharField(max_length=150)
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    question_specific_field = ...

    def clean(self):
        # Make sure that question_specific_field is set only if is_question is true.

class Vote(models.Model):
    target = models.ForeignKey(QuestionOrAnswer)
    ...

【讨论】:

如果 Answer 具有 Question 的外键(它实际上应该)会发生什么? 您可以使用自引用外键:question = models.ForeignKey('self')。见***.com/questions/15285626/…【参考方案3】:

看看abstract base classes

您可以将公共字段/功能放入其中(submitterdetail 等),然后在您的 QuestionAnswer 模型中继承它

编辑:

基于其他答案和我对您的问题的更多了解,我正在更改我的答案。您目前在“干”和“良好的可读性/可维护性”之间徘徊,并且不同的开发人员会在不同的网站上出现,所以您可能只需要选择您最喜欢的一个。

关于那张纸条,我是这样表述的:

##############################
# Question and Answer Models #
##############################

class QuestionAnswerBase(models.Model): # Choose a better name :)
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    ... # More common fields here

    class Meta:
        abstract = True


class Question(QuestionAnswerBase):
    title = models.CharField(max_length=150)
    ...# some additional fields such as tags etc


class Answer(QuestionAnswerBase):
    ... # No extra fields needed but you'll prob want a custom __unicode__ method etc


###############
# Vote Models #
###############

class VoteBase(models.Model):
    voter = models.ForeignKey(User)
    ... # Other shared fields etc

    class Meta:
        abstract = True

class AnswerVote(VoteBase):
    answer = models.ForeignKey(Answer)


class QuestionVote(VoteBase):
    question = models.ForeignKey(Question)

(在这种情况下,注释块是为了便于阅读。)

“但现在我有更多的模型!”

是的,你有,但它们中没有重复的字段,并且抽象意味着你有能力添加不同的行为——例如——QuestionVoteAnswerVote(假设你想要这个能力如果出现更好的Answer,则撤回AnswerVotes,但不希望能够撤回QuestionVotes)而不需要“这是X是Y还是Z?如果是Z就行在你编写的每个模型方法中都有 this else do this" 子句。

“看起来很丑!”

它既可读又显式,这意味着它是beautiful :)

“我想换一种方式!”

没关系,已经有其他很好的答案可以说明如何做到这一点。这只是我作为一个非常热衷于最佳实践和可读代码的人的建议。

【讨论】:

但是你不能将外键指向一个抽象类,因为抽象类没有与之相关的表。 @Selcuk 你是对的,我误读了要求。虽然默认情况下无法完成,但有一些很好的 django 包允许很多 DRYer 模型继承 + 外键关系,例如django-polymorphic。不过在这种情况下可能有点矫枉过正。

以上是关于如何干燥我的模型的主要内容,如果未能解决你的问题,请参考以下文章

如何在 jar 中增强 ebean 模型

rails:保持干燥,达到什么水平? [关闭]

使用嵌套资源干燥 Rails 视图

将 Django 模型验证错误传输到表单的惯用方式

计算机的网络参考模型

在运行时在 Django 中指定关系动词?