Code

Opened 6 years ago

Closed 5 years ago

Last modified 3 years ago

#8813 closed (fixed)

BaseInlineFormSet unable to save existing objects

Reported by: tobias Owned by: jkocherhans
Component: Forms Version: master
Severity: Keywords:
Cc: copelco Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

Description

The Error:

Environment:

Request Method: POST
Request URL: http://localhost:8000/caktus-books/ledger/exchange/215/edit/invoice/
Django Version: 1.0-beta_2-SVN-8874
Python Version: 2.5.2
Installed Applications:
['django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.admin',
 'django.contrib.humanize',
 'django.contrib.markup',
 'crm',
 'ledger']
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.middleware.doc.XViewMiddleware')


Traceback:
File "/home/tobias/caktus/eclipse-workspace/caktus_books/django/core/handlers/base.py" in get_response
  86.                 response = callback(request, *callback_args, **callback_kwargs)
File "/home/tobias/caktus/eclipse-workspace/caktus_books/django/contrib/auth/decorators.py" in __call__
  67.             return self.view_func(request, *args, **kwargs)
File "/home/tobias/caktus/eclipse-workspace/caktus_books/django/db/transaction.py" in _commit_on_success
  238.                 res = func(*args, **kw)
File "/home/tobias/caktus/eclipse-workspace/caktus_books/ledger/views.py" in create_edit_exchange
  88. 			transactions = transaction_formset.save(commit=False)
File "/home/tobias/caktus/eclipse-workspace/caktus_books/django/forms/models.py" in save
  372.         return self.save_existing_objects(commit) + self.save_new_objects(commit)
File "/home/tobias/caktus/eclipse-workspace/caktus_books/django/forms/models.py" in save_existing_objects
  386.             obj = existing_objects[form.cleaned_data[self._pk_field.name]]

Exception Type: KeyError at /caktus-books/ledger/exchange/215/edit/invoice/
Exception Value: None

From the debugger I found that:

self._pk_field.name = 'id'
self._pk_field.name in form.cleaned_data is True
form.cleaned_data[self._pk_field.name] is None

The Formset:

class BaseTransactionFormSet(BaseInlineFormSet):
	@requires_kwarg('exchange_type')
	def __init__(self, *args, **kwargs):
		self.exchange_type = kwargs.pop('exchange_type')
		super(BaseTransactionFormSet, self).__init__(*args, **kwargs)
	
	def add_fields(self, form, index):
		super(BaseTransactionFormSet, self).add_fields(form, index)
		form.fields.pop('project')
		
		form.fields['memo'].widget = forms.TextInput(attrs={'size':'35'})
		form.fields['quantity'].widget = forms.TextInput(attrs={'size':'5'})
		form.fields['amount'].widget = forms.TextInput(attrs={'size':'8'})
		
		et = self.exchange_type
		if et.credit:
			if et.slug == 'invoice':
				qs = ledger.Account.objects.filter(Q(type=et.credit.type) | Q(type='expense'))
			else:
				qs = ledger.Account.objects.filter(type=et.credit.type)
				
			form.fields['credit'].queryset = qs.order_by('number')
		else:
			form.fields.pop('credit')
		
		if et.debit:
			form.fields['debit'].queryset = ledger.Account.objects.filter(type=et.debit.type).order_by('number')
		else:
			form.fields.pop('debit')
		
TransactionFormSet = inlineformset_factory(ledger.Exchange, ledger.Transaction, formset=BaseTransactionFormSet)

The View:

@login_required
@transaction.commit_on_success
def create_edit_exchange(request, exchange_type_slug, exchange_id=None):
	exchange_type = get_object_or_404(ledger.ExchangeType, slug=exchange_type_slug)
	exchange = None
	
	if exchange_id:
		exchange = get_object_or_404(ledger.Exchange, pk=exchange_id, type=exchange_type)
	
	if request.POST:
		exchange_form = ExchangeForm(
			request.POST,
			instance=exchange, 
			exchange_type=exchange_type,
		)
		transaction_formset = TransactionFormSet(
			request.POST,
			instance=exchange, 
			exchange_type=exchange_type,
		)
		
		if exchange_form.is_valid() and transaction_formset.is_valid():
			exchange = exchange_form.save()
			
			transactions = transaction_formset.save(commit=False)
			for transaction in transactions:
				if exchange_type.credit:
					transaction.credit = exchange_form.cleaned_data['credit']
				if exchange_type.debit:
					transaction.debit = exchange_form.cleaned_data['debit']
				transaction.project = exchange_form.cleaned_data['project']
				transaction.save()
			
			request.user.message_set.create(message='%s successfully saved.' % exchange_type)
			return HttpResponseRedirect(reverse('list_exchanges', kwargs={'exchange_type_slug': exchange.type.slug}))
	else:
		exchange_form = ExchangeForm(
			instance=exchange, 
			exchange_type=exchange_type,
		)
		transaction_formset = TransactionFormSet(
			instance=exchange, 
			exchange_type=exchange_type,
		)

	context = {
		'exchange': exchange,
		'exchange_type': exchange_type,
		'exchange_form': exchange_form,
		'transaction_formset': transaction_formset,
	}
	
	return render_to_response('books/ledger/exchange/create.html', context, context_instance=RequestContext(request))

Attachments (1)

models.py (4.0 KB) - added by gordyt 5 years ago.
possible related problem (can see in admin)

Download all attachments as: .zip

Change History (16)

comment:1 Changed 6 years ago by tobias

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

This happens only when saving existing related objects with the InlineFormSet. Creating new objects works fine w/the above code.

comment:2 Changed 6 years ago by tobias

This happens even if I don't override the formset in inlineformset_factory

comment:3 Changed 6 years ago by copelco

  • Cc copelco added

comment:4 Changed 6 years ago by brosner

Can you also show the models related to this?

comment:5 Changed 6 years ago by tobias

The problem was that I wasn't printing out {{ transaction_form }} in my template, but I was printing only the fields I needed. So, that explains the absence of cleaned_data["id"]. When I manually print {{ transaction_form.id }} in my template it works fine.

Two suggestions:

1) make is_valid raise the appropriate error if the pk field is missing from request.POST.

2) make a note in the documentation that {{ form.id }} must be included in your template, if you don't use the {{ form }} or {{ formset }} helpers.

comment:6 Changed 6 years ago by Fugazi

I had the same problem. Thanks Tobias for the report and "solution".

Changed 5 years ago by gordyt

possible related problem (can see in admin)

comment:7 Changed 5 years ago by gordyt

I just uploaded a self-contained models.py file at the request of Karen T. from http://groups.google.com/group/django-users/browse_thread/thread/e2ed78ae41011b75/ this thread. The comments at the top of the file show the error message. It is possibly related to this ticket and, if so, is very easy to reproduce because you don't need to write any views, etc.

Just drop this into an app and file up the admin interface. Add a Book and (using the inline admin interface), add one or more Authors. This should save with no errors. Then, click on the Book entry in the admin interface. This should display just fine and also show the associated Author(s).

You can make a change or not, just click Save again and you should get an error message.

You will notice that both the Book and the Author models are using a custom primary key - a UUIDField that came straight out of the django-extensions project.

This error seems to occur only with using a custom primary key with an inline object. So if Book and Author each have separate admin pages, no errors occur.

Hope this helps!

--gordy

P.S. - At the top of the thread that I referenced I referred to a customized version of the UUIDField -- one that uses native uuid database types if the backend supports it. To eliminate the possibility that my customized UUIDField was causing the problem, I replicated the issue using an unmodified version of the UUIDField and that is what is included in the models.py file.

comment:8 Changed 5 years ago by jacob

  • milestone set to 1.1
  • Triage Stage changed from Unreviewed to Accepted

comment:9 Changed 5 years ago by jkocherhans

  • Owner changed from nobody to jkocherhans
  • Status changed from new to assigned

Part of the problem here is that you're using auto_created=True on your primary key fields. auto_created means that the *field* was auto_created, not that the *value* will be. Taking that off results in another error though, which occurs because a hidden field is not added to the form unless the primary key is auto_created or is an AutoField. I have a fix for both problems in my branch related to #9284.

comment:10 Changed 5 years ago by jkocherhans

  • Resolution set to fixed
  • Status changed from assigned to closed

(In [10190]) Fixed #9284. Fixed #8813. BaseModelFormSet now calls ModelForm.save().
This is backwards-incompatible if you were doing things to 'initial' in BaseModelFormSet.init, or if you relied on the internal _total_form_count or _initial_form_count attributes of BaseFormSet. Those attributes are now public methods.

comment:11 follow-up: Changed 5 years ago by bruce@…

  • Resolution fixed deleted
  • Status changed from closed to reopened

This bug still appears in revision 11620 from svn. Using gordyt's models.py, adding a book with an author and then editing it generates this:

Environment:

Request Method: POST
Request URL: http://127.0.0.1:8000/admin/gamedata/book/6fcbefca-b835-11de-80f2-00188b8e3ea8/
Django Version: 1.2 pre-alpha
Python Version: 2.6.2
Installed Applications:
['django.contrib.auth',

'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'admin.gamedata',
'admin.django_evolution']

Installed Middleware:
('django.middleware.common.CommonMiddleware',

'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware')

Traceback:
File "/usr/lib64/python2.6/site-packages/django/core/handlers/base.py" in get_response

  1. response = callback(request, *callback_args, callback_kwargs)

File "/usr/lib64/python2.6/site-packages/django/contrib/admin/options.py" in wrapper

  1. return self.admin_site.admin_view(view)(*args, kwargs)

File "/usr/lib64/python2.6/site-packages/django/utils/decorators.py" in call

  1. return self.decorator(self.func)(*args, kwargs)

File "/usr/lib64/python2.6/site-packages/django/views/decorators/cache.py" in _wrapped_view_func

  1. response = view_func(request, *args, kwargs)

File "/usr/lib64/python2.6/site-packages/django/contrib/admin/sites.py" in inner

  1. return view(request, *args, kwargs)

File "/usr/lib64/python2.6/site-packages/django/db/transaction.py" in _commit_on_success

  1. res = func(*args, kw)

File "/usr/lib64/python2.6/site-packages/django/contrib/admin/options.py" in change_view

  1. instance=new_object, prefix=prefix)

File "/usr/lib64/python2.6/site-packages/django/forms/models.py" in init

  1. queryset=qs)

File "/usr/lib64/python2.6/site-packages/django/forms/models.py" in init

  1. super(BaseModelFormSet, self).init(defaults)

File "/usr/lib64/python2.6/site-packages/django/forms/formsets.py" in init

  1. self._construct_forms()

File "/usr/lib64/python2.6/site-packages/django/forms/formsets.py" in _construct_forms

  1. self.forms.append(self._construct_form(i))

File "/usr/lib64/python2.6/site-packages/django/forms/models.py" in _construct_form

  1. form = super(BaseInlineFormSet, self)._construct_form(i, kwargs)

File "/usr/lib64/python2.6/site-packages/django/forms/models.py" in _construct_form

  1. pk = self.data[pk_key]

File "/usr/lib64/python2.6/site-packages/django/utils/datastructures.py" in getitem

  1. raise MultiValueDictKeyError, "Key %r not found in %r" % (key, self)

Exception Type: MultiValueDictKeyError at /admin/gamedata/book/6fcbefca-b835-11de-80f2-00188b8e3ea8/
Exception Value: Key 'author_set-0-id' not found in <QueryDict: {u'author_set-5-book': [u'6fcbefca-b835-11de-80f2-00188b8e3ea8'], u'author_set-5-name': [u''], u'_save': [u'Save'], u'author_set-TOTAL_FORMS': [u'6'], u'author_set-0-book': [u'6fcbefca-b835-11de-80f2-00188b8e3ea8'], u'title': [u'asdkaskldj'], u'author_set-0-name': [u'asldkjasd'], u'author_set-3-name': [u''], u'author_set-2-book': [u'6fcbefca-b835-11de-80f2-00188b8e3ea8'], u'author_set-INITIAL_FORMS': [u'3'], u'author_set-4-name': [u''], u'author_set-2-name': [u'lksjadlasd'], u'author_set-3-book': [u'6fcbefca-b835-11de-80f2-00188b8e3ea8'], u'author_set-1-book': [u'6fcbefca-b835-11de-80f2-00188b8e3ea8'], u'author_set-4-book': [u'6fcbefca-b835-11de-80f2-00188b8e3ea8'], u'author_set-1-name': [u'lkasjdalsda']}>

comment:12 in reply to: ↑ 11 Changed 5 years ago by jkocherhans

  • Resolution set to fixed
  • Status changed from reopened to closed

Replying to bruce@playfirst.com:

This bug still appears in revision 11620 from svn. Using gordyt's models.py, adding a book with an author and then editing it generates this:

The models are wrong. The UUIDField should not have auto_created=True. auto_created=True means the the field was created automatically, and it isn't in this case. If you can reproduce your bug without auto_created=True, please open a new ticket instead of reopening this one and put something like "references #8813" in the description.

comment:13 Changed 5 years ago by bruce@…

I reproduce the error with gordy's models.py file, even if I set auto_created=False for both Book and Author.

I will open another bug also.

The error follows.

MultiValueDictKeyError at /admin/gamedata/book/7432e312-d39b-11de-913b-00188b8e3ea8/

Key 'author_set-0-id' not found in <QueryDict: {u'_save': [u'Save'], u'author_set-TOTAL_FORMS': [u'5'], u'author_set-0-book': [u'7432e312-d39b-11de-913b-00188b8e3ea8'], u'title': [u'book1'], u'author_set-0-name': [u'author1'], u'author_set-3-name': [u''], u'author_set-2-book': [u'7432e312-d39b-11de-913b-00188b8e3ea8'], u'author_set-INITIAL_FORMS': [u'2'], u'author_set-4-name': [u''], u'author_set-2-name': [u'asdasdasd'], u'author_set-3-book': [u'7432e312-d39b-11de-913b-00188b8e3ea8'], u'author_set-1-book': [u'7432e312-d39b-11de-913b-00188b8e3ea8'], u'author_set-4-book': [u'7432e312-d39b-11de-913b-00188b8e3ea8'], u'author_set-1-name': [u'authro2']}>

Request Method: POST
Request URL: http://127.0.0.1:8000/admin/gamedata/book/7432e312-d39b-11de-913b-00188b8e3ea8/
Exception Type: MultiValueDictKeyError
Exception Value:

Key 'author_set-0-id' not found in <QueryDict: {u'_save': [u'Save'], u'author_set-TOTAL_FORMS': [u'5'], u'author_set-0-book': [u'7432e312-d39b-11de-913b-00188b8e3ea8'], u'title': [u'book1'], u'author_set-0-name': [u'author1'], u'author_set-3-name': [u''], u'author_set-2-book': [u'7432e312-d39b-11de-913b-00188b8e3ea8'], u'author_set-INITIAL_FORMS': [u'2'], u'author_set-4-name': [u''], u'author_set-2-name': [u'asdasdasd'], u'author_set-3-book': [u'7432e312-d39b-11de-913b-00188b8e3ea8'], u'author_set-1-book': [u'7432e312-d39b-11de-913b-00188b8e3ea8'], u'author_set-4-book': [u'7432e312-d39b-11de-913b-00188b8e3ea8'], u'author_set-1-name': [u'authro2']}>

etc... trac is blocking the rest :(

comment:14 Changed 5 years ago by bruce@…

related to #12235

comment:15 Changed 3 years ago by jacob

  • milestone 1.1 deleted

Milestone 1.1 deleted

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.