Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django TransactionManagementError when using signals

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.
like image 712
Chase Roberts Avatar asked Nov 21 '13 19:11

Chase Roberts


People also ask

Is signal asynchronous Django?

To answer directly: No. It's sync.

Should I use signals Django?

Only use signals to avoid introducing circular dependencies. If you have two apps, and one app wants to trigger behaviour in an app it already knows about, don't use signals. The app should just import the function it needs and call it directly.

How do Django signals work?

Django includes a “signal dispatcher” which helps decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place.

Can't execute queries until the end of the atomic block?

You can't execute queries until the end of the 'atomic' block." is raised when you try to used a database connection after a database exception even those autocommit was set to false from the start. It should be up to the user how to handle the database exception and the transaction as autocommit was set to false.


1 Answers

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.

like image 124
Maciej Gol Avatar answered Sep 30 '22 17:09

Maciej Gol