Inheriting specific parent model attributes in foreignkey->'self' relationship in Django
I have a Django model:
class Foo(models.Model):
name = models.CharField(unique=True)
attribute1 = models.FloatField(null=True, blank=True)
attribute2 = models.F开发者_JAVA百科loatField(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)
I'd like that when inherit
is not null/blank, that attribute1 and attribute2 etc. are inherited from the parent object inherit
so that when I access the attributes I get the values of the parent. I dont care about setting values in the child.
I thought about using model methods eg.:
_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)
But it seems ugly since I have about 10 attributes. Is there a better way to do this?
Perhaps the __getattr__
and __setattr__
are a good choice here.
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)
Disclaimer: I didn't try running this
You could use a Descriptor:
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)
the Model would look like this:
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')
This could probably be enhanced with a Metaclass that automatically hides the model fields behind a descriptor.
I had a similar problem. The goal was to have a class that allowed for certain fields to take on the value of the group. The code below introduces these features to your model:
- If you want to make a field inherit the group value, you only have to create another BooleanField with the name "inherit_group_".
- If you set the value of an inheritable field, the value of "inherit_group_" is automatically set to False (since you've set an explicit value).
- If you set "inherit_group_" to True, the value of the associated field is returned back to the group's value.
- Everything else is handled including saving and initialization.
One warning - if you're using a ModelForm with this Model, you'll need to override the save function so that all the "inherit_group_ attributes are set last since, when the associated location fields are set, "inherit_group_" will be set to False as described above. That code is below as well. This whole thing is probably best handled by creating new classes - InheritableModel and InheritableModelForm. I was too lazy to do that though :).
Here's the code for the Model:
_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)]
Here's the the code for the 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
精彩评论