在Django中外键'self'关系中继承特定父模型属性
我有一个Django模型:
class Foo(models.Model):
name = models.CharField(unique=True)
attribute1 = models.FloatField(null=True, blank=True)
attribute2 = models.FloatField(null=True, blank=True)
attribute3 = models.BooleanField(null=True, blank=True)
attribute4 = models.CharField(null=True, blank=True)
inherit = models.ForeignKey('self', related_name='children', null=True, blank=True)
我希望当inherit
不为空时,属性attribute1、attribute2等等能够从父对象inherit
那里继承,这样当我访问这些属性时,就能得到父对象的值。我并不关心在子对象中设置这些值。
我考虑过使用模型方法,比如:
_attribute1 = models.FloatField(null=True, blank=True)
get_attribute1(self):
if self.inherit:
return self.inherit._attribute1
else:
return self._attribute1
set_attribute1(self, value):
if not self.inherit:
self._attribute1 = value
attribute1 = property(get_attribute1, set_attribute1)
但这样看起来不太好,因为我大约有10个属性。有没有更好的方法来实现这个?
3 个回答
0
我之前也遇到过类似的问题。我的目标是创建一个类,让某些字段可以继承组的值。下面的代码为你的模型引入了这些功能:
- 如果你想让某个字段继承组的值,只需要创建一个名为 "inherit_group_" 的布尔字段。
- 当你给一个可以继承的字段设置值时,"inherit_group_" 的值会自动变成 False(因为你已经给它设置了明确的值)。
- 如果你把 "inherit_group_" 设置为 True,那么这个字段的值就会恢复为组的值。
- 其他的事情,比如保存和初始化,都会自动处理。
有一点需要注意 - 如果你在使用这个模型的 ModelForm,你需要重写保存函数,这样所有的 "inherit_group_" 属性会最后被设置。因为当相关的地点字段被设置时,"inherit_group_" 会像上面说的那样被设置为 False。下面也有这段代码。其实最好是通过创建新的类 - InheritableModel 和 InheritableModelForm 来处理这一切。不过我当时太懒了,没有这样做 :)。
这是模型的代码:
_inheritable_fields = []
def __init__(self, *args, **kwargs):
super(Location, self).__init__(*args, **kwargs)
# Change iheritable field values to group value if current value is
# None
self._inheritable_fields = [
fname for fname in dir(self)
if hasattr(self, 'inherit_group_%s' % fname)]
# Make sure that all fields are in the correct state given the
# inherit_group values
[setattr(self, 'inherit_group_%s' % fname, getattr(self,
'inherit_group_%s' % fname))
for fname in self._inheritable_fields]
def __setattr__(self, name, val):
super(Location, self).__setattr__(name, val)
if name == "group" and val:
# A new group was specified. Update all fields that are currently
# inheriting from the group
[models.Model.__setattr__(self, fname, getattr(self.group, fname))
for fname in self._inheritable_fields
if getattr(self, 'inherit_group_%s' % fname)]
elif name in self._inheritable_fields:
# An inheritable field value changed. Update its inheritance state
models.Model.__setattr__(self, 'inherit_group_%s' % name, False)
elif name.startswith('inherit_group_'):
field_name = re.sub('^inherit_group_', '', name)
if val and field_name in self._inheritable_fields:
# An inheritance state (e.g., inherit_group_name) was changed.
# Change value back to group value
if hasattr(self, 'group'):
models.Model.__setattr__(self, field_name,
getattr(self.group, field_name))
else:
models.Model.__setattr__(self, field_name, None)
def save(self, *args, **kwargs):
# Set all fields using the inherited value to None for DB storage.
val_from_group = [
fname for fname in self._inheritable_fields
if getattr(self, 'inherit_group_%s' % fname)]
[models.Model.__setattr__(self, fname, None) for fname in val_from_group]
super(Location, self).save(*args, **kwargs)
# Return the fields changed above back to the group values.
[models.Model.__setattr__(self, fname, getattr(self.group, fname))
for fname in self._inheritable_fields
if getattr(self, 'inherit_group_%s' % fname)]
这是 ModelForm 的代码:
def save(self, commit=True):
location = super(LocationForm, self).save(commit=False)
# location.inherit_group_x has to be set last as it'll be set to
# False when it's associated field is set
[setattr(location, 'inherit_group_%s' % fname,
self.cleaned_data['inherit_group_%s' % fname])
for fname in location._inheritable_fields]
if commit:
location = super(LocationForm, self).save()
return location
0
你可以使用一个描述符:
class InheritedAttribute(object):
def __init__(self, name):
self.attname = '_' + name
def __get__(self, instance, owner):
if instance.inherit:
return getattr(instance.inherit, self.attname)
else:
return getattr(instance, self.attname)
def __set__(self, instance, value):
setattr(instance, self.attname, value)
模型看起来会是这样的:
class Foo(models.Model)
name = models.CharField(unique=True)
_attribute1 = models.FloatField(null=True, blank=True)
_attribute2 = models.FloatField(null=True, blank=True)
_attribute3 = models.BooleanField(null=True, blank=True)
_attribute4 = models.CharField(null=True, blank=True)
inherit = models.ForeignKey('self', related_name='children', null=True, blank=True)
attribute1 = InheritedAttribute('attribute1')
attribute2 = InheritedAttribute('attribute2')
attribute3 = InheritedAttribute('attribute3')
attribute1 = InheritedAttribute('attribute4')
这可能还可以通过一个元类来增强,这样可以自动把模型的字段隐藏在描述符后面。
2
也许使用 __getattr__
和 __setattr__
是个不错的选择。
class Foo(models.Model):
name = models.CharField(unique=True)
inherit = models.ForeignKey('self', related_name='children', null=True, blank=True)
_attribute1 = models.FloatField(null=True, blank=True)
_attribute2 = models.FloatField(null=True, blank=True)
_attribute3 = models.BooleanField(null=True, blank=True)
_attribute4 = models.CharField(null=True, blank=True)
def __getattr__(self, name):
if self.inherit and hasattr(self.inherit, name):
return getattr(self.inherit, name, None)
elif hasattr(self, '_'+name):
return getattr(self, '_'+name, None)
return super(Foo, self).__getattr__(name)
def __setattr__(self, name, value):
if self.inherit and hasattr(self.inherit, name):
return setattr(self.inherit, name, value)
elif hasattr(self, '_'+name):
return self.__dict__[name] = value
return super(Foo, self).__setattr__(name, value)
免责声明:我没有尝试运行这个