Add getters and setters to model fields
|Reported by:||Owned by:|
|Component:||Database layer (models, ORM)||Version:||master|
|Cc:||Taiping, jerf@…, gav@…, terrex2, ramusus@…, semente+djangoproject@…, erikrose, Chris Chambers, denilsonsa@…, Christopher Grebs, noah, inactivist@…||Triage Stage:||Accepted|
|Has patch:||yes||Needs documentation:||yes|
|Needs tests:||no||Patch needs improvement:||yes|
Whenever you have two distinct ways to update a variable, you introduce the opportunity to have bugs. This becomes increasingly true as the system grows in size.
It is often the case that when changing one field on an object, you want to be able to automatically run code of some kind; this is the same basic motivation behind the "property" builtin in Python. However, the obvious way of doing that in Django doesn't work:
class Something(models.Model): field = models.BooleanField(...) ... def set_field(self, value): # do something field = property(set_field)
The second field overrides the first, and in the process of constructing the model Django never gets a chance to see the models.BooleanField.
This patch adds a 'getter' and 'setter' attribute to all fields, which takes a string of a method to call when a field is retrieved or set. It turns out that it is fairly easy to add a property to the class during Django's initialization, at which point it has already retrieved the field information. This example from the enclosed tests shows the basics of its usage:
class GetSet(models.Model): has_getter = models.CharField(maxlength=20, getter='simple_getter') has_setter = models.CharField(maxlength=20, setter='simple_setter') has_both = models.CharField(maxlength=20, getter='simple_getter', setter='updater') updated_length_field = models.IntegerField(default=0) def simple_getter(self, value): return value + "_getter" def simple_setter(self, value): return value + "_setter" def updater(self, value): self.updated_length_field = len(value) return value
This defines a getter on
has_getter that returns a filtered value from the DB, a setter on
has_setter that processes the value to add "_setter" to it in all cases, and on
has_both we see a setter than implements the use case of updating another field when a property is set. (As is often the case, this is a trivial example; in my real code, for instance, the value being updated is actually in a related object.)
A getter receives as its argument the current "real" value (the one that either came from the database, object construction, or a prior setting of the value), and what the getter returns is actually what the user gets back from the attribute.
A setter receives as its argument the value that the user is setting the property to, and what it returns is what the property will actually be set to. The pattern for just using that as a hook is to return what is passed in after you've done whatever it is your hook does, as shown above.
These properties are only created for fields that have getters or setters, so the backwards-compatibility impact of this should be zero, and the performance impact should be a check for getters/setters per field once at startup, which is minimal. I'm a little less certain about exactly how these getters and setters will interact with all the various field types, but due to the way in which it hooks in it should, in theory, have minimal impact, because no Python code should fail to go through the property.
Getters and setters do not operate during the creation of the object, be it by retrieval from the database or creation by instantiating the class; this avoids a lot of tricky issues with property initialization order and double-application of some common setters, but will need to be documented.
I'd be happy to add the appropriate documentation but I do not see where to contribute that.
Change History (65)
comment:8 Changed 9 years ago by
|Owner:||changed from nobody to Marc Fargas|
|Status:||new → assigned|
|Summary:||[patch] Add getters and setters to model fields → Add getters and setters to model fields|
comment:25 Changed 8 years ago by
|Owner:||changed from Marc Fargas to Marty Alchin|
|Patch needs improvement:||set|
|Status:||assigned → new|
comment:39 Changed 7 years ago by
|Owner:||changed from Marty Alchin to Alex Gaynor|
|Status:||new → assigned|