[英]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:但是有两个问题:
__init__() takes 2 positional arguments but 9 were given
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__
可能会与这些工作发生冲突,除非您按照以下规则以安全的方式执行此操作:
__init__
-- meaning you shouldn't change the arguments that the method accepts.__init__
的签名——这意味着你不应该改变方法接受的参数。__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.