Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass argument to __init__ of object model class in iterate queryset

I have a model with overridden __init__ method like this:

class MyModel(models.Model):
    ...

    def __init__(self, *args, **kwargs):
        if not kwargs.get('skip', False):
            do_something()
        super().__init__(*args, **kwargs) 

How can I pass skip argument to __init__, when I iter the queryset:

data = [obj for obj in MyModel.objects.all()]

I would like to implement this via method in custom manager, for use this something like this: queryset.with_skip()

like image 863
Ron Arr Avatar asked Sep 01 '17 07:09

Ron Arr


1 Answers

I see that you don't remove the the argument skip from kwargs before passing it to super().__init__. That means that "skip" is a name of field, otherwise you got exception TypeError("'skip' is an invalid keyword argument for this function").

If you really need do_something() when the object is created before using so indispensably that nobody should forget to avoid all unsupported ways (??) then custom managers etc. are not enough.

Your problem is that models.Model.__init__(...) supports both *args and **kwargs arguments so perfectly that they should be interchangeable. You broke it and if the "skip" is passed by the complete tuple of positional arguments, you ignore it. That is if the object is created from the database. Read the docs Customizing model loading:

... If all of the model’s fields are present, then values are guaranteed to be in the order __init__() expects them. That is, the instance can be created by cls(*values)...
.
| @classmethod
| def from_db(cls, db, field_names, values):
| ...
| instance = cls(*values)
| ...

An easy way to fix it is to call do_something() after super().__init__ and read self.skip instead of implement parsing both kwargs and args.

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs) 
        if not self.skip:
            do_something()

A problem could be the signal "post_init" that is sent at the end of super().__init__ if you need it.

The last possibility is to support *args (hacky, but still uses documented names someway):

    def __init__(self, *args, **kwargs):
        if kwargs:
            skip = kwargs.get('skip', False)
        else:
            # check "val is True" in order to skip if val is DEFERRED
            skip = any(field.name == 'skip' and val is True
                       for val, field in zip(args, self._meta.concrete_fields)
                       )
        if not skip:
            do_something()
        super().__init__(*args, **kwargs)

EDIT: Maybe you you don't need what you wanted and a Proxy model that can do something extra sometimes over a basic model on the same data in the same database table is the right solution. ("Skip" doesn't look like a name describing the object data, but like a name describing a mode of object creation. It is easier to test and maintain a subclass than a mysterious switch inside.)

like image 143
hynekcer Avatar answered Oct 19 '22 15:10

hynekcer