[英]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()
但是有两个问题:
__init__() takes 2 positional arguments but 9 were given
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.