Search code examples
djangodjango-filtermetaclass

Can I add large numbers of filters programmatically


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


Solution

  • 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',],
            }