POST到TestUpdateView更新现有对象并创建新对象

时间:2019-06-05 13:21:50

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

我对一个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)

1 个答案:

答案 0 :(得分: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)
相关问题