【问题标题】:Custom Foreign Key Property自定义外键属性
【发布时间】:2011-10-12 21:04:06
【问题描述】:

我有一个名为Personnel 的模型,我将其用作User 模型的配置文件模型。这是我的模型

class Personnel(models.Model):
    user = models.OneToOneField(
        User
    )
    phone = models.CharField(
        null=False, max_length=50, verbose_name="Phone"
    )

我需要在 ModelForm 中使用上述模型。这是我的ModelForm

class PersonnelForm(forms.ModelForm):
    class Meta:
        model = Personnel
        exclude = ('user')

    def __init__(self, *args, **kwargs):
        personnel = Personnel(user=User.objects.create(username=(''.join(choice(ascii_uppercase + digits) for x in range(30)))))
        super(PersonnelForm, self).__init__(
             instance=personnel, *args, **kwargs
        )

我不需要将user 字段显示在网络上,因此我将其排除在外。但是我仍然需要为它传递一个值,所以通过传递一个Personnel 实例来启动我的PersonnelFormUser 模式有一个名为 email 的字段应该由用户输入,但我无法显示此字段,因为它不是我的 Personnel 模型上的字段。

我可以创建一个名为email 的虚拟字段,它在设置时设置user.email 字段的值,在获取时获取相同的值。像这样:

@property
def email():
    def fget(self):
        return 'fff'

    def fset(self, email):
        self.user.email = email

我需要以类似于上面的 sn-p 的方式使用它,因为我将使用 PersonnelFormDjango's generic CRUD views

谢谢。

(请随时编辑并为标题建议一个更好的名称,以便其他人也能从中受益。)


使用丹尼尔的建议:

class PersonnelForm(forms.ModelForm):

    email = forms.EmailField(
        required=True, label=_("Email")
    )

    class Meta:
        model = Personnel
        exclude = ('user')

    def save(self, *args, **kwargs):
        commit = kwargs.get('commit', True)
        kwargs['commit'] = False
        personnel = super(PersonnelForm, self).save(*args, **kwargs)
        if not personnel.user: #To prevent creating a new user when updating.
            user = User.objects.create_user(
                email=self.cleaned_data['email'],
                username=(''.join(choice(ascii_uppercase + digits) for x in range(30))),
            )
        if commit:
            personnel.save()
        return personnel

这适用于使用 ModelForm 创建新记录,但我似乎无法更新电子邮件或在更新时查看电子邮件。

【问题讨论】:

  • 您不需要为用户传递任何值,因为您已从 PersonnelForm 中排除了该属性。
  • Brandon,如果我不排除用户字段,那么它会在我的表单中为我提供一个下拉列表,用于创建 Personnel 记录。我需要创建一个Personnel 记录并在幕后自动创建一个User。每个User record 都会有一个随机的usernamepassword 可以为空,稍后我会处理它,但我需要用户能够指定电子邮件地址——因此属性混乱。
  • 我完全明白。您可以创建 User 对象并设置任何属性,而不会造成属性混乱。至少有 3 个地方可以创建 User 对象 - 在 Personnel 对象的 save 方法中、处理 Personnel 表单的视图中,甚至在附加到 Personnel 类的 post_save 信号中。
  • 我无法编辑视图,因为它是 Django 内置的通用视图之一。我需要接受用户对电子邮件地址字段的输入,但这在我将该字段添加到我的模型之前不会显示。如果您查看我的代码,我已经在创建 User 并将其作为实例传递给 ModelForm。我不需要用户输入用户名,因为它是随机生成的,但确实需要输入 email。如果我没有电子邮件,我看不出post_save 信号对我有什么帮助,自定义save 方法也是如此。如果我在这里遗漏了什么并且听起来很愚蠢,请原谅我。
  • 不用担心。您始终可以向模型表单添加与模型上的字段不对应的字段,例如您需要在人员表单上的电子邮件字段。您可以获取该值并将其应用于您的用户对象。

标签: python django django-models properties django-forms


【解决方案1】:

您的问题有两个误解。首先,即使您已将其排除在外,您也需要为该字段传递一个值;其次,您不能包含不在模型中的字段。这些都不是真的。

绝对可以定义一个单独的email 字段,将其与真实模型字段一起显示在表单上,​​然后使用该值创建User 对象。

鉴于您不想修改视图来执行此逻辑(IMO 是错误的决定,但这是您的代码),您可以在覆盖的 save 方法中执行此操作:

class PersonnelForm(forms.ModelForm):
    email = forms.CharField()

    class Meta:
        model = Personnel
        exclude = ('user',)

    def save(self, *args, **kwargs):
        # call super method with commit=False, but remember original commit arg
        commit = kwargs.get('commit', True)
        kwargs['commit'] = False
        new_object = super(PersonnelForm, self).save(*args, **kwargs)
        user = User.objects.create_user(
            email=self.cleaned_data['user'],
            username=get_random_username(),
            password=User.objects.make_random_password()
        )
        new_object.user = user
        # now save if commit was not explicitly set to False
        if commit:
            new_object.save()
        return new_object

【讨论】:

  • 干杯丹尼尔。做到了。我的印象是您不能在 ModelForm 中拥有其他字段,但似乎可以。
  • 我遇到了一个小故障。你看,当我创建一个新记录时,你的方法似乎有效,但是当我使用 ModelForm 更新该记录时,它会尝试创建一个新的User。为了解决这个问题,我添加了一个简单的if 子句。 (见编辑)。我目前无法解决的另一个问题是如何在更新时显示personnel.user.email 字段的值。所有字段都直接从Personnel 记录更新,但emailUser 模型中。
猜你喜欢
  • 2022-01-19
  • 1970-01-01
  • 2020-05-03
  • 2020-05-02
  • 2011-03-27
  • 2011-01-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多