1

I have a number of models that extend from feincms.models.Base, and use the FeinCMS item editor in the admin site (i.e. they all use feincms.admin.item_editor.ItemEditor as their ModelAdmin).

The models have some shared functionality that I want to be able to define in a shared ModelAdmin class that I can then extend for each model.

The problem is, this doesn't play well with FeinCMS extensions, causing unexpected results, such as duplicate tabs, where the extensions add things to the ModelAdmins more than once.

Is there a way to do this without messing up the extensions?

Joe
  • 6,497
  • 4
  • 29
  • 55
seddonym
  • 16,304
  • 6
  • 66
  • 71

1 Answers1

1

This is possible, but you have to adopt slightly different syntax. First, an explanation.

The reason that straightforward inheritance of ModelAdmins is broken is because of the two issues with the way FeinCMS extensions manipulate the ModelAdmin classes:

  1. First, any lists or dictionaries attached to the ModelAdmin (e.g. SharedModelAdmin.list_display) are passed by reference, and so shared between multiple ModelAdmins. This means that the extensions can end up performing an operation twice on the same list (even though it's attached to a different ModelAdmin).
  2. While in our admin.py we define the ModelAdmin's settings at the class level, FeinCMS manipulates the ModelAdmin's instance.

So, in order to get it working, we can use the following mixin:

class Faked(object):
    "A fake class to use to stand in for True in ExtendableModelAdminMixin."
    pass


class ExtendableModelAdminMixin(object):
    """ModelAdmin mixin to allow ModelAdmins to be extended (i.e.
subclassed) without messing
    up the Feincms extension registering mechanism.

    Technical note: the reason we do this is because otherwise references
    get preserved across different ModelAdmins, which means the ModelAdmins
    fail Django's checks.
    The straightforward declarative syntax of ModelAdmins sets
    attributes at the class level, but FeinCMS's
    initialize_extensions() method overrides them on the
    instance level.  So in our mixin we do a deepcopy of any
    instance level attributes before initializing the extensions.
    """
    def __init__(self, *args, **kwargs):
        # Set the _extensions_initialized attribute to prevent
        # extensions being initialized just yet
        self._extensions_initialized = Faked
        super(ExtendableModelAdminMixin, self).__init__(*args, **kwargs)

        # Before running extensions, copy any lists so we don't
        # preserve references across different ModelAdmin subclasses
        # TODO - include any other ModelAdmin properties that
        # are causing issues.
        for attr_name in ('list_display',
                          'fieldsets',
                          'search_fields', 'list_filter'):
            original = getattr(self, attr_name, [])
            copied_attr = deepcopy(original)
            setattr(self, attr_name, copied_attr)

        # Now we're ready to initialize extensions
        del(self._extensions_initialized)
        self.initialize_extensions()

Usage:

class SharedModelAdmin(ExtendableModelAdmin, ItemEditor):
    # Declare some defaults here, as usual
    list_display = ['field_one', 'field_two']

class MyModelAdmin(SharedModelAdmin):
    def __init__(self, *args, **kwargs):
        super(MyModelAdmin, self).__init__(*args, **kwargs)
        # Override things at the instance level
        self.list_display += ['field_three']
seddonym
  • 16,304
  • 6
  • 66
  • 71