1

I have a table with about 30 columns and I'd like to attach five filters to most columns in a highly repetitive manner. So I hoped that I could use a class decorator to define them as per this SO answer. No joy. TypeError: 'NoneType' object is not callable (at runtime when I invoke the view)

Anyway, then I read up about "proper" metaclassing and tried

class SettingsMeta(type):
    def __new__(cls, clsname, bases, attrs):

        for name in ('fx_t', 'status'): # just two for now but target ~60 with 5 different lookup_expr
            attr_name = name + '_start'
            attrs[attr_name] =  FD.CharFilter( 
                field_name = name,
                lookup_expr = 'istartswith' ,
                label =  name.replace('_t','').capitalize() + ' starts with',
            )  
        return super(SettingsMeta, cls).__new__(cls, clsname, bases, uppercase_attrs)

class SelectMaskdataFilters( FD.FilterSet, metaclass=SettingsMeta): 
    class Meta:
        model = Maskdata
        fields = {
            'notes':        [ 'icontains',],
        }
    #status_sw = FD.CharFilter( field_name='status', lookup_expr='startswith')
    ...

Again no joy: TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases at server start-up.

SelectMaskDataFilters is itself working as expected in a view if I just remove metaclass=SettingsMeta

I'm in over my head, but I really don't like repeating myself with a hundred or so filter definitions all of which will be almost the same. So, does anybody know how to define a large number of filters in in a FilterSet other than copy, paste, and error-prone small changes? Note, most of the filters will use method= not lookup_expr= so the fields attribute in the inner Meta class is not useful

nigel222
  • 7,582
  • 1
  • 14
  • 22

2 Answers2

1

You can define these in the local scope of the SelectMaskdataFilters:

import django_filters

class SelectMaskdataFilters(django_filters.FilterSet):
    for name in ('fx_t', 'status'):
        label = name.replace('_t','').capitalize()
        locals()[f'{name}_start'] = django_filters.CharFilter( 
            field_name=name,
            lookup_expr='istartswith' ,
            label=f'{label} starts with'
        )
    
    class Meta:
        model = Maskdata
        fields = {
            'notes': [ 'icontains',],
        }
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
1

The metaclass conflict error is due to the fact the class you are inherting from (FD.FilterSet) already uses a custom metaclass. So, if you inherit your from type, Python has conflicting metaclasses that it can't make work nicely together: which metaclass.__new__ method should be called? The one in your metaclass or in the FD.FilterSet metaclass.

The way to make that work properly is to make your metaclasses work colaboratively, and have a single metaclass that resolves the conflicts. In this case, all you have to do is to inherit of the metaclass of FD.FieldSet itself instead of type. (and, of course, call the superclass's methods were needed)

You can use the one-parameter form of type to get the proper metaclass, you don't even need to import it explicitly.

In your listing, just change this line:

class SettingsMeta(type):

for

class SettingsMeta(type(FD.FilterSet)):

and everything should work (with no need to repeat yourself in the class body for each of your models)

jsbueno
  • 99,910
  • 10
  • 151
  • 209