3

For my project I need to dynamically create custom (Class) methods.

I found out it is not so easy in Python:

class UserFilter(django_filters.FilterSet):
    '''
    This filter is used in the API
    '''
    # legacy below, this has to be added dynamically
    #is_field_type1 = MethodFilter(action='filter_field_type1') 

    #def filter_field_type1(self, queryset, value):
    #    return queryset.filter(related_field__field_type1=value)

    class Meta:
        model = get_user_model()
        fields = []

But it is giving me errors (and headaches...). Is this even possible?

I try to make the code between #legacy dynamic

One option to do this I found was to create the class dynamically

def create_filter_dict():
    new_dict = {}
    for field in list_of_fields:

        def func(queryset, value):
            _filter = {'stableuser__'+field:value}
            return queryset.filter(**_filter)

        new_dict.update({'filter_'+field: func})

        new_dict.update({'is_'+field: MethodFilter(action='filter_'+field)})

    return new_dict


meta_model_dict = {'model': get_user_model(), 'fields':[]}
meta_type = type('Meta',(), meta_model_dict)

filter_dict = create_filter_dict()
filter_dict['Meta'] = meta_type
UserFilter = type('UserFilter', (django_filters.FilterSet,), filter_dict)

However, this is giving me

TypeError at /api/v2/users/
func() takes 2 positional arguments but 3 were given

Does anyone know how to solve this dilemma?

17
  • 8
    "this does not work" is probably just about the worst way you could possibly give a diagnosis of your issue Commented Jul 25, 2016 at 18:33
  • 1
    That's not how you create a classmethod. What are you even trying to do here? You have a class definition inside another class definition (which is totally possible, just surely not what you want)... wha problem are you even trying to solve? Commented Jul 25, 2016 at 18:33
  • show the complete traceback Commented Jul 25, 2016 at 18:33
  • 1
    Trying to change the FilterSet class in __init__() is already much too late. At that point, FilterSetMetaclass has already done all the relevant setup. You need to either build the FilterSet class dynamically (type()) or dig into django-filter and find a better way. Commented Jul 25, 2016 at 19:01
  • 1
    The error you're getting in EDIT 3 is because dict.update returns None, not the dict. Commented Aug 1, 2016 at 18:42

3 Answers 3

2
+50

Exception Value: 'UserFilter' object has no attribute 'is_bound'

You are getting this error because the class methods you are generating, are not bound to any class. To bound them to the class, you need to use setattr()

Try this on a console:

class MyClass(object):
    pass

@classmethod 
def unbound(cls):
    print "Now I'm bound to ", cls


print unbound
setattr(MyClass, "bound", unbound) 
print MyClass.bound 
print MyClass.bound()

Traceback: UserFilter = type('Foo', (django_filters.FilterSet, ), create_filter_dict().update({'Meta':type('Meta',(), {'model': get_user_model(), 'fields':[]} )})) TypeError: type() argument 3 must be dict, not None

Now, this is failing because dict.update() doesn't return the same instance, returns None. That can be fixed easily

class_dict = create_filter_dict()
class_dict.update({'Meta':type('Meta',(), {'model': get_user_model(), 'fields':[]})}
UserFilter = type('Foo', (django_filters.FilterSet, ), class_dict))

However, just look how messy that code looks. I recommend to you to try to be clearer with the code you write even if it requires to write a few extra lines. In the long run, the code will be easier to maintain for you and your team.

meta_model_dict = {'model': get_user_model(), 'fields':[]}
meta_type = type('Meta',(), meta_model_dict)

filter_dict = create_filter_dict()
filter_dict['Meta'] = meta_type
UserFilter = type('Foo', (django_filters.FilterSet,), filter_dict)

This code might not be perfect but it is more readable than the original line of code you posted:

UserFilter = type('Foo', (django_filters.FilterSet, ), create_filter_dict().update({'Meta':type('Meta',(), {'model': get_user_model(), 'fields':[]})}))

And removes a complication on an already kinda difficult concept to grasp.

You might want to learn about metaclasses. Maybe you can overwrite the new method of a class. I can recommend you 1 or 2 posts about that.

Another option is that maybe you are not adding the filters correctly or in a way django doesn't expect? That would explain why you get no errors but none of your functions gets called.

Sign up to request clarification or add additional context in comments.

3 Comments

Wow, I really learned something today and although I did not check the answer yet, this is really going to help me build it! Also the references you mention about metaclass are great! Bounty well spent :)
Okay, it does not work yet. Still trying, so any thoughts would be much appreciated!
Have you tried to add a simple filter that works well maybe for just one case? Try to see what it's making that work, and what you might be missing.
0

You can use classmethod. Here is example how you can use it:

class UserFilter:
    @classmethod
    def filter_field(cls, queryset, value, field = None):
        # do somthing
        return "{0} ==> {1} {2}".format(field, queryset, value)
    @classmethod
    def init(cls,list_of_fields ):
        for field in list_of_fields:
            ff = lambda cls, queryset, value, field=field: cls.filter_field(queryset, value, field )
            setattr(cls, 'filter_'+field, classmethod( ff ))

UserFilter.init( ['a','b'] )
print(UserFilter.filter_a(1,2)) # a ==> 1 2
print(UserFilter.filter_b(3,4)) # b ==> 3 4

1 Comment

It does not give me any errors, but is also does not add the filter to the query. I think your code does not play well with django_filters.
0

You are asking for:

custom (Class) methods.

So we take an existing class and derive a subclass where you can add new methods or overwrite the methods of the original existing class (look into the code of the original class for the methods you need) like this:

from universe import World
class NewEarth(World.Earth):
   def newDirectionUpsideDown(self,direction):
       self.rotationDirection = direction

All the other Methods and features of World.Earth apply to NewEarth only you can now change the direction to make the world turn your new way.

To overwrite an existing method of a class is as as easy as this.

class NewEarth(World.Earth):
   def leIitRain(self,amount): # let's assume leIitRain() is a standard-function of our world
       return self.asteroidStorm(amount) #let's assume this is possible Method of World.Earth

So if someone likes a cool shower on earth he/she/it or whatever makes room for new development on the toy marble the burning way.

So have fun in your way learning python - and don't start with complicated things.

If I got you completely wrong - you might explain your problem in more detail - so more wise people than me can share their wisdom.

2 Comments

Sorry, I mean a custom dynamiccally generated class method. So your example of inheritance is not going to answer the question.
If I search on django-filter.readthedocs.io/en/latest/index.html nothing states that you should use dynamic classes or dynamic functions/methods. MAYBE the word "dynamically" in the comments is misleading?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.