16

In order to develop custom Django model fields, I'm reading the documentation.

I have already developed my custom field (which is almost equal to that of the example, HandField: a field mapped over a Python class... With the only difference that I inherit from models.CharField and not models.Field).

from external_library import ExternalClass

class ExternalClassField(models.CharField):
    description = "An ExternalClass field"

    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 14
        super(ExternalClassField, self).__init__(*args, **kwargs)

    def from_db_value(self, value, expression, connection, context):
        if value is None:
            return value
        return ExternalClass(value)

    def to_python(self, value):
        if isinstance(value, ExternalClass):
            return value

        if value is None:
            return value

        return ExternalClass(value)

    def get_prep_value(self, value):
        if value is None:
            return value

        if isinstance(value, ExternalClass):
            return value.to_string()

        return value

The field behave as expected. However, I'm stuck at this part of the documentation: the deconstruct() function.

In particular, what I don't understand is this:

  • What exactly is the purpose of the deconstruct function?
  • How come my field works perfectly well even without it (and even if I modify the init arguments)?
  • How and when is that Django calls the deconstruct function?

I don't want to blindly copy-paste code that I don't understand, but the documentation is not clear.

Saturnix
  • 10,130
  • 17
  • 64
  • 120

1 Answers1

31

The deconstruct() method is used to help perform model migrations that aren't able to automatically be handled by the system. Let's walk through a scenario where deconstruct would get called.

Let's say we had some model, and we added a custom field to it. The we try to migrate with python manage.py makemigrations.

We encounter the following error:

ValueError: Cannot serialize: Foo
There are some values Django cannot serialize into migration files.

It turns out that there's already a related ticket that's been filed with the Django Project, let's check it out.

ticket-issue

One of the core developers responded that this is intended behavior, because our field contains a callable.

ticket-resolution

So, we missed something in the documentation. There's a callable value being stored, and it can't be automatically migrated for some reason. What can we do?

Well, in addition to telling us about the ValueError, manage.py also gave us a useful link to the documentation:

docs-link

Once on that page, scroll down a bit, until we get to the section about serializing values.

Django can serialize the following:

  • ...
  • Anything with a custom deconstruct() method (see below)
  • ...

Well, let's see below:

You can let Django serialize your own custom class instances by giving the class a deconstruct() method. It takes no arguments, and should return a tuple of three things (path, args, kwargs):

  • path should be the Python path to the class, with the class name included as the last part (for example, myapp.custom_things.MyClass). If your class is not available at the top level of a module it is not serializable.
  • args should be a list of positional arguments to pass to your class’ init method. Everything in this list should itself be serializable.
  • kwargs should be a dict of keyword arguments to pass to your class’ init method. Every value should itself be serializable.

Note that the deconstruct() method works hand in hand with __eq__(), as stated by the documentation:

To prevent a new migration from being created each time makemigrations is run, you should also add a __eq__() method to the decorated class. This function will be called by Django’s migration framework to detect changes between states.

In my case, the mistake was adding parenthesis after a value that should not have been called, but in many cases you'll want to implement that deconstruct method for migrations. (Here's another useful link that has an example.)

Community
  • 1
  • 1
Moshe
  • 57,511
  • 78
  • 272
  • 425