Как создать пользовательскую модель Поле для вызова to_python, когда к полю открывается сразу после инициализации (не загружается из БД) в Django >= 1.10?

После обновления с Django 1.9 до 1.10 я испытал изменение в поведении с полем, предоставленным пакетом django-geolocation.

Это изменение, которое было сделано для совместимости 1.10, которая нарушила поведение: https://github.com/philippbosch/django-geoposition/commit/689ff1651a858d81b2d82ac02625aae8a125b9c9

Ранее, если вы инициализировали модель с помощью GeopositionField, а затем сразу же получили доступ к этому полю, вы вернете объект Geoposition. Теперь вы просто возвращаете строковое значение, которое вы указали при инициализации.

Как вы достигаете такого же поведения с Django 1.10? Есть ли другой метод, например from_db_value, который нужно переопределить для вызова to_python?

Ответы

Ответ 1

После многократного поиска выяснилось, что в 1.8 поведение пользовательских полей было изменено таким образом, что to_python больше не вызывается при назначении в поле.

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

Новый подход не вызывает метод to_python() при назначении, как в случае с SubfieldBase. Если вам нужно это поведение, переопределите класс Creator из исходного кода Djangos в вашем проекте.

Здесь билет Django с еще одним обсуждением этого изменения: https://code.djangoproject.com/ticket/26807

Итак, чтобы сохранить прежнее поведение, вам нужно сделать что-то вроде этого:

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)

Затем добавьте это в настраиваемое поле:

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

Решение взято из этого запроса на растяжение: https://github.com/hzdg/django-enumfields/pull/61