简体   繁体   English

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

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

I have a following DB structure:我有以下数据库结构:

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

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

In my view, I need to create a Word object first, and after determination of its group (depending on Word.original ), create a Verb object, and save it.在我看来,我需要先创建一个Word对象,确定它的组后(取决于Word.original ),创建一个Verb对象,并保存它。

What is the best way to inherit from the Word class and save the object as Verb ?Word类继承并将对象另存为Verb的最佳方法是什么?

There are several solutions that I've tried:我尝试了几种解决方案:

1) Modification of the __init__ method in 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

This causes a lot of errors, since I'm overriding the django's built-in __init__ method.这会导致很多错误,因为我覆盖了 django 的内置__init__方法。

2) Using super().__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

Apparently, this works pretty well:显然,这很有效:

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

But there are two problems:但是有两个问题:

  1. It causes an error when trying to see the objects in django admin page:尝试查看 django 管理页面中的对象时会导致错误:
__init__() takes 2 positional arguments but 9 were given
  1. This is still too much code, it doesn't feel right.这还是代码太多,感觉不太对。 I still need to write this:我仍然需要这样写:
self.original = base_word.original
self.translation = base_word.translation

in every subclass.在每个子类中。 And this is just an example.这只是一个例子。 In real project, I have much more fields.在实际项目中,我有更多的领域。 I suppose there is a more elegant solution.我想有一个更优雅的解决方案。

Overriding __init__ is not the right way to do this.覆盖__init__不是执行此操作的正确方法。 Django models perform a lot of behind the scenes work, which overriding __init__ can conflict with, unless you do it in a safe way by following these rules: Django 模型执行了许多幕后工作,覆盖__init__可能会与这些工作发生冲突,除非您按照以下规则以安全的方式执行此操作:

  • Don't alter the signature of __init__ -- meaning you shouldn't change the arguments that the method accepts.不要改变__init__的签名——这意味着你不应该改变方法接受的参数。
  • Perform the custom __init__ logic after calling the super().__init__(*args, **kwargs) method.调用super().__init__(*args, **kwargs)方法执行自定义__init__逻辑。

In this particular case, you might use django's proxy model inheritance features .在这种特殊情况下,您可能会使用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

Now we can treat the different word types as if they were separate models, or access all of them together through the Word model.现在我们可以将不同的词类型视为单独的模型,或者通过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>]>

This works with admin.ModelAdmin classes too.这也适用于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