1

I have to create a class which instances must fit two conditions:

  • being an str subclass so that it can be passed to os.listdir()
  • being deconstructible so that the string does not appear as-is when django generates migrations, but as mailing.conf.StrConfRef('another string')

Here is what I tried:

class StrConfRef(str):

    def __new__(cls, name, within=None):
        value = globals()[name]
        if within:
            value = within.format(value)
        self = str.__new__(cls, value)
        self.name = name
        self.within = within
        return self

    def deconstruct(self):
        return ('{}.{}'.format(__name__, self.__class__.__name__), (self.name,),
                {'within': self.within})

The first point is respected os.listdir(StrConfRef(...)) works. However, it is still evaluated as a "standard string" in migrations. I checked out django.db.migrations.autodetector and noticed that when the code is executed, it StrConfRef instances reach this line (which is expected, and should mean that StrConfRef is properly deconstructed).

So I wonder why it appears as a string in my migrations, and not a mailing.conf.StrConfRef instance. And how to fulfill my conditions.

PS: If you wonder why I need this behavior, checkout this question.

PS2: I'm runnign Python 3.4 and Django 1.9.2

Community
  • 1
  • 1
Antoine Pinsard
  • 33,148
  • 8
  • 67
  • 87

1 Answers1

1

Unfortunately it looks like object that have a deconstruct() method don't have priority over str subclasses.

What you could could here is use the django.db.migrations.writer.SettingsReference class which looks like it has a priority over str.

What I suggest you do instead is create a custom field subclass that will default to your conf value. For example, your Campaign.prefix_subject could be an instance of this class:

class PrefixSubject(models.BooleanField):
    default_help_text = (
        'Wheter to prefix the subject with "{}" or not.' % conf.SUBJECT_PREFIX
    )

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('help_text', default_help_text)
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        if kwargs['help_text'] == self.default_help_text:
            kwargs.pop('help_text')
        return name, path, args, kwargs
Simon Charette
  • 5,009
  • 1
  • 25
  • 33
  • I checked out `SettingsReference`, unfortunately this would require the setting to be set, while I provide default settings to enable the host project not to worry about it. The other method you suggest is the method I originally wanted to avoid. However, if I have no other choice, I will use it to remove 'path' from FilePathField kwargs. – Antoine Pinsard Feb 26 '16 at 12:43
  • There has been work toward making the `MigrationWriter` more extensible (https://github.com/django/django/commit/4b1529e2cbee697e7e772b05ad5038edef22f38d) but no way to register a custom serializer yet. – Simon Charette Feb 26 '16 at 16:02