views:

599

answers:

1

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.

A: 

Ok, this problem got triaged. I'm marking this as a solution to close it.

Attila Oláh