如何防止Django post_save信号代码与Fixture冲突?

58
在我的应用中,当一个新用户注册时,我想要在特定的表中创建条目。例如,我想要创建一个用户配置文件,它将引用他们的公司和一些其他记录。我使用post_save信号实现了这个功能:
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")

这个代码在运行时很好用。我可以使用管理员创建一个新用户并且其他三个表也会得到合理的条目。(除了员工,因为当保存表单时用户的名字和姓氏没有被填写)我仍然不明白为什么会这样。

问题出现在我运行测试套件时。在此之前,我创建了一堆fixture来在表格中创建这些条目。现在我得到了一个错误,提示:

IntegrityError: duplicate key value violates unique constraint "core_userprofile_user_id_key"

我认为这是因为我已经在ID为"1"的fixture中创建了一个公司(company)、员工(employee)和档案(profile)记录,现在post_save信号(signal)正在尝试重新创建它。

我的问题是:在运行fixtures时可以禁用此post_save signal吗? 我可以检测到自己正在作为测试套件的一部分运行并且不创建这些记录吗?我现在应该从fixtures中删除这些记录吗(虽然该信号只设置默认值而不是我想要进行测试的值)?为什么fixture加载代码不能覆盖已经创建的记录?

人们是如何做到这一点的?

4个回答

86

1
两年后,这是我发现处理这个问题的最佳方式。我不得不为自定义用户配置文件(AUTH_PROFILE_MODULE)模型执行此操作,其中它将从我的应用程序创建一小组默认用户。我只需在自定义信号中检查kwargs中是否有“raw”即可。 - Jordan
2
很棒的解决方案!你今天早上为我省了很多麻烦。要简化这个过程,可以使用命名参数 created 并删除多余的括号:if created and not kwargs.get('raw', False): - Joe Tricarico

26

这是一个旧问题,但我发现最直接的解决方法是使用由load data传递的'raw'参数,并且修饰监听函数,例如:

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

然后

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

2
值得指出的是,m2m信号不提供“原始”标志。我不确定如何解决这个问题。 - Mike Stoddart

16

3

在我的一个项目中,我遇到了类似的问题。在我的情况下,信号也减慢了测试的速度。最终,我放弃信号而选择重写 Model.save() 方法。

然而,在你的情况下,我认为通过重写任何 save() 方法来实现这一点是没有意义的。在这种情况下,您可以尝试这个方法。请注意,我只尝试过一次。它“似乎”起作用,但并未经过全面测试。

  1. 创建 您自己的测试运行器
  2. 在加载夹具之前,断开User 类的 post_save 信号中的 callback_create_profile 函数。
  3. 让固定装载。
  4. 将函数重新连接到信号。

哦,那也是一种方法...我不知道你可以断开信号。 - poswald

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接