【问题标题】:Django update one field using ModelFormDjango 使用 ModelForm 更新一个字段
【发布时间】:2012-01-03 05:16:48
【问题描述】:

如果 POST 请求只有一个字段作为参数,我如何使用 ModelForm 更新实例中的一个字段? ModelField 尝试用 None 覆盖未在 POST 请求中传递的字段,从而导致数据丢失。

我有一个带有 +25 字段的模型

class C(models.Model):
    a = models.CharField(max_length=128)
    b = models.CharField(max_length=128)
    ...
    x = models.IntegerField()

我有一个桌面应用程序,它执行 POST 请求,以便通过 views.py 中公开的 api 方法编辑 C 的实例

在 api 方法中,我使用 ModelForm 来验证字段,如下所示:

form = CModelForm(request.POST, instance=c_instance)
if form.is_valid():
    form.save() 

在执行 save() 时,django 要么抱怨某些其他字段不能为空,要么(如果所有字段都是可选的)用 None 覆盖它们。

有人知道如何管理它吗?我会手动进行所有检查并手动更新,但是该模型的字段列表非常长……

【问题讨论】:

    标签: python django


    【解决方案1】:

    我很惊讶还没有人提到modelform_factory

    您可以通过创建特定版本的表单类、定义您感兴趣的字段子集轻松实现部分表单更新:

    from django.forms import modelform_factory
    
    ...
    
    foo = get_object_or_404(Foo, slug=foo_slug)
    
    if request.method == "POST": 
        PartialFooForm = modelform_factory(
            Foo, form=FooForm, fields=("my_first_field", "my_second_field",)
        )
        form = PartialFooForm(request.POST, instance=foo)
        if form.is_valid():
            foo = form.save()
            return redirect(foo)
    else:
        form = FooForm(instance=foo)
    

    【讨论】:

    • 谢谢你分享这个,Niko——它就像一个魅力。绝对是我遇到的解决此问题的最佳解决方案。
    【解决方案2】:

    我知道我参加聚会很晚了,但你能为表单创建一个工厂函数吗?

    def model_form_factory(cls, data, *args, **kwargs):
        """ Create a form to validate just the fields passed in the data dictionary.
    
        e.g. form = form_factory(MyModel, request.POST, ...)
    
        """
    
    
        data = data.copy()
        data.pop('csrfmiddlewaretoken', None)
    
        class PartialForm(forms.ModelForm):
            class Meta:
                model  = cls
                fields = data.keys()
    
        return PartialForm(data, *args, **kwargs)
    

    【讨论】:

      【解决方案3】:

      好吧,你必须创建方法来初始化你的请求中缺少数据的表单,比如:

      class MyModel(Model):
          ... your model ...
      
          def initial(self,data):
              fields = [list of possible fields here]
      
              for field in fields:
                  if data.get(field) is None:
                      data[field] = getattr(self,field)
      
              return data
      

      然后,在您的表单中传递这些数据,例如:

      form = MyForm(instance.initial(request.POST.copy()),instance=instance)
      

      对于 JSON:

      from json import loads
      
      form = MyForm(instance.initial(loads(request.body)),instance=instance)
      

      【讨论】:

        【解决方案4】:

        我解决了这个类似于 Roman Semko 的答案(可能不适用于 ManyToMany 字段):

        更改表单的 __init__ 方法以更新数据:

        import urllib
        
        from django import forms
        from django.http import QueryDict
        from django.forms.models import model_to_dict
        
        class MyModelForm (forms.ModelForm):
            class Meta:
                model = MyModel
        
            def __init__ (self, *args, **kwargs):
                super(MyModelForm, self).__init__(*args, **kwargs)
        
                # if form has being submitted and 
                # model instance exists, then get data
        
                if self.is_bound and self.instance.pk:
        
                    # get current model values
                    modeldict = model_to_dict( instance )
                    modeldict.update( self.data.dict() )
        
                    # add instance values to data
                    urlencoded = urllib.urlencode( modeldict )
                    self.data = QueryDict( urlencoded )
        

        【讨论】:

          【解决方案5】:

          Django 的ModelForm 并非旨在处理模型上任意字段子集的部分更新。

          由 OP 描述的用例,一个访问 API 的桌面应用程序,Django Rest Framework 会更好地处理。

          具体来说,您将创建一个继承自ModelSerializer 的序列化程序,然后在UpdateAPIView 中使用它。 DRF 中的序列化器类似于 Django 中的表单。

          如果您不介意使用 DRF 添加另一个依赖项,这是一个非常棒的库,这可能比滚动您自己的解决方案要好。

          class MyModelSerializer(serializers.ModelSerializer):
          
              class Meta:
                  model = MyModel
                  fields = '__all__'
          
          
          class MyModelDetail(generics.RetrieveUpdateAPIView):
              queryset = MyModel.objects.all()
              serializer_class = MyModelSerializer
          

          【讨论】:

            【解决方案6】:

            想通了。我所做的是使用实例中的值更新 request.POST 字典 - 以便所有未更改的字段都自动出现。这样就可以了:

            from django.forms.models import model_to_dict
            from copy import copy
            
            def UPOST(post, obj):
                '''Updates request's POST dictionary with values from object, for update purposes'''
                post = copy(post)
                for k,v in model_to_dict(obj).iteritems():
                    if k not in post: post[k] = v
                return post
            

            然后你就这样做:

            form = CModelForm(UPOST(request.POST,c_instance),instance=c_instance)
            

            【讨论】:

              【解决方案7】:

              您可以通过如下指定这些字段来使用 ModelForm 中的部分字段:

              class PartialAuthorForm(ModelForm):
                  class Meta:
                      model = Author
                      fields = ('name', 'title')
              

              来自文档:

              如果在使用 ModelForm 创建表单时指定字段或排除, 那么不在结果表单中的字段将不会被设置 表单的 save() 方法。

              https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#using-a-subset-of-fields-on-the-form

              【讨论】:

              • 问题是我不知道要更新哪个字段:-/
              猜你喜欢
              • 1970-01-01
              • 2012-04-29
              • 1970-01-01
              • 2021-01-22
              • 2012-10-24
              • 2018-11-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多