12

I have a one to one field with django's users and UserInfo. I want to subscribe to the post_save callback function on the user model so that I can then save the UserInfo as well.

@receiver(post_save, sender=User) 
def saveUserAndInfo(sender, instance, **kwargs):
    user = instance
    try:
        user.user_info.save()
    except:
        info = UserInfo()
        info.user = user
        info.save()

However, I am getting a TransactionManagementError when I try to do this. I am assuming because the user model hasn't finished saving and I am trying to read the id to save it to the user_info. Anyone know how to do this properly?

A second problem. I wanted to attach a UserInfo instance to a user as soon as the user was created. So on post_init I tried to create a UserInfo instance and assign it to the user instance but it doesn't work because the user hasn't been assigned a pk yet. I am assuming I just have to wait until post_save (or later) to create this instance. Is this the only way to do it?

Traceback:

File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  114.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/contrib/admin/options.py" in wrapper
  430.                 return self.admin_site.admin_view(view)(*args, **kwargs)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/utils/decorators.py" in _wrapped_view
  99.                     response = view_func(request, *args, **kwargs)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/views/decorators/cache.py" in _wrapped_view_func
  52.         response = view_func(request, *args, **kwargs)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/contrib/admin/sites.py" in inner
  198.             return view(request, *args, **kwargs)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/utils/decorators.py" in _wrapper
  29.             return bound_func(*args, **kwargs)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/utils/decorators.py" in _wrapped_view
  99.                     response = view_func(request, *args, **kwargs)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/utils/decorators.py" in bound_func
  25.                 return func(self, *args2, **kwargs2)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/transaction.py" in inner
  339.                 return func(*args, **kwargs)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/contrib/admin/options.py" in add_view
  1129.                 self.save_model(request, new_object, form, False)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/contrib/admin/options.py" in save_model
  858.         obj.save()
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/models/base.py" in save
  545.                        force_update=force_update, update_fields=update_fields)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/models/base.py" in save_base
  582.                                    update_fields=update_fields, raw=raw, using=using)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/dispatch/dispatcher.py" in send
  185.             response = receiver(signal=self, sender=sender, **named)
File "/Users/croberts/lunchbox/userinfo/models.py" in saveUserAndInfo
  83.         info.save()
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/models/base.py" in save
  545.                        force_update=force_update, update_fields=update_fields)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/models/base.py" in save_base
  573.             updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/models/base.py" in _save_table
  654.             result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/models/base.py" in _do_insert
  687.                                using=using, raw=raw)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/models/manager.py" in _insert
  232.         return insert_query(self.model, objs, fields, **kwargs)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/models/query.py" in insert_query
  1511.     return query.get_compiler(using=using).execute_sql(return_id)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/models/sql/compiler.py" in execute_sql
  898.             cursor.execute(sql, params)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/backends/util.py" in execute
  69.             return super(CursorDebugWrapper, self).execute(sql, params)
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/backends/util.py" in execute
  47.         self.db.validate_no_broken_transaction()
File "/Users/croberts/.virtualenvs/lunchbox/lib/python2.7/site-packages/django/db/backends/__init__.py" in validate_no_broken_transaction
  365.                 "An error occurred in the current transaction. You can't "

Exception Type: TransactionManagementError at /gov/auth/user/add/
Exception Value: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.
Chase Roberts
  • 9,082
  • 13
  • 73
  • 131

1 Answers1

22

The error is caused by the user.user_info.save() line throwing an exception, and the transaction was flagged as broken (PostgreSQL enforces rolling back either the whole transaction, or to any savepoint stored before doing any more queries inside that transaction).

You can rollback the transaction when an error occurs:

from django.db import IntegrityError, transaction

@receiver(post_save, sender=User) 
def saveUserAndInfo(sender, instance, **kwargs):
    user = instance
    try:
        with transaction.atomic():
            user.user_info.save()
    except IntegrityError:
        info = UserInfo()
        info.user = user
        info.save()

This is greatly described in the documentation.

Also, note that catching all exception types is not generally the best idea as you might silence exceptions you weren't expecting.

Maciej Gol
  • 15,394
  • 4
  • 33
  • 51
  • This makes sense and works, except for when I try to make new users from the admin panel. I have my User and UserInfo grouped together using inlines and I think it is trying to save the thing at the same time so it gives me a IntegrityError at /loginView/ column user_id is not unique – Chase Roberts Nov 21 '13 at 22:04
  • @ChaseRoberts, check out [this answer](http://stackoverflow.com/a/6117457/1338158). It solves the problem. – Maciej Gol Nov 21 '13 at 22:18
  • Are save() transaction not atomic already? With 10k requests per minute this would be a big change to my model – David Schumann May 22 '15 at 17:19
  • @davidnathan it depends. `save` itself is not, although views can be atomic. The point here is that since the OP got the error message, the save was inside a transaction. Now, inside a transaction `transaction.atomic ()` creates a _savepoint_ which automatically gets rolledback on exception. This is the use case here. – Maciej Gol May 23 '15 at 20:13