繁体   English   中英

如何安全地从 django models.Model 类继承?

[英]How to safely inherit from django models.Model class?

我有以下数据库结构:

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

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

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

  • 不要改变__init__的签名——这意味着你不应该改变方法接受的参数。
  • 调用super().__init__(*args, **kwargs)方法执行自定义__init__逻辑。

在这种特殊情况下,您可能会使用django 的代理模型继承功能

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.
    """

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM