15

After upgrading from Django 1.9 to 1.10, I've experienced a change in behaviour with a field provided by the django-geolocation package.

This is the change that was made for 1.10 compatibility that broke the behaviour: https://github.com/philippbosch/django-geoposition/commit/689ff1651a858d81b2d82ac02625aae8a125b9c9

Previously, if you initialized a model with a GeopositionField, and then immediately accessed that field, you would get back a Geoposition object. Now you just get back the string value that you provided at initialization.

How do you achieve the same behaviour with Django 1.10? Is there another method like from_db_value that needs to be overridden to call to_python?

Acorn
  • 49,061
  • 27
  • 133
  • 172

1 Answers1

10

After lots of digging it turns out that in 1.8 the behaviour of custom fields was changed in such a way that to_python is no longer called on assignment to a field.

https://docs.djangoproject.com/en/1.10/releases/1.8/#subfieldbase

The new approach doesn’t call the to_python() method on assignment as was the case with SubfieldBase. If you need that behavior, reimplement the Creator class from Django’s source code in your project.

Here's a Django ticket with some more discussion on this change: https://code.djangoproject.com/ticket/26807

So in order to retain the old behaviour you need to do something like this:

class CastOnAssignDescriptor(object):
    """
    A property descriptor which ensures that `field.to_python()` is called on _every_ assignment to the field.
    This used to be provided by the `django.db.models.subclassing.Creator` class, which in turn
    was used by the deprecated-in-Django-1.10 `SubfieldBase` class, hence the reimplementation here.
    """

    def __init__(self, field):
        self.field = field

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        return obj.__dict__[self.field.name]

    def __set__(self, obj, value):
        obj.__dict__[self.field.name] = self.field.to_python(value)

And then add this to the custom field:

def contribute_to_class(self, cls, name):
    super(MyField, self).contribute_to_class(cls, name)
    setattr(cls, name, CastOnAssignDescriptor(self))

Solution was taken from this pull request: https://github.com/hzdg/django-enumfields/pull/61

Acorn
  • 49,061
  • 27
  • 133
  • 172