Hi, I have a custom model field class whose Python representation is an instance of a custom Python class (sort of like the HandField
example in the docs here). The custom class used by the custom field type really only exists to keep track of some internal state - it’s a wrapper for values of a type that users actually care about.
Right now, the way users manipulate the model attributes associated with the custom field is via a property getter/setter on the instance of the custom class, like so:
class ThingUserCaresAbout():
def __init__(self, x: int) -> None:
self.x = x
class ValueWrapperUserDoesntCareAbout:
def __init__(self, initial_value: Optional[ThingUserCaresAbout] = None) -> None:
self._value = initial_value
# also does some internal setup stuff
@property
def value(self) -> Optional[ThingUserCaresAbout]:
# do some logic related to the state here
return self._value
@value.setter
def set_value(self, new_value: Optional[ThingUserCaresAbout]) -> None:
# do some logic related to the state here
self._value = new_value
The custom field and interaction with the model looks something like this:
class SomeCustomField(Field):
# (Implementation of to_python, from_db_value, get_db_prep_value, etc.
# to serialize and deserialize DB and Python state into and out of
# ValueWrapperUserDoesntCareAbout objects that wrap ThingUserCaresAbout objects)
class ExampleModel(Model):
something = SomeCustomField()
example_instance = ExampleModel()
example_instance.something # => ValueWrapperUserDoesntCareAbout object
example_instance.something.value # => None
example_instance.something.value = ThingUserCaresAbout(5)
I don’t like the way that that this exposes the ValueWrapperUserDoesntCareAbout
object and requires the use of the value
property getter and setter.
I’m wondering if it’s possible to have the custom model field to use Python’s data descriptors as the model attributes, instead of exposing this ValueWrapperUserDoesntCareAbout
class. I’m thinking something like:
- The state that’s currently stored in
ValueWrapperUserDoesntCareAbout
could be stored in a_some_custom_field_data
dict on the model instance- Keys would be the field names of the
SomeCustomField
fields on the model, and values would contain the state that’s currently stored in theValueWrapperUserDoesntCareAbout
class
- Keys would be the field names of the
- The model attributes themselves (like the
something
attribute of anExampleModel
instance above) would be instances of a new descriptor class- This descriptor would have
__get__
and__set__
methods that read or manipulate the state of the_some_custom_field_data
dict on the model instance and return or store aThingUserCaresAbout
instance.
- This descriptor would have
Based on what I’ve seen of contribute_to_class
I think it should be possible to at least set up the descriptors and the data storage, but this is deeper in the weeds of the ORM internals than I’m used to, and it’s certainly not part of the publicly documented Django custom field API. I haven’t found any third-party examples that do anything like this, either.
So, before I pursue this further:
- Has anyone here done something like this before? Are there existing custom field types that do this kind of thing that I can refer to?
- Is there existing Django functionality that this could break?
- When Django calls methods on the custom field class such as
get_db_prep_value
, will it pass the value returned by the descriptor’s__get__
, or will it somehow pass the descriptor instance itself? - Same question for
from_db_value
and analogous custom field methods - when Django sets the model instance attribute to the deserialized value from the database, will it call the descriptor’s__set__
, or will it do something else?