【问题标题】:POST to TestUpdateView updates existing object and creates new objectPOST 到 TestUpdateView 更新现有对象并创建新对象
【发布时间】:2019-10-21 00:23:15
【问题描述】:

我对 UpdateView 进行了测试:

  • 更新现有对象
  • 然后尝试同时创建一个新的空白对象

当我在浏览器中运行视图时不会发生这种行为,因此它与测试的运行方式有关。

  • 发布到更新 URL
  • 请求通过 UpdateView 运行
  • Form_valid() 运行并命中instance.save()
  • 然后某些东西导致在保存原始对象后立即创建第二个对象

有什么想法吗?

测试

class TestReviewUpdateView(TestCase):

    def setUp(self):
        self.review = ReviewFactory()
        self.submission = self.review.submission
        self.factory = RequestFactory()
        self.kwargs = {'submission_pk': self.submission.pk}

    def test_form_valid_with_object(self):
        self.request = self.factory.post(reverse(
            'submissions:review_update', kwargs=self.kwargs))

        # Create user
        self.request.user = StaffFactory()

        view = views.ReviewUpdateView()
        view.request = self.request
        view.object = self.review
        kwargs = {
            'scores': self.review.get_list_of_scores()
        }
        form = forms.ReviewForm(**kwargs)
        form.cleaned_data = {'axe_0': '4', 'axe_1': '4', 'axe_2': '4'}
        response = view.form_valid(form)
        assert response.status_code == 302

观点

class ReviewUpdateView(
    BaseReviewForm,
    UpdateView
):
    """ A view for updating reviews. """

    def dispatch(self, request, *args, **kwargs):
        self.submission = self.get_submission()
        self.conference = self.submission.conference
        return super().dispatch(request, *args, **kwargs)

def get_submission(self):
    return Submission.upcoming_objects.get_queryset(self.request.user).get(
            pk=self.kwargs['submission_pk'])

    def get_object(self):
        return self.model.objects.get(
            submission=self.submission,
            user=self.request.user)

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs.update({
            'scores': self.object.get_list_of_scores(),
        })
        return kwargs

 def save_scores(self, form, instance):
    for field in form.cleaned_data:
        # The score fields will be numbers:
        if "axe_" in field:
            form_field = form.fields[field]
            # Save the field's label as the key and score as value
            instance.scores[form_field.label] = form.cleaned_data[field]
    return instance

    def form_valid(self, form):
        instance = self.get_object()
        instance = self.save_scores(form, instance)
        instance.save()
        return super().form_valid(form)

表格

class ReviewForm(forms.ModelForm):
    """ Form for new reviews of submissions """
    class Meta:
        model = Review
        fields = []

    def __init__(self, *args, **kwargs):
        review_fields = kwargs.pop("review_fields")
        scores = kwargs.pop("scores")
        super().__init__(*args, **kwargs)
        if review_fields:
            for i in review_fields:
                self.fields["axe_%s" % i] = forms.ChoiceField(
                    choices=NUMBER_CHOICES,
                    label=review_fields[i],
                    widget=forms.RadioSelect)
                if scores:
                    self.fields["axe_%s" % i].initial = scores[int(i)]

        self.helper = FormHelper()
        self.helper.layout = Layout()
        for i in review_fields:
            self.helper.layout.fields.append(
                InlineRadios("axe_%s" % i)
            )
        self.helper.layout.fields.append(
            ButtonHolder(
                Submit('submit', 'Submit', css_class='btn btn-primary')
            )

模型

class Review(TimeStampedModel):
    """ Review is a model for collecting answers from reviewers """
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
    )
    user = models.ForeignKey(
        get_user_model(),
        null=False,
        on_delete=models.PROTECT)
    submission = models.ForeignKey(
        Submission,
        null=False,
        on_delete=models.CASCADE
    )
    scores = JSONField(null=True, blank=True)
    avg_score = models.DecimalField(max_digits=3, decimal_places=2, default=0)

    class Meta:
        unique_together = ("user", "submission")
        ordering = ['-avg_score', '-created']

    def save(self, *args, **kwargs):
        self.avg_score = self.calc_avg_score()
        import pdb; pdb.set_trace()
        # save() is called twice and when it runs a second time, it errors because no values are set
        super().save(*args, **kwargs)
        self.submission.save()

回答

@dirkgroten 为我指明了正确的方向。代码解决方案如下-

def test_form_valid_with_object(self):
    user = User.objects.create_superuser('foo', 'myemail@test.com', 'bar')
    self.review.user = user
    self.review.save()
    self.submission.conference.reviewers.add(user)
    self.client.login(username='foo', password='bar')
    response = self.client.post(
        reverse('submissions:review_update', kwargs=self.kwargs),
        data={'axe_0': '4', 'axe_1': '4', 'axe_2': '4'})
    self.assertEqual(302, response.status_code)

【问题讨论】:

  • 这不是您应该测试视图的方式:您应该只提交对该视图的请求 (response = self.factory.post(url, data={...})) 并对响应做出断言。不要尝试自己制作视图对象。
  • 谢谢,这也是我需要的反馈。

标签: django django-forms django-views django-testing


【解决方案1】:

你这样做是错误的。您应该在单独的测试中测试您的表单和视图。

通过使用data 实例化您的表单来测试您的表单,而不是使用instance 来创建对象,并添加instance 来进行对象更新。检查表单的有效性以获取有效和无效输入。例如:

form = ReviewForm(data=kwargs, instance=self.submission)
self.assertFalse(form.is_valid())
self.assertTrue(form.errors['some_field'])  # check some_field has an error

然后通过向他们发出请求并测试响应来测试您的视图:

self.client.force_login(user) # if you test for logged in user
response = self.client.post(url, data={...}) # this runs all your view code
self.assertEqual(302, response.status_code)  # form was valid
self.assertTrue(Review.objects.exists())
# or in case of invalid data
self.assertEqual(200, response.status_code)
self.assertTrue(response.context['form'].errors)

【讨论】:

  • 2 个问题 - 当我尝试断言 status_code 时,我收到了 AttributeError: 'WSGIRequest' object has no attribute 'status_code''。我本来希望响应会返回状态码?另外,您会将请求中的instance 传递给UpdateView
  • 什么是self.factory?如果您的测试用例是TestCase 的子类,您应该使用self.client。以上更新。
  • 通常UpdateView 的 url 包含要更新的对象的 id (/reviews/<id>)。所以它在url 中。也可能是您通过表单中的隐藏字段发布 id,然后它只是 data 中的一个键/值。
  • 在您的情况下,似乎要更新的对象来自self.get_submission()request.user,但我没有看到这种方法,所以我不知道你是如何得到它的。但显然它没有发布,也没有在网址中。
  • 想要显示代码示例 - 移至上方并接受您的。再次感谢您
猜你喜欢
  • 2017-10-24
  • 2017-02-25
  • 2019-03-12
  • 2015-09-24
  • 2021-01-14
  • 1970-01-01
  • 2015-02-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多