How do I prevent fixtures from conflicting with django post_save signal code?

PythonDjangoSignalsFixturesDjango Signals

Python Problem Overview


In my application, I want to create entries in certain tables when a new user signs up. For instance, I want to create a userprofile which will then reference their company and some other records for them. I implemented this with a post_save signal:

def callback_create_profile(sender, **kwargs):
    # check if we are creating a new User
    if kwargs.get('created', True):
        user = kwargs.get('instance')
        company = Company.objects.create(name="My Company")
        employee = Employee.objects.create(company=company, name_first=user.first_name, name_last=user.last_name)
        profile = UserProfile.objects.create(user=user, employee=employee, partner=partner)
# Register the callback
post_save.connect(callback_create_profile, sender=User, dispatch_uid="core.models")

This works well when run. I can use the admin to create a new user and the other three tables get entries with sensible as well. (Except that is, the employee since the user.first_name and user.last_name aren't filled out in the admin's form when it saves. I still don't understand why it is done like that)

The problem came when I ran my test suite. Before this, I had created a bunch of fixtures to create these entries in the tables. Now I get an error that states:

IntegrityError: duplicate key value violates unique constraint "core_userprofile_user_id_key"

I think this is because I have already created a company,employee and profile records in the fixture with id "1" and now the post_save signal is trying to recreate it.

My questios are: can I disable this post_save signal when running fixtures? Can I detect that I am running as part of the test suite and not create these records? Should I delete these records from the fixtures now (although the signal only sets defaults not the values I want to be testing against)? Why doesn't the fixture loading code just overwrite the created records?

How do people do this?

Python Solutions


Solution 1 - Python

I think I figured out a way to do this. There is a 'raw' parameter in the kwargs passed in along with signals so I can replace my test above with this one:

if (kwargs.get('created', True) and not kwargs.get('raw', False)):

Raw is used when loaddata is running. This seems to do the trick.

It is mentioned here: <http://code.djangoproject.com/ticket/13299>

Would be nice if this was documented: <http://docs.djangoproject.com/en/1.2/ref/signals/#django.db.models.signals.post_save>

Solution 2 - Python

This is an old question, but the solution I've found most straightforward is to use the 'raw' argument, passed by load data, and decorate the listener functions, for example:

from functools import wraps


def disable_for_loaddata(signal_handler):
    @wraps(signal_handler)
    def wrapper(*args, **kwargs):
        if kwargs['raw']:
            print "Skipping signal for %s %s" % (args, kwargs)
            return
        signal_handler(*args, **kwargs)
    return wrapper

and then

@disable_for_loaddata
def callback_create_profile(sender, **kwargs):
    # check if we are creating a new User
    ...

Solution 3 - Python

Simple solution, add this to the beginning of your post_save function:

if kwargs.get('raw', False):
    return False

This will cause this function to exit when loading a fixture.

See: https://docs.djangoproject.com/en/dev/ref/signals/#post-save

Solution 4 - Python

I faced a similar problem in one of my projects. In my case the signals were slowing down the tests as well. I ended up abandoning signals in favour of overriding a Model.save() method instead.

In your case however I don't think it makes sense to achieve this by overriding any save() methods. In that case you might want to try this. Warning, I only tried it once. It seemed to work but is not thoroughly tested.

  1. Create your own test runner.
  2. Before you load the fixtures, disconnect the callback_create_profile function from the User class' post_save signal.
  3. Let the fixtures load.
  4. Connect the function back to the signal.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionposwaldView Question on Stackoverflow
Solution 1 - PythonposwaldView Answer on Stackoverflow
Solution 2 - PythonbjwView Answer on Stackoverflow
Solution 3 - PythonJ.ViolaView Answer on Stackoverflow
Solution 4 - PythonManoj GovindanView Answer on Stackoverflow