I have the following code:
from form_utils import forms as betterforms
from django.db import models
class FilterForm(betterforms.BetterForm):
def __init__(self, *args, **kwargs):
super(FilterForm, self).__init__(*args, **kwargs)
print('filter form __init__')
...
class NewEntityForm(FilterForm, FileFormMixin):
def __init__(self, *args, **kwargs):
super(NewEntityForm, self).__init__(*args, **kwargs)
# super(FileFormMixin, self).__init__() <-- really does not matter
print('newentityform __init__')
FileForMixin defined as follows:
class FileFormMixin(object):
def __init__(self, *args, **kwargs):
super(FileFormMixin, self).__init__(*args, **kwargs)
print('file form mixin __init__')
FileFormMixin
is provided by https://github.com/mbraak/django-file-form, betterforms by https://github.com/carljm/django-form-utils.
The problem is, FileFormMixin
's __init__
is never getting called. How can I fix that? I really need all of them. Right now it's executing only FilterForm
and NewEntityForm
constructors.
UPDATE
So, I looked at all mentioned classes __init__
's, and they're calling super()!
FileFormMixin
:
class FileFormMixin(object):
def __init__(self, *args, **kwargs):
super(FileFormMixin, self).__init__(*args, **kwargs)
BetterForm
:
class BetterBaseForm(object):
...
def __init__(self, *args, **kwargs):
self._fieldsets = deepcopy(self.base_fieldsets)
self._row_attrs = deepcopy(self.base_row_attrs)
self._fieldset_collection = None
super(BetterBaseForm, self).__init__(*args, **kwargs)
class BetterForm(with_metaclass(BetterFormMetaclass, BetterBaseForm),
forms.Form):
__doc__ = BetterBaseForm.__doc__
More of that, printing class' mro as @elwin-arens proposed, gives following output:
filter form __init__
NewEntityForm.__mro__ (<class 'myapp.forms.NewEntityForm'>, <class 'myapp.forms.FilterForm'>, <class 'form_utils.forms.BetterForm'>, <class 'django.forms.widgets.NewBase'>, <class 'form_utils.forms.BetterBaseForm'>, <class 'django.forms.forms.Form'>, <class 'django.forms.forms.BaseForm'>, <class 'django_file_form.forms.FileFormMixin'>, <class 'object'>)
newsiteform __init__
But __init__
for FileFormMixin is executed only if I call it explicitly as @tom-karzes advised
tl;dr
From your posted MRO of NewEntityForm
you see the class BaseForm
. In the source of django.forms.forms.BaseForm you can also see that this class does not call super(BaseForm, self).__init__()
and therefore is responsible for breaking the chain to FileFormMixin.
In this case you can work around this by changing the order of NewEntityForm's base classes like so:
class NewEntityForm(FileFormMixin, FilterForm):
def __init__(self, *args, **kwargs):
super(NewEntityForm, self).__init__(*args, **kwargs)
Explanation
What you're probably thinking is that the classes FilterForm
and FileFormMixin
exists as base classes side-by-side.
In reality inheriting from multiple classes creates a Method Resolution Order (MRO) that is a list of classes that is traversed linear, in-order. This order determines what super
means in a given context.
A simplified example to demonstrate:
class A(object):
def __init__(self):
super(A, self).__init__()
print('A', A.__mro__)
class B(object):
def __init__(self):
super(B, self).__init__()
print('B', B.__mro__)
class C(B, A):
def __init__(self):
super(C, self).__init__()
print('C', C.__mro__)
c = C()
super
takes a class and moves one to the right in the method resolution order. So super(C, self).__init__()
calls B.__init__
, which should be obvious, but super(B, self).__init__()
in this context calls A.__init__
and not object.__init__
, which probably is less obvious.
This is because in the above code example C has the following method resolution order
:
C (<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>)
To put it in pictures, the left if what you might expect, the right is what's actually happening:
Mental model Actual model
C C
| |
/ \ B
B A |
| | A
\ / |
object object
So if B doesn't call it's super:
class A(object):
def __init__(self):
super(A, self).__init__()
print('A')
class B(object):
def __init__(self):
print('B')
class C(B, A):
def __init__(self):
super(C, self).__init__()
print('C')
c = C()
This results in A's __init__()
not being called after calling super(C, self).__init__()
.
This then begs the question what the MRO of NewEntityForm
looks like. You can print the mro like so:
print('NewEntityForm.__mro__', NewEntityForm.__mro__)
This will probably show a list where FileFormMixin
comes after a class that does not call it's super init method, as Daniel Roseman already said.
A possible solution might be to explicitly call the init of the class you want to apply like so:
class NewEntityForm(FilterForm):
def __init__(self, *args, **kwargs):
super(NewEntityForm, self).__init__(*args, **kwargs)
FileFormMixin.__init__(self)
print('newentityform __init__')
Or you could flip the base classes of NewEntityForm
around.
Presumably, BetterForm does not call super, so the chain stops there.
The only thing you can do is to swap the order of classes in the declaration:
class NewEntityForm(FileFormMixin, FilterForm):
Although of course this might affect any other methods that are defined in both classes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With