﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
22951	Adding 'deconstruct' method breaks serialization of type	Sam Hartsfield <samh.public+django@…>	jambonrose	"A custom field may take a type as as argument (for example, an Enum type). Normally, class references are one of the things the migrations framework can serialize by default ([https://docs.djangoproject.com/en/dev/topics/migrations/#serializing-values]). However, we probably also want to serialize the value, otherwise we get an error (see `ValueEnum` in the example).

To serialize the value, we add a `deconstruct` method (in the example of `ValueEnum2`, this uses the `deconstructible` decorator). I would expect that, after adding this, the field would work. However, '''`django.db.migrations.autodetector.deep_deconstruct` tries to call `deconstruct` on the class.'''

`ValueEnum3` is an example of a workaround, which should probably not be required.

I would expect Django to either check whether something is a type before calling `deconstruct`, or to not call `deconstruct` if it is an instance method (e.g. you might want to be able to also add a `deconstruct` classmethod to customize deconstruction of the class).

{{{
#!python
# Requires Python 3.4 or the 'enum34' package
import enum
import types
from django.db import models
from django.utils.deconstruct import deconstructible
from django.utils.six import with_metaclass


class SimpleEnumField(with_metaclass(models.SubfieldBase, models.IntegerField)):
    def __init__(self, enum_type, **kwargs):
        self.enum = enum_type
        choices = [(e.value, e.name) for e in enum_type]
        super(SimpleEnumField, self).__init__(choices=choices, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super(SimpleEnumField, self).deconstruct()
        kwargs['enum_type'] = self.enum
        kwargs.pop('choices', None)
        return name, path, args, kwargs

    def get_prep_value(self, value):
        return value.value

    def to_python(self, value):
        return self.enum(value)


class ValueEnum(enum.Enum):
    """"""
    Django will be able to serialize the type, but not the instance::

        ValueError: Cannot serialize: <ValueEnum.A: 0>

    """"""
    A = 0
    B = 1


@deconstructible
class ValueEnum2(enum.Enum):
    """"""
    The deconstructible decorator allows Django to serialize the instances,
    but breaks serialization of the type--it will try to call deconstruct
    on the type, but it will fail because it is an instance method::

        TypeError: deconstruct() missing 1 required positional argument: 'obj'

    """"""
    A = 0
    B = 1


def deconstruct_enum(self):
    return (
        '%s.%s' % (self.__class__.__module__, self.__class__.__name__),
        (self.value,),
        {}
    )


class ValueEnum3(enum.Enum):
    """"""
    Workaround: if we add the deconstruct method to the instances, but not
    to the type, then Django will do the right thing.
    """"""
    A = 0
    B = 1

    def __init__(self, value):
        self.deconstruct = types.MethodType(deconstruct_enum, self)


class Foo(models.Model):
    # This field works
    field1 = SimpleEnumField(ValueEnum)

    # ValueError: Cannot serialize: <ValueEnum.A: 0>
    # field2 = SimpleEnumField(ValueEnum, default=ValueEnum.A)

    # TypeError: deconstruct() missing 1 required positional argument: 'obj'
    # field3 = SimpleEnumField(ValueEnum2, default=ValueEnum2.A)

    field4 = SimpleEnumField(ValueEnum3, default=ValueEnum3.A)
}}}
"	Bug	closed	Migrations	1.7-rc-1	Normal	fixed			Accepted	1	0	0	0	1	0
