25

Back in the days of South migrations, if you wanted to create a custom model field that extended a Django field's functionality, you could tell South to use the introspection rules of the parent class like so:

from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^myapp\.stuff\.fields\.SomeNewField"])

Now that migrations have been moved to Django, is there a non-South equivalent of the above? Is an equivalent even needed anymore, or is the new migration stuff smart enough to just figure it out on its own?

Troy
  • 21,172
  • 20
  • 74
  • 103
  • Ever find an answer to this? – Dan Passaro Apr 27 '15 at 14:27
  • 3
    When I upgraded to Django 1.7, I removed the call to `add_introspection_rules()` and things seem to "just work", at least for the custom fields that we are using. There may still be cases where an equivalent of `add_introspection_rules()` is needed, but I haven't found an equivalent, and I haven't come across any specific scenarios, yet, that *need* an equivalent. – Troy Apr 27 '15 at 17:18
  • 2
    Running into this issue as well. It seems like overriding `deconstruct()` will get you through most issues, but I'm having a problem with Django being unable to serialize non top-level functions. – dursk Jun 24 '15 at 15:16
  • `deconstruct` is what corresponds to `add_introspection_rules`; what do you expect from an answer [that is not already covered by the documentation](https://docs.djangoproject.com/en/1.8/howto/custom-model-fields/#custom-field-deconstruct-method)? – Phillip Jul 01 '15 at 06:32
  • @mattm The introduction of `__qualname__` in python 3 solved most of these issues, though not for lambdas and functions in ``. On python 2 there is not much we can do afaik, so you'll have to stick with top-level functions. – knbk Jul 01 '15 at 21:59
  • @knbk Using the pattern described here https://code.djangoproject.com/ticket/22999 got the job done for me. – dursk Jul 02 '15 at 15:31
  • @mattm That's a good solution. Just keep in mind this `deconstruct` method is different from the one for `Field` - it should not return a `name`, but just `(path, args, kwargs)`. – knbk Jul 02 '15 at 16:43

1 Answers1

8

As Phillip mentions in the comments, deconstruct() is the official way to handle custom fields in django migrations.

To go on to complete the request for clarification... It would appear that there are already a couple of examples of code out there written to handle both. For example, this excerpt (to handle the on parameter for ExclusiveBooleanField) is taken from django-exclusivebooleanfield:

from django.db import models, transaction
from django.db.models import Q

from six import string_types
from six.moves import reduce


try:
    transaction_context = transaction.atomic
except AttributeError:
    transaction_context = transaction.commit_on_success


class ExclusiveBooleanField(models.BooleanField):
    """
    Usage:

    class MyModel(models.Model):
        the_one = ExclusiveBooleanField()


    class MyModel(models.Model):
        field_1 = ForeignKey()
        field_2 = CharField()
        the_one = ExclusiveBooleanField(on=('field_1', 'field_2'))
        # `on` is a bit like a unique constraint, value of field
        # is only exclusive for rows with same value of the on fields
    """
    def __init__(self, on=None, *args, **kwargs):
        if isinstance(on, string_types):
            on = (on, )
        self._on_fields = on or ()
        super(ExclusiveBooleanField, self).__init__(*args, **kwargs)

    def contribute_to_class(self, cls, name):
        super(ExclusiveBooleanField, self).contribute_to_class(cls, name)
        models.signals.class_prepared.connect(self._replace_save, sender=cls)

    def deconstruct(self):
        """
        to support Django 1.7 migrations, see also the add_introspection_rules
        section at bottom of this file for South + earlier Django versions
        """
        name, path, args, kwargs = super(
            ExclusiveBooleanField, self).deconstruct()
        if self._on_fields:
            kwargs['on'] = self._on_fields
        return name, path, args, kwargs

    def _replace_save(self, sender, **kwargs):
        old_save = sender.save
        field_name = self.name
        on_fields = self._on_fields

        def new_save(self, *args, **kwargs):
            def reducer(left, right):
                return left & Q(**{right: getattr(self, right)})

            with transaction_context():
                if getattr(self, field_name) is True:
                    f_args = reduce(reducer, on_fields, Q())
                    u_args = {field_name: False}
                    sender._default_manager.filter(f_args).update(**u_args)
                old_save(self, *args, **kwargs)
        new_save.alters_data = True

        sender.save = new_save


try:
    from south.modelsinspector import add_introspection_rules
    add_introspection_rules(
        rules=[
            (
                (ExclusiveBooleanField,),
                [],
                {"on": ["_on_fields", {"default": tuple()}]},
            )
        ],
        patterns=[
            'exclusivebooleanfield\.fields\.ExclusiveBooleanField',
        ]
    )
except ImportError:
    pass
Peter Brittain
  • 13,489
  • 3
  • 41
  • 57
  • @mlissner: I think this covers the formal external references and example that you asked for. – Peter Brittain Jul 01 '15 at 12:14
  • Custom **deconstruct()** method **without using super** -- http://stackoverflow.com/questions/31953802/upgrading-from-django-1-6-to-1-7-getting-callable-is-not-serialize-when-running – storm_m2138 Oct 11 '16 at 21:44