Django表单验证中使用已认证用户作为字段

5

模型:

class ProjectType(models.Model):
    project_type_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=45, help_text='Type of project', verbose_name='Project Type')
    slug = models.SlugField(max_length=45, blank=True)
    description = models.CharField(max_length=400, help_text='Description of the main  purpose of the project', verbose_name='Project Type Description')
    default = models.BooleanField(default=False)
    owner = models.ForeignKey(User)
class Meta:
    ...
    unique_together = (('slug', 'owner'),('name', 'owner'))

我需要一个表单来创建/更新ProjectType。请注意owner字段 - 它应该是当前登录的用户。问题是如何确保在unique_together中的约束得到正确验证。
我不想在表单上显示owner字段 - 它是当前用户,因此应由系统自动设置。但无论我如何尝试,要么验证不起作用,要么会出现其他错误。
我尝试过的方法(单独或组合):
  • Creating a hidden field in the related ModelField
  • Defining init in ProjectTypeForm (in various ways), for example:

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user', None)
        super(ProjectTypeForm, self).__init__(*args, **kwargs)
        self.fields['owner'].initial = self.user
    
  • Setting values in the view like:

    ...
    if request.method == 'POST':
        project_type = ProjectType(owner=request.user)
        form = ProjectTypeForm(request.POST, instance=project_type, user = request.user.pk) # also tries w/o pk
    ...
    
  • Overriding clean() method of the form in various ways, along these lines:

    def clean(self):
        cleaned_data = super(ProjectTypeForm, self).clean()
        slug=cleaned_data.get('slug')
        owner = cleaned_data.get('owner')
    
        if slug:
            user = User.objects.get(pk=owner)
            ...
    
许多方法都基于stackoverflow.com上的各种答案。然而,无论我尝试什么,都找不到实现我所需的自动设置所有者字段和验证唯一性的方法:owner/type_name和owner/type_slug。(1)典型的错误是owner不被识别为用户(它被视为PK),(2)验证不正确(例如缺少或者它忽略了正在编辑的是同一记录等等)。(3)所有者是一个必填字段。
记录一下 - 如果在表单中 owner 是一个常规字段,则所有内容都按预期工作,但我不能允许用户设置 owner 值。
有没有任何优雅的解决方案呢?
谢谢!
2个回答

5

从表单中排除所有者字段,并在表单的init方法中保存用户-然后您可以使用它来验证表单,例如:

class ProjectTypeForm(...):
    ...
    def __init__(self, user, *args, **kwargs):
        super(ProjectTypeForm, self).__init__(*args, **kwargs)
        self.user = user

    def clean(self):
        user_projects = ProjectType.objects.filter(owner=self.user)
        if user_projects.filter(slug=self.cleaned_data['slug']):
            raise forms.ValidationError('...')
        elif user_projects.filter(name=self.cleaned_data['name']):
            raise forms.ValidationError('...')
        else:
            return self.cleaned_data

在创建新的ProjectType时,您可以按照以下方式进行操作:

if request.method == 'POST':
    form = ProjectTypeForm(request.user, request.POST)
    if form.is_valid():
        ptype = form.save(commit=False)
        ptype.owner = request.user
        ptype.save()

你不需要这个来保存现有的ProjectType对象。

谢谢。但我不认为这会起作用。你建议的问题在于,“所有者”(当前用户)是在调用form.is_valid()之后设置的——django表单验证不知道所有者是谁。因此,在验证期间没有办法检查约束条件——所有者/项目类型slug和项目类型名称的唯一性。会发生什么:我将从数据库引擎获得较低级别的异常(未被捕获)——我曾经遇到过这种情况。但我不是Django专家。所以,我会尝试你的建议。 - myrka
在我尝试之前,再对Greg说一句-仅供记录-我看到解决这个问题的唯一方法实际上是在该表单上使用"owner"作为ChoiceField,但仅限于一个用户的选择 - 当前已登录的用户。我也没有尝试过,但理论上似乎很可行。我也会报告这个问题,虽然我希望有一种基于Django巧技的更优雅的方法。 - myrka
刚刚尝试了您的建议。正如我所预料的那样:在/type-project-create/处出现了IntegrityError错误,重复键值违反了唯一约束条件"PROJECT_TYPE_slug_owner_id_key",详细信息是:(slug, owner_id)=(eee, 4)已经存在。 - myrka
我接受你的解决方案(出于与上文相同的原因,我不喜欢自己的解决方案)。但是,如果有人想要实际使用这个被接受的解决方案,请注意——它主要是一个概念性的方法。在实践中,需要注意许多事情才能使用它。例如,像self.cleaned_data['slug']这样的东西,如果表单中没有slug值(在slug被验证为必填字段之前),就会抛出KeyError。底线是,在clean()中需要更多的编码和一些逻辑变化才能使其可用。 - myrka
clean()总是在所有其他字段验证之后调用,因此如果slug是必填字段,则它将始终存在。只是这样说而已。无论如何,你是对的,这不是生产就绪的代码! - Greg
正如你所说,它总是被称为。因此,如果在代码中未指定slug(或name),你将会得到KeyError,因为slug将不存在。只需尝试一下,你就会明白。 - myrka

0

正如我在评论中提到的那样,一种可能的解决方案是使用 Django 表单并在表单上使用 owner 字段。因此,我已经按照以下方式修改了 init

def __init__(self, user, *args, **kwargs):
    super(ProjectTypeForm, self).__init__(*args, **kwargs)
    self.fields['owner'] = forms.ModelChoiceField(
        label='Owner*',
        queryset=User.objects.filter(username=user.username),
        help_text="Project types are unique to logged-in users who are set as their owners.",
        required=True,
        empty_label=None)

基本上,它仍然使用ChoiceField,但将其设置为一个选项-当前用户。此外,empty_label=None确保没有“空”选择。效果是(由于用户名是唯一的),当前用户名可见并且是除了更多选择之外的下拉列表中唯一的选择。

在视图中,我遵循这种方法:

...
if request.method == 'POST':
    project_type = ProjectType()
    form = ProjectTypeForm(request.user,request.POST, instance=project_type,)
    if form.is_valid():
        project_type.save()

        return HttpResponseRedirect(reverse('project_types'))
else:
    form = ProjectTypeForm(request.user)
...

基本上就是这样 - 唯一约束(以及整个事情)的验证非常好用。

我喜欢这个解决方案吗?不。我认为它是一个Hack(具有讽刺意味的是,即使它符合标准的Django方法)。但它需要完全不必要的东西。这种方法的一个好处是它清楚地向当前用户传达他/她被设置为项目类型所有者的信息。但是即使有这个想法,我宁愿显示一条消息(而不是字段),当前用户X将被设置为正在创建的项目类型的所有者。因此,如果有人有更好的解决方案,请提交以展示Django的全部功能和灵活性。


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