Django:仅更新UpdateView中已更改的字段

Posted

技术标签:

【中文标题】Django:仅更新UpdateView中已更改的字段【英文标题】:Django: Only update fields that have been changed in UpdateView 【发布时间】:2013-08-22 23:52:29 【问题描述】:

我正在使用 UpdateView 来更新一系列字段。但是,我只希望将已修改的字段保存到数据库中。如果在更新过程中没有为字段提供值,我希望将以前的值用作默认值。如果为字段提供了新值,则只应更新该字段。我该怎么做呢?

#views.py
class AccountUpdate(UpdateView):
""" Updates an account; unchanged fields will not be updated."""
context_object_name = 'account'
form_class = UpdateForm
template_name = 'accounts/update_account.html'
success_url = '/accounts/home'

def get_object(self, queryset=None):
    return self.request.user

def form_valid(self, form):
    clean = form.cleaned_data
    #encrypt plain password
    form.instance.password = hash_password(clean['password'])
    context = 
    self.object = context.update(clean, force_update=False)
    return super(AccountUpdate, self).form_valid(form)

#templates
% extends 'base.html' %
<title>% block title %Update Account% endblock %</title>
% block content %
 account.non_field_errors 
<div class="errors" style="list-style-type: none;">
    % if account.first_name.errors % account.first_name.errors % endif %
    % if account.last_name.errors % account.last_name.errors % endif %
    % if account.email.errors % account.email.errors % endif %
    % if account.password.errors % account.password.errors % endif %
    % if account.username.errors % account.username.errors % endif %
</div>
<form action="" name="edit" method="post">
    % csrf_token %
    <ul>
        <li>Fullname</li>
       <li>  form.first_name  form.last_name </li>
        <li>Email</li>
        <li> form.email </li>
        <li>Password</li>
        <li> form.password </li>
        <li>Username</li>
        <li> form.username </li>
    </ul>
    <ul>
        <li><input type="submit" value="update account"></li>
    </ul>
</form>
<ul>
    <li class="nav"><a href="/accounts/">cancel changes</a></li>
</ul>
% endblock %

models.py 中还根据需要声明了所有字段。目前该表单仅在我为每个字段提供值时才有效。

【问题讨论】:

【参考方案1】:

我在更新过程中使用自定义哈希来加密密码。当我访问编辑页面并点击更新按钮时,当前加密形式的旧密码被重新加密,因此丢失旧密码

我会通过不在表单中包含 password 本身来处理这个问题。相反,我会添加一个新字段(类似于new_password)以允许输入新密码。然后,在您的 is_valid 方法中,将密码设置为该字段的哈希值(如果有内容)。

您还应该使用sensitive value filtering 工具来防止用户密码出现在通过电子邮件发送的错误报告中。

class UpdateForm(forms.ModelForm):
    class Meta:
        model = user
        fields = ('first_name', 'last_name', 'email', 'username')
    new_password = forms.CharField(required=False, widget=forms.widgets.PasswordInput)

然后在你看来:

@sensitive_variables('new_password')
@sensitive_post_parameters('new_password')
def form_valid(self, form):
    clean = form.cleaned_data
    new_password = clean.get('new_password')
    if new_password:
        #encrypt plain password
        form.instance.password = hash_password(new_password)
    return super(AccountUpdate, self).form_valid(form)

【讨论】:

感谢您的解决方案。根据您对 Rob 回答的评论,我想我将能够解决个别字段更新问题。完成后我会发布我的解决方案。谢谢! 我不太确定您还有什么其他字段更新问题 - 如果没有更改,设置 first_name = "Joe" 应该是无害的。 如果 update_form 预先填充了first_name = 'joe',但用户决定删除更新页面上该字段中的值并将其留空,我希望first_name 默认返回@987654330 @。很抱歉造成混乱。 我明白了。这是使用通用视图更难设置的东西,至少如果您仍然允许某些字段合法地为空白的可能性。我想你可以遍历表单的 changed_data 名称,看看它们是否为空,如果是,则复制 self.object 版本的值。 if you're still allowing for the possibility of some of the fields legitimately being blank. 所有四个字段都是必需的。我还能继续循环吗?【参考方案2】:

最简单的方法是使用已经存在的内容预先填充字段。 使用 account.name 或其他方式在模板上执行此操作。

【讨论】:

我已经用我的模板文件更新了这个问题。当我进入编辑页面时,表单已经预先填充了除密码字段之外的值(不知道为什么)但是当我尝试保存表单时,除非我填写所有空白字段 password 上尝试render_value=True。见:docs.djangoproject.com/en/dev/ref/forms/widgets/… 好的,render_value=True 工作...唯一的问题是,我在更新过程中使用自定义哈希来加密密码。当我访问编辑页面并点击更新按钮时,当前加密形式的旧密码被重新加密,因此丢失了旧密码。另一方面,由于表单已经被预先填充,我该如何解决我最初的问题,即只更新更改的字段? 我能想到的唯一方法是使用一系列 if-elif 语句将数据库中的每个字段与提交的值进行匹配,以查看它们是否不同,然后执行单个字段更新查询。 .这听起来乏味且不符合 Python 风格 另外,您为什么在更新表单上显示密码?把它放在一个单独的表单上,或者如果用户没有更改他/她的密码,则让它为空。

以上是关于Django:仅更新UpdateView中已更改的字段的主要内容,如果未能解决你的问题,请参考以下文章

使用基于类的 UpdateView 在 Django 中更新用户模型

Django 中 UpdateView 上的日期格式更改

带有更新的 slug 的 Django 通用视图 UpdateView 重定向 URL

Django 使用 UpdateView 出现此错误:/user/2/edit 处的 NoReverseMatch

基于 Django 类的视图 - 具有两个模型表单的 UpdateView - 一个提交

在 Django 中,如何在提交 CreateView 时重定向到 UpdateView?