I recently ran into a frustrating error while working on a Django project. I was overriding the __init__
method of a ModelForm
to customize form behavior based on a collection_type
, and Django threw this lovely gem at me:
TypeError: __init__() got multiple values for keyword argument 'collection_type'
At first glance, it didn’t make any sense. I passed all arguments correctly (or so I thought), and the error message wasn’t pointing to anything obviously wrong. After banging my head against the wall for an hour or so, I finally figured it out.
Let me walk you through what caused this error, how I fixed it, and how I made my form even smarter afterward. Hopefully, this saves you the time I lost.
My Code
Here’s the form I initially wrote:
class CreateCollectionForm(forms.ModelForm):
def __init__(self, collection_type, user=None, parent=None, *args, **kwargs):
self.collection_type = collection_type
if collection_type == 'library':
self.user = user
elif collection_type in ['bookshelf', 'series']:
self.parent = parent
else:
raise AssertionError('collection_type must be "library", "bookshelf" or "series"')
super(self.__class__, self).__init__(*args, **kwargs)
And I instantiated the form like this:
form = CreateCollectionForm(
request.POST,
collection_type=collection_type,
parent=parent,
user=request.user
)
Why This Broke
When I passed request.POST
as the first positional argument, Python assumed it was meant for the first declared parameter of __init__
, which was collection_type
.
But I also passed collection_type=collection_type
explicitly as a keyword argument. So essentially, Python saw two values for collection_type
one from the positional request.POST
and one from the keyword and panicked.
Here’s the rule Python follows:
You cannot pass a single argument as both a positional and a keyword argument at the same time.
CreateCollectionForm(request.POST, collection_type=collection_type)
caused the TypeError
.
The Correct Code
I changed the constructor to follow Django best practices by allowing *args, **kwargs
first, and then extracted the custom arguments manually:
class CreateCollectionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
collection_type = kwargs.pop('collection_type', None)
user = kwargs.pop('user', None)
parent = kwargs.pop('parent', None)
self.collection_type = collection_type
self.user = user
self.parent = parent
if collection_type not in ['library', 'bookshelf', 'series']:
raise AssertionError('collection_type must be "library", "bookshelf" or "series"')
super(CreateCollectionForm, self).__init__(*args, **kwargs)
And now, I could safely call the form like this:
form = CreateCollectionForm(
request.POST,
collection_type=collection_type,
parent=parent,
user=request.user
)
No more TypeError
.
Making the Form Smarter
Once the error was fixed, I decided to go a step further. I wanted the form to behave differently depending on the collection_type
for example, to change field labels or make certain fields required.
So I added this logic:
class CreateCollectionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
collection_type = kwargs.pop('collection_type', None)
user = kwargs.pop('user', None)
parent = kwargs.pop('parent', None)
self.collection_type = collection_type
self.user = user
self.parent = parent
super(CreateCollectionForm, self).__init__(*args, **kwargs)
if collection_type == 'library':
self.fields['name'].label = "Library Name"
self.fields['description'].required = True
elif collection_type == 'bookshelf':
self.fields['name'].label = "Bookshelf Name"
elif collection_type == 'series':
self.fields['name'].label = "Series Title"
else:
raise AssertionError('collection_type must be "library", "bookshelf", or "series"')
This gave my form a more personalized UX depending on what the user was creating a library, bookshelf, or series.
Final Thought
This little journey reminded me of two important lessons. First, Django forms are incredibly powerful, but their initialization must be handled carefully. When you override the constructor, always start with *args
and **kwargs
, then extract any custom arguments manually to avoid conflicts. Second, form customization goes far beyond just defining fields in your model. By adjusting the __init__
method, you gain the flexibility to dynamically update field labels, apply conditional validation, or even show or hide fields based on the context — making your forms smarter and more user-friendly.