Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I make a custom model Field call to_python when the field is accessed immediately after initialization (not loaded from DB) in Django >=1.10?

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?

like image 379
Acorn Avatar asked Sep 08 '16 13:09

Acorn


People also ask

How do I create a custom field in Django?

Look at the existing Django fields (in django/db/models/fields/__init__.py ) for inspiration. Try to find a field that's similar to what you want and extend it a little bit, instead of creating an entirely new field from scratch. Put a __str__() method on the class you're wrapping up as a field.

How do I add a field to an existing model?

To answer your question, with the new migration introduced in Django 1.7, in order to add a new field to a model you can simply add that field to your model and initialize migrations with ./manage.py makemigrations and then run ./manage.py migrate and the new field will be added to your DB.

How can we make field required in Django?

Let's try to use required via Django Web application we created, visit http://localhost:8000/ and try to input the value based on option or validation applied on the Field. Hit submit. Hence Field is accepting the form even without any data in the geeks_field. This makes required=False implemented successfully.

How do I add a field to an existing model in Django?

Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py Select an option: Then go to the Django project root folder in a terminal and run the command makemigrations. It will generate a new migration .


1 Answers

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

like image 138
Acorn Avatar answered Oct 23 '22 03:10

Acorn