【问题标题】:How to safely inherit from django models.Model class?如何安全地从 django models.Model 类继承?
【发布时间】:2020-04-28 09:14:42
【问题描述】:

我有以下数据库结构:

class Word(models.Model):
    original = models.CharField(max_length=40)
    translation = models.CharField(max_length=40)

class Verb(Word):
    group = models.IntegerField(default=1)

在我看来,我需要先创建一个Word 对象,确定其组后(取决于Word.original),创建一个Verb 对象,并保存。

Word 类继承并将对象另存为Verb 的最佳方法是什么?

我尝试了几种解决方案:

1) 修改Verb 中的__init__ 方法:

class Verb(Word):
    group = models.IntegerField(default=1)

    def __init__(self, base_word):
        self.original = base_word.original
        self.translation = base_word.translation

这会导致很多错误,因为我覆盖了 django 的内置 __init__ 方法。

2) 使用super().__init__():

class Verb(Word):
    group = models.IntegerField(default=1)

    def __init__(self, base_word):
        super().__init__()
        self.original = base_word.original
        self.translation = base_word.translation

显然,这很好用:

base_word = Word()
new_verb = Verb(base_word)
new_verb.save()

但是有两个问题:

  1. 尝试查看 django 管理页面中的对象时会导致错误:
__init__() takes 2 positional arguments but 9 were given
  1. 这还是代码太多,感觉不太对劲。我仍然需要这样写:
self.original = base_word.original
self.translation = base_word.translation

在每个子类中。这只是一个例子。在实际项目中,我有更多的领域。我想有一个更优雅的解决方案。

【问题讨论】:

  • 为什么不将确定是否创建Verb 转移到表单。请不要覆盖__init__。 django 文档明确表示强烈建议不要覆盖__init__,尤其是not更改__init__ 的签名。
  • 我可以提供更多细节。 Word 有 4 个子类:VerbNounAdjectivePronoun。他们有很多共同的领域,所以我把他们放在Word。首先,我完成了Word 中的所有这些字段,然后我创建了 4 个子类之一,而哪一个由表单确定。否则,在我看来,我将不得不编写相同的代码 4 次。
  • 但据我所知,您可以只构造一个Verb(original='foo', translation='bar', group=42),Django 将处理此问题并在WordVerb 的表中记录并正确链接.
  • 这正是我想要避免的。它有效,但同样,我必须为视图中的每个类填写相同的字段 4 次。
  • 但是子类化在这里看起来不像是正确的工具。您可能想使用OneToOneField 制作模型,或者对abstract=True 模型进行子类化。

标签: python django database django-models django-views


【解决方案1】:

覆盖__init__ 不是正确的方法。 Django 模型执行了大量的幕后工作,覆盖 __init__ 可能会与之发生冲突,除非您按照以下规则以安全的方式进行:

  • 请勿更改 __init__ 的签名——这意味着您不应更改该方法接受的参数。
  • 在调用super().__init__(*args, **kwargs) 方法之后执行自定义__init__ 逻辑。

在这种特殊情况下,您可以使用django's proxy model inheritance features

VERB = "V"
NOUN = "N"
# ...
WORD_TYPE_CHOICES = (
    (VERB, "Verb"),
    (NOUN, "Noun"),
    # ...
)

class Word(models.Model):
    original = models.CharField(max_length=40)
    translation = models.CharField(max_length=40)

    WORD_TYPE = ""  # This is overridden in subclasses

    word_type = models.CharField(
        max_length=1,
        blank=True,
        editable=False,  # So that the word type isn't editable through the admin.
        choices=WORD_TYPE_CHOICES,
        default=WORD_TYPE,  # Defaults to an empty string
    )

    def __init__(self, *args, **kwargs):
        # NOTE: I'm not 100% positive that this is required, but since we're not
        # altering the signature of the __init__ method, performing the
        # assignment of the word_type field is safe.
        super().__init__(*args, **kwargs)
        self.word_type = self.WORD_TYPE

    def __str__(self):
        return self.original

    def save(self, *args, **kwargs):
        # In the save method, we can force the subclasses to self-assign
        # their word types.
        if not self.word_type:
            self.word_type = self.WORD_TYPE
        super().save(*args, **kwargs)

class WordTypeManager(models.Manager):
    """ This manager class filters the model's queryset so that only the
    specific word_type is returned.
    """
    def __init__(self, word_type, *args, **kwargs):
        """ The manager is initialized with the `word_type` for the proxy model. """
        self._word_type = word_type
        super().__init__(*args, **kwargs)

    def get_queryset(self):
        return super().get_queryset().filter(word_type=self._word_type)

class Verb(Word):
    # Here we can force the word_type for this proxy model, and set the default
    # manager to filter for verbs only.
    WORD_TYPE = VERB
    objects = WordTypeManager(WORD_TYPE)

    class Meta:
        proxy = True

class Noun(Word):
    WORD_TYPE = NOUN
    objects = WordTypeManager(WORD_TYPE)

    class Meta:
        proxy = True

现在我们可以将不同的词类型视为单独的模型,或者通过Word 模型一起访问所有这些词。

>>> noun = Noun.objects.create(original="name", translation="nombre")
>>> verb = Verb(original="write", translation="escribir")
>>> verb.save()

# Select all Words regardless of their word_type
>>> Word.objects.values_list("word_type", "original")
<QuerySet [('N', 'name'), ('V', 'write')]>

# Select the word_type based on the model class used
>>> Noun.objects.all()
<QuerySet [<Noun: name>]>
>>> Verb.objects.all()
<QuerySet [<Verb: write>]>

这也适用于 admin.ModelAdmin 类。

@admin.register(Word)
class WordAdmin(admin.ModelAdmin):
    """ This will show all words, regardless of their `word_type`. """
    list_display = ["word_type", "original", "translation"]

@admin.register(Noun)
class NounAdmin(WordAdmin):
    """ This will only show `Noun` instances, and inherit any other config from
    WordAdmin.
    """

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-03
    • 2018-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多