Let's say I have the following Django models with these ForeignKey
fields (simplified syntax):
class Container(Model):
items -> Item
sub_items -> SubItem
sub_sub_items -> SubSubItem
class Item(Model):
container -> Container
children -> SubItem
class SubItem(Model):
container -> Container
parent -> Item
children -> SubSubItem
class SubSubItem(Model):
container -> Container
parent -> SubItem
Now I'm creating a custom ModelAdmin and ModelForm for the Container model:
class ContainerForm(ModelForm):
items = Field(widget=MyCustomWidget(...))
sub_items = Field(widget=HiddenWidget(...))
sub_sub_items = Field(widget=HiddenWidget(...))
class ContainerAdmin(ModelAdmin):
form = ContainerForm
Then, MyCustomWidget
renders a special widget that displays the items, sub-items and sub-sub-items in a nicve hierarchy, and when they get selected, it populates the hidden field with the appropriate ids. And it works, in most cases.
HOWEVER: if I don't select any item or sub-item or sub-sub-item, somewhere my empty lists get replaced by None
, and I get an error:
add() argument after * must be a sequence, not NoneType
Here's the traceback:
File "/usr/lib64/python2.6/site-packages/django/core/handlers/base.py" in get_response
92. response = callback(request, *callback_args, **callback_kwargs)
File "/usr/lib64/python2.6/site-packages/django/contrib/admin/options.py" in wrapper
226. return self.admin_site.admin_view(view)(*args, **kwargs)
File "/usr/lib64/python2.6/site-packages/django/views/decorators/cache.py" in _wrapped_view_func
44. response = view_func(request, *args, **kwargs)
File "/usr/lib64/python2.6/site-packages/django/contrib/admin/sites.py" in inner
186. return view(request, *args, **kwargs)
File "/usr/lib64/python2.6/site-packages/django/db/transaction.py" in _commit_on_success
240. res = func(*args, **kw)
File "/usr/lib64/python2.6/site-packages/django/contrib/admin/options.py" in add_view
735. form.save_m2m()
File "/usr/lib64/python2.6/site-packages/django/forms/models.py" in save_m2m
75. f.save_form_data(instance, cleaned_data[f.name])
File "/usr/lib64/python2.6/site-packages/django/db/models/fields/related.py" in save_form_data
967. setattr(instance, self.attname, data)
File "/usr/lib64/python2.6/site-packages/django/db/models/fields/related.py" in __set__
627. manager.add(*value)
The interesting thing is that form.cleaned_data[field] is set to an empty list, but later on it gets replaced by None (and I do not replace this, Django does, somewhere, I couldn't figure out where).
So, my hacky approach was to hook into ModelAdmin.save_model(...)
, like this:
def save_model(self, request, new_object, form, change=False):
# hook into save_model to work around the m2m widget save issue
for field in ['items', 'sub_items', 'sub_sub_items']:
form.cleaned_data[field] = form.cleaned_data[field] or []
return super(ContainerAdmin, self).save_model(request, new_object, form, change=False)
This hack makes the admin page working, but I don't think that this is the right way to hook into the ModelAdmin
. What is the right way to handle m2m relationship values in widgets?
Let me know if you need more info, this is my first post on SO, so thanks for any response.