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

4

59

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?

Cyrillus answered 17/8, 2010 at 6:19 Comment(0)
C
88

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/stable/ref/signals/#django.db.models.signals.post_save

Cyrillus answered 17/8, 2010 at 6:57 Comment(2)
2 years later, this is the best way that I found to handle this. I had to do this for the custom user profile (AUTH_PROFILE_MODULE) model where it would create a small list of default users from my app. I simply checked within my custom signal whether 'raw' in kwargs.Battologize
Great solution! You saved me a lot of headache this morning. To simplify this a bit, one could use the named argument created and remove the redundant parentheses: if created and not kwargs.get('raw', False):Orestes
R
26

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
    ...
Racehorse answered 10/7, 2012 at 8:53 Comment(1)
It's worth pointing out that the m2m signals don't provide the "raw" flag. I'm not sure how to get around this.Illogic
B
16

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

Behead answered 10/2, 2014 at 17:15 Comment(0)
K
3

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.
Klemperer answered 17/8, 2010 at 6:40 Comment(1)
Oh that would be an approach as well... I didn't know you could disconnect signals.Cyrillus

© 2022 - 2024 — McMap. All rights reserved.