[Answer]-Properly override ModelForm's form fields with keeping the **kwargs from the model fields


You can use **kwargs to capture and pass through all keyword arguments that you don’t specify directly. kwargs can be anything, but convention is to use that name. It would look as follows:

def __init__(self, mandatarg1, ckwarg1=cdefault1, **kwargs)
    # do something
    super(MyCustomCharField, self).__init__(**kwargs)
    # do something else

Due to the double asteriks **, all keyword arguments that are not specifically defined will be packed in a dict object called kwargs. By passing **kwargs to the call to super, these values are unpacked again before the super’s __init__ function is called with these keyword arguments.

E.g. you can call the following:

field1 = MyCustomCharField(mandatvalue1, max_length=255, ckwarg1 = cvalue1)

kwargs will then look like this:

kwargs = {'max_length': 255}

And python will handle the call to super as if you had called:

super(MyCustomCharField, self).__init__(max_length=255)

The same can be done for positional arguments with a single asteriks, convention is to do this with *args. args will be a tuple of the positional arguments, consistent with the order in which the arguments were passed. *args and **kwargs have to be the last arguments defined in a function definition. You should be careful with *args, though: always remember that in a function definition there is no difference in positional arguments and keyword arguments. So if you were to extend the MultiValueField class, and you want to pass the fields as a positional argument, you have to treat every single argument in you subclass’s __init__ method as a positional argument. So:

class MyCustomMultiValueField(forms.MultiValueField):
    def __init__(customarg1, customarg2=None, *args, **kwargs):
        # do something
        super(MyCustomMultiValueField, self).__init__(*args, **kwargs)

# this won't work, as now customargs2 is (BooleanField(), DateField()), instead of fields
MyCustomMultiValueField(1, (BooleanField(), DateField()))

# you can still pass fields as a keyword argument
MyCustomMultiValueField(1, fields=(BooleanField(), DateField()))

If you only want to specify keyword arguments, or if you want to specify positional arguments after the super’s positional arguments, a common way to do that is to only specify *args and **kwargs as the arguments:

def __init__(self, *args, **kwargs):
    keywordarg1 = kwargs.pop('keyword1', None)
    mandatorykeywordarg2 = kwargs.pop('keyword2')
    if len(args) == 2:
        # because tuples are immutable
        args = list(args)
        positionalargumentafterfields = args.pop(1)
    super(MyCustomMultiValueField, self).__init__(*args, **kwargs)
    # *args also works with a list

A common use-case for this is when you want to specify extra arguments, while some other code relies on the positional arguments of the base class to create your subclass. Django models and forms are examples where you’d want to do this to prevent breaking the code.

Documentation on arguments in function definitions.


Leave a comment