Условный Inline в админской панели Django - ошибка "ManagementForm data is missing or has been tampered with"

Основной текст заметки

Иногда требуется делать условный Inline. как его сделать - много где описано - но в ситуации когда условие отображения инлайна изменяется в процессе редактирования, вылезает ошибка `ManagementForm data is missing or has been tampered with`

Что бы ее избежать приходится сохранять значение атрибута и использовать его при проверке.

Ниже приведен пример

Программный код
==============
admin.py
==============

class CharacterAdmin(admin.ModelAdmin):
    model = Character
    inlines = [CharacterValueInline]
    prepopulated_fields = {"handle": ("internal_name",), "public_name": ("internal_name",)}

    def get_inline_instances(self, request, obj:Character=None):
        inline_instances = []
        for inline_class in self.get_inlines(request, obj):
            inline = inline_class(self.model, self.admin_site)
            if request:
                if not obj or obj._original_character_type != obj.CHARACTER_TYPE_SET:
                    continue
                if not (inline.has_view_or_change_permission(request, obj) or
                        inline.has_add_permission(request, obj) or
                        inline.has_delete_permission(request, obj)):
                    continue
                if not inline.has_add_permission(request, obj):
                    inline.max_num = 0
            inline_instances.append(inline)

        return inline_instances

    def get_formsets_with_inlines(self, request, obj:Character=None):
        for inline in self.get_inline_instances(request, obj):
            # hide/show market-specific inlines based on market name
            if obj and obj._original_character_type == obj.CHARACTER_TYPE_SET:
                yield inline.get_formset(request, obj), inline


==============
models.py
==============

class Character(models.Model):
    """
    Класс реализует характеристики товара
    """

    class Meta:
        verbose_name = _('Character')
        verbose_name_plural = _('Characters')
        app_label = 'store'

    internal_name = models.CharField(
        max_length=100,
        null=False,
        blank=False,
        unique=True,
        verbose_name=_('Internal character name'),
    )

    handle = models.SlugField(
        max_length=32,
        null=False,
        blank=False,
        unique=True,
        verbose_name=_('Hadle'),
        help_text=_('case insensitive')
    )

    public_name = models.CharField(
        max_length=100,
        null=True,
        blank=True,
        verbose_name=_('Public character name'),
    )

    name = property(lambda self:self.public_name if self.public_name else self.internal_name)

    CHARACTER_TYPE_INHERITED = 0x1000
    CHARACTER_TYPE_INTEGER = 0x0001
    CHARACTER_TYPE_FLOAT = 0x0002
    CHARACTER_TYPE_DECIMAL = 0x0003
    CHARACTER_TYPE_SET = 0x0004

    CHARACTER_TYPES = (
        (CHARACTER_TYPE_INTEGER, _('Integer value')),
        (CHARACTER_TYPE_FLOAT, _('Float value')),
        (CHARACTER_TYPE_DECIMAL, _('Fixed decimal value')),
        (CHARACTER_TYPE_SET, _('Set of string values')),
    )

    _original_character_type = None

    character_type = models.IntegerField(
        null=False,
        blank=False,
        default=CHARACTER_TYPE_INTEGER,
        choices=CHARACTER_TYPES
    )

    def __init__(self, *args, **kwargs):
        super(Character, self).__init__(*args, **kwargs)
        self._original_character_type = self.character_type

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        self.handle = self.handle.upper()  # Переводим значение в верхний регистр
        return super().save(force_insert, force_update, using, update_fields)

    def __str__(self):
        return self.internal_name

Заметка написана: 25.05.2020

Теги заметки: Django