使用django-extra-views的UpdateWithInlinesView更新用户密码异常
解决Django-extra-views中密码更新被父类方法覆盖的问题
这个问题我之前也踩过坑,核心原因是你在forms_valid里手动更新了用户密码,但父类的forms_valid方法会重新保存表单绑定的Users实例——而你的AdminProfileForm并没有把密码相关字段纳入配置,表单保存时会用实例原有数据覆盖你刚设置的新密码。
问题根源拆解
你当前的代码逻辑存在一个关键漏洞:
- 手动获取
self.request.user设置新密码并保存 - 调用父类
forms_valid,它会处理AdminProfileForm对应的Users实例 - 由于表单只包含
first_name和last_name,保存时会忽略你手动修改的密码,用实例原本的旧密码覆盖回去
最优解决方案:在表单的clean方法中处理密码
把密码验证和设置逻辑移到表单的clean方法里,这样在表单验证阶段就更新好实例的密码,父类保存时自然会用更新后的实例:
修改表单定义
class AdminProfileForm(forms.ModelForm): class Meta: model = Users fields = ['first_name', 'last_name'] # 设置为非必填,允许用户不修改密码 password1 = forms.CharField(widget=forms.PasswordInput, label='New Password', required=False) password2 = forms.CharField(widget=forms.PasswordInput, label='Confirm Password', required=False) def clean(self): cleaned_data = super().clean() password1 = cleaned_data.get('password1') password2 = cleaned_data.get('password2') # 仅当用户输入新密码时才执行验证逻辑 if password1 or password2: if password1 != password2: raise forms.ValidationError("Passwords do not match.") # 直接在表单绑定的instance上设置密码 self.instance.set_password(password1) return cleaned_data
简化视图的forms_valid方法
现在不需要在视图里手动处理密码了,父类方法会自动保存已经更新好密码的实例:
class AdminProfile(BaseContext, UpdateWithInlinesView): # 你的其他配置(比如template_name等) inlines = [AdminInline] form_class = AdminProfileForm def get_object(self): return Users.objects.get(pk=self.request.user.id) def forms_valid(self, form, inlines): # 父类会自动保存包含新密码的form.instance return super().forms_valid(form, inlines)
另一种思路:在视图中直接更新表单实例
如果你不想修改表单,也可以在视图里先更新表单绑定的实例,再调用父类方法:
def forms_valid(self, form, inlines): password1 = form.cleaned_data['password1'] password2 = form.cleaned_data['password2'] if password1 and password2: if password1 != password2: raise forms.ValidationError("Passwords don't match") # 注意这里修改的是form.instance,而非self.request.user form.instance.set_password(password1) # 现在父类保存的是已经更新密码的实例 return super().forms_valid(form, inlines)
这种方法也能解决问题,但不如在表单里处理更符合Django的表单验证逻辑,推荐第一种方案。
内容的提问来源于stack exchange,提问作者ACA
相关产品推荐
相关产品推荐

