【问题标题】:A better way of setting values in CreateView?在 CreateView 中设置值的更好方法?
【发布时间】:2013-10-03 19:32:21
【问题描述】:

models.py:

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    post_body = models.TextField()
    user = models.ForeignKey(User)
    pub_date = models.DateTimeField('published')

    def get_absolute_url(self):
        return u'/entries/%d' % self.id

forms.py:

class NewPostForm(forms.ModelForm):
    class Meta:
        model = BlogPost
        fields = ('title', 'post_body')

所以,我试图让用户在博客中发布新帖子。 BlogPost 模型有 4 个字段 - titlepost_bodyuserpub_date。用户应该能够填写titlepost_body,而其他两个应该自动填写。我尝试的第一件事是:

class NewPostView(generic.edit.CreateView):
    model = BlogPost
    form_class = NewPostForm

    def get_initial(self):
        return {'pub_date': timezone.now(),
                'user': self.request.user}

但它唯一做的就是为 html 表单提供默认值。经过进一步研究,这是最终工作的代码:

class NewPostView(generic.edit.CreateView):
    model = BlogPost
    form_class = NewPostForm

    def form_valid(self, form):
        obj = form.save(commit=False)
        obj.user = self.request.user
        obj.pub_date = timezone.now()
        obj.save()
        return HttpResponseRedirect(obj.get_absolute_url())

所以,虽然覆盖 form_valid 方法有效,但我不觉得应该怎么做?有没有更好的地方来执行这些操作?这样做的传统方法是什么?

【问题讨论】:

    标签: python django


    【解决方案1】:

    您可以覆盖form_valid,然后在返回语句中调用super().form_valid(form)。您不必担心保存表单、保存对象或返回 HttpResponseRedirectect。所有这些都将由超级处理。您需要做的就是设置您想要在幕后控制的form.instance 的值。

    views.py

    class NewPostView(generic.edit.CreateView):
        model = BlogPost
        form_class = NewPostForm
    
        def form_valid(self, form):
            form.instance.user = self.request.user
            form.instance.pub_date = timezone.now()
            return super().form_valid(form)
    

    这将是一种在幕后添加值的简洁方法,除了添加值之外,您不需要执行任何逻辑操作。您可以在 django 文档中找到对该逻辑的引用:

    https://docs.djangoproject.com/en/2.1/topics/class-based-views/generic-editing/#models-and-request-user


    理想情况下,对于pub_date,您希望将字段设置为使用auto_now_add 自动记录创建时间。

    models.py

    class BlogPost(models.Model):
        title = models.CharField(max_length=200)
        post_body = models.TextField()
        user = models.ForeignKey(User)
        pub_date = models.DateTimeField('published', auto_add_now=True)
    
        def get_absolute_url(self):
            return u'/entries/%d' % self.id
    

    您可以在以下链接中找到有关 DateTimeField 的文档及其对 auto_add_now 参数的引用。它还有一个auto_add,每次修改条目时都会更新,以防您需要last_edited_date 字段。

    https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.DateTimeField


    此外,您的用户有可能无需登录即可访问表单。您需要处理未设置self.request.user 的情况,或者您可以使用LoginRequiredMixin 仅允许登录用户访问此表单。我在使用 mixin 时更轻松了。

    具有上述所有更改的 view.py 将如下所示。

    views.py

    from django.contrib.auth.mixins import LoginRequiredMixin
    from django.views.generic import CreateView
    
    from .forms import NewPostForm
    from .models import BlogPost
    
    class NewPostView(LoginRequiredMixin, CreateView):
        model = BlogPost
        form_class = NewPostForm
    
        def form_valid(self, form):
            form.instance.user = self.request.user
            return super().form_valid(form)
    

    【讨论】:

      【解决方案2】:

      这正是它的完成方式,除了您的 form_save 方法(不应该返回响应):

      1. 您可以在pub_date 字段中使用auto_now_add,它会在首次创建模型时添加当前时间戳(省去您的麻烦)。

      2. 您还可以在 success_url 参数中使用模型中的字段,如下所示:

        class CreateNewPost(generic.edit.CreateView):
            model = BlogPost
            form_class = NewPostForm
            success_url = '/entries/%(id)s'
        
            def form_valid(self, form):
                obj = form.save(commit=False)
                obj.user = self.request.user
                obj.pub_date = timezone.now()
                obj.save()
        
      3. 如果您需要自定义返回 URL,则应覆盖 get_success_url,而不是返回来自 form_valid 的响应。

      【讨论】:

      • 添加auto_add_now=True后开始出现这个错误:'BlogAdmin.fieldsets[1][1]['fields']' refers to field 'pub_date' that is missing from the form.找到答案:stackoverflow.com/questions/3516799/…
      • 另一件事 - 在设置 success_url 并从 form_valid 中删除 ResponseRedirect 后,我收到此错误:The view blog.views.NewPostView didn't return an HttpResponse object.
      【解决方案3】:

      另一种方法是将保存行为放在表单本身上。

      class NewPostForm(forms.ModelForm):
          class Meta:
              model = BlogPost
              fields = ('title', 'post_body')
      
          def __init__(self, *args, **kwargs):
              self.user = kwargs.pop('user')
              super(NewPostForm, self).__init__(*args, **kwargs)
      
          def save(self):
              obj = super(NewPostForm, self).save(commit=False)
              obj.user = self.user
              obj.pub_date = timezone.now()
              obj.save()
              return obj
      

      这很好地在视图之外以更可重用的方式封装了逻辑。

      您需要覆盖视图上的get_form_kwargs 以传递用户,或使用django-braces 中的UserFormKwargsMixin 类以确保在初始化表单时视图传递user

      请注意,django-braces 还提供了一个 UserKwargModelFormMixin,您可以在表单上使用它而不是覆盖 __init__()。如果你采用大括号的方法,你最终会得到这样的结果:

      forms.py

      class NewPostForm(UserKwargModelFormMixin, forms.ModelForm):
          class Meta:
              model = BlogPost
              fields = ('title', 'post_body')
      
          def save(self):
              obj = super(NewPostForm, self).save(commit=False)
              obj.user = self.user
              obj.pub_date = timezone.now()
              obj.save()
              return obj
      

      views.py

      class NewPostView(UserFormKwargsMixin, generic.edit.CreateView):
          model = BlogPost
          form_class = NewPostForm
      

      【讨论】:

      • 我在尝试从kwargs 发送pop 'user' 时收到KeyError。我猜它没有传递给 NewPostForm 的__init__
      • 没错,确保您在视图中覆盖get_form_kwargs,或者在视图中包含UserFormKwargsMixin
      【解决方案4】:

      您还可以在模型中将默认值指定为default = timezone.now(),这样无论何时创建对象,它都会自动采用当前时间。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-19
        • 2015-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-19
        相关资源
        最近更新 更多