Opened 71 minutes ago

Last modified 49 minutes ago

#37304 assigned Bug

Incorrect migration generated when a JSONField have a local scope defined Decoder

Reported by: takuyozora Owned by: Yassin Bahri
Component: Migrations Version: 6.1
Severity: Normal Keywords: JSONField migration
Cc: takuyozora Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Hi,

I have a JSONField on one of my model with a custom JSONDecoder :

class UnitAbstract(models.Model):
    class Meta:
        abstract = True
    # ...
    base_stats = models.JSONField(default=rpg.stats.RPGBaseStats(
        pv={'maximum': 100},
        mana={'maximum': 0}
    ).toJSON(), encoder=RPGJSONEncoder, decoder=rpg.stats.RPGBaseStats.getJSONDecoder())

class Unit(UnitAbstract):
    pass

But the migration file generated by makemigration is incorrect (because of this part : decoder=terrarpg.rpg.mixins.DeconstructibleMixin.getJSONDecoder.<locals>.RPGJSONDecoder) :

class Migration(migrations.Migration):

    dependencies = [
        ('terrarpg', '0035_unitgroup_formation_rank_alter_unit_base_stats_and_more'),
    ]

    operations = [
        migrations.AlterField(
            model_name='unit',
            name='base_stats',
            field=models.JSONField(decoder=terrarpg.rpg.mixins.DeconstructibleMixin.getJSONDecoder.<locals>.RPGJSONDecoder, default=[
#...
], encoder=terrarpg.rpg.parsers.RPGJSONEncoder),
        ),

I assume the <locals> that produce the syntax error is here because my decoder is a dynamically generated class as you can see here :

class DeconstructibleMixin(abc.ABC):
    # ...
    @classmethod
    def getJSONDecoder(cls):
        class RPGJSONDecoder(json.JSONDecoder):
            def decode(self, *args, **kwargs) -> cls:
                data = json.JSONDecoder.decode(self, *args, **kwargs)
                if isinstance(data, list) and len(data) >= 1:
                    return cls.fromJSON(data)
                return data
        return RPGJSONDecoder

If I do not apply the migration everything is working properly (because the field have been created before without the custom decoder).
So maybe either Django should not produce a migration file when only the decoder part change on a JSONField because it's not link to the DB schema, or it should handle properly the fact that the decoder is a local scope defined class.

I hope that the bug issue is filled properly and that my English is compressible enough.

Change History (1)

comment:1 by Yassin Bahri, 49 minutes ago

Has patch: set
Owner: set to Yassin Bahri
Status: newassigned
Triage Stage: UnreviewedAccepted

I reproduced this issue and prepared a patch with regression tests.

The issue is that migration serialization currently allows local classes to be serialized by TypeSerializer, producing invalid migration code containing <locals>. FunctionTypeSerializer already rejects local functions, so the patch applies the same kind of early failure to local classes.

The regression tests cover both direct local class serialization and the reported JSONField(decoder=...) case.

Tested with:

PYTHONPATH=. python tests/runtests.py migrations.test_writer --verbosity 1 --parallel 1

Branch: https://github.com/yassinbahri/django/tree/ticket-37304

Note: See TracTickets for help on using tickets.
Back to Top