将经过身份验证的用户作为字段的 Django 表单验证
Posted
技术标签:
【中文标题】将经过身份验证的用户作为字段的 Django 表单验证【英文标题】:Django form validation with authenticated user as a field 【发布时间】:2012-05-12 11:04:02 【问题描述】:型号:
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 字段 - 它是当前用户,所以它应该由系统自动设置。但无论我如何尝试这样做,要么验证不起作用,要么还有其他错误。
在我尝试过的方法中(单独或组合):
在相关的 ModelField 中创建隐藏字段在ProjectTypeForm中定义init(以各种方式),例如:
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(ProjectTypeForm, self).__init__(*args, **kwargs)
self.fields['owner'].initial = self.user
在视图中设置值,如:
...
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
...
按照以下方式以各种方式覆盖表单的 clean() 方法:
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)
...
其中许多方法都基于在 ***.com 上找到的各种答案。但是,无论我尝试什么,我都找不到完成我需要的方法:(1)所有者字段的自动设置和(2)唯一性验证:所有者/类型名称和所有者/类型_slug。我遇到的典型错误是(a)owner 未被识别为用户(它被视为 PK),(b)不正确的验证(如缺少它或它错过了它相同的事实正在编辑的记录等),(c) 所有者是必填字段。
作为记录 - 如果 owner 是表单中的常规字段,则一切正常,但我不能允许用户设置 owner 值。
是否有任何优雅的解决方案来解决这个问题?
谢谢!
【问题讨论】:
【参考方案1】:从表单中排除所有者字段,并将用户保存在表单的 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 专家。所以,我会试试你的建议。 在我尝试之前的另一条评论@Greg - 仅供记录 - 我看到解决这个问题的唯一方法实际上是在该表单上使用“所有者”,因为它应该是一个 ChoiceField 但限制只能选择一个用户 - 当前登录的用户。我也没有尝试过,但理论似乎是合理的。我也会对此进行报告——尽管我希望基于一些 Django 诡计有一种更优雅的方法。 刚刚尝试了您的建议。完全符合我的预期:/type-project-create/ 重复键值处的 IntegrityError 违反了唯一约束“PROJECT_TYPE_slug_owner_id_key” 详细信息:键 (slug, owner_id)=(eee, 4) 已经存在。 我接受您的解决方案(出于同样的原因,我不喜欢我自己的解决方案 - 见上文)。但如果有人想实际使用公认的解决方案,请注意 - 这主要是一种概念方法。在实践中使用它需要注意一些事项。例如,如果 slug 值不在表单中(在 slug 被验证为必填字段之前),self.cleaned_data['slug'] 之类的东西将抛出 KeyError。底线 - clean() 中的更多编码以及在它可用之前需要对其进行一些逻辑更改。clean()
总是在所有其他字段都经过验证后被调用,所以如果slug
是必填字段,它就会一直存在。只是说。无论如何,你是对的,它不是生产就绪的代码!【参考方案2】:
正如我在评论中提到的,一种可能的解决方案本质上是与 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 的全部功能和灵活性。
【讨论】:
以上是关于将经过身份验证的用户作为字段的 Django 表单验证的主要内容,如果未能解决你的问题,请参考以下文章
在 Django 的身份验证用户模型中,将名字、姓氏作为必需属性而不是可选属性
从 Django Rest Framework 将经过身份验证的用户传递给 SendBird