将额外的参数传递给post_save信号

22

我在Django应用程序中有一个用户注册表单,当用户尝试注册时,它会收集额外的数据,例如地址、城市、国家、电话号码等。

通过post_save信号,这些数据保存在Account模型类中。用户创建过程大致如下:

# Function to Create user Account/Profile
def create_user_account(sender, instance, created, **kwargs):
    if created:
      models.Account.objects.create(user=instance)

# Create User / User Registration
def UserRegistration(request):
    if request.method == 'POST':
        username = request.POST['fn'].capitalize() + ' ' + request.POST['ln'].capitalize()
        # CREATE USER
        newuser = User.objects.create_user(username=username, email=request.POST['email'], password=request.POST['pw'])
        newuser.first_name = request.POST['fn'].capitalize()
        newuser.last_name = request.POST['ln'].capitalize()
        newuser.save()
    return HttpResponse(username)

#Post Save handler to create user Account/Profile
post_save.connect(create_user_account, sender=User)

当用户提交表单时,会调用UserRegistration函数,在该函数内可以获取POST数据,我的目标是将这些数据传递给create_user_account方法,以便填充Account模型中的字段。

目前,我确实在数据库中看到了创建的Account对象,但除了用户字段之外,所有字段都为空。显然,因为POST变量没有被传递到create_user_account方法。


但是在你的例子中,你已经将所有信息存储在用户模型('fn','ln')上,因此你应该能够通过实例.first_name在你的create_user_account函数中访问它。或者我错过了什么? - arie
1
如果您想使用POST中的某些数据,应该在视图内创建“账户”。信号并不是为此而设计的。 - Aleksei astynax Pirogov
@arie 那个方法中还有更多未被使用的数据。 - Amyth
@astynax 谢谢,看起来这就是我需要做的方式。 - Amyth
3个回答

23

我的做法是给实例设置一些'_attrs',然后在信号处理程序中使用它们。

我想你的情况可能是:

# Function to Create user Account/Profile
def create_user_account(sender, instance, created, **kwargs):
    if created:
        attrs_needed = ['_language', '_field', '_otherfield']
        if all(hasattr(instance, attr) for attr in attr_needed):
            models.Account.objects.create(
                user=instance, 
                language=instance._language, 
                field=instance._field,
                otherfield=instance._otherfield)

# Create User / User Registration
def UserRegistration(request):
  if request.method == 'POST':
    username = request.POST['fn'].capitalize() + ' ' + request.POST['ln'].capitalize()
    # CREATE USER
    newuser = User.objects.create_user(
        username=username, email=request.POST['email'],
        password=request.POST['pw'])
    newuser.first_name = request.POST['fn'].capitalize()
    newuser.last_name = request.POST['ln'].capitalize()

    # Set some extra attrs to the instance to be used in the handler.
    newuser._language = request.POST['language']
    newuser._field = request.POST['field']
    newuser._otherfield = request.POST['otherfield']
    newuser.save()


  return HttpResponse(username)

#Post Save handler to create user Account/Profile
post_save.connect(create_user_account, sender=User)

我很不喜欢这样做,而且我想它可能会以可怕的方式中断,并且有时很难调试,此外没有一种严格的方法来强制处理程序所需的数据,可以为特定实例定义 signal_data(data, signal, instance) 来定义信号处理程序所需的数据。

一个好的选择是使用实例的方法作为信号的处理程序,也许我们可以使用更结构化的方式来传递数据。

再见。


2
感谢您抽出时间回答这个问题。为了解决这个问题,我基本上决定在视图中调用帐户模型创建方法,正如@astynax所建议的那样。 - Amyth
1
我已经编辑了代码,因为它出现了一个错误,现在我设置属性后再保存。 - Jorge E. Cardona
谢谢您发布这个。我尝试使用它,但是 newuser._language 报错 - 'User' 对象没有属性 '_language'。有什么建议吗? - markhops
覆盖了 if all(hasattr(instance, attr) for attr in attr_needed): 来查看问题所在。不知何故,我无法使自定义属性与实例保持一致。 - markhops
1
对我来说,User.objects.create_user( 立即触发了 post_save 处理程序。我如何延长 post_save 处理程序,以便可以在 User 对象上设置一些属性? - Csaba Toth
显示剩余4条评论

2

User.objects.create_userUser.objects.create都会立即触发post_save处理程序,因为UserManagercreate_user中调用了save。所以我无法想象Jorge的答案怎么能行(至少如果你只想触发一次保存-为什么要触发两次?)。你需要做的是研究一下create_user的功能,分解它,这样你就可以真正控制何时调用save

# Function to Create user Account/Profile
def create_user_account(sender, instance, created, **kwargs):
    if created:
        attrs_needed = ['_language', '_field', '_otherfield']
        if all(hasattr(instance, attr) for attr in attr_needed):
            models.Account.objects.create(
                user=instance, 
                language=instance._language, 
                field=instance._field,
                otherfield=instance._otherfield)

# Create User / User Registration
def UserRegistration(request):
  if request.method == 'POST':
    username = request.POST['fn'].capitalize() + ' ' + request.POST['ln'].capitalize()
    # CREATE USER
    newuser = User(
        username=username,
        email=request.POST['email'],
        first_name=request.POST['fn'].capitalize()
        last_name = request.POST['ln'].capitalize()
    )
    newuser.set_password(request.POST['pw'])

    # Set some extra attrs to the instance to be used in the handler.
    newuser._language = request.POST['language']
    newuser._field = request.POST['field']
    newuser._otherfield = request.POST['otherfield']
    newuser.save()  # Now this will be really the first save which is called

  return HttpResponse(username)

#Post Save handler to create user Account/Profile
post_save.connect(create_user_account, sender=User, weak=False)

请注意,当我连接处理程序时,也使用了weak=False。该参数用于防止处理程序被垃圾回收机制误删除。

1
这个答案应该是正确的。批准的答案是错误的,因为这个答案中的解释更加准确。谢谢。 - Huy Chau

0
我发现当我们使用create_usercreate方法时,receiver(post_save, sender=User)会立即被调用, 因此,我们需要先初始化对象,然后填充额外的字段。

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