I have a test for an UpdateView that is:
This behavior does not happen when I run through the view in the browser, so it is something to do with how the test is running.
instance.save()
Any ideas?
The Test
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
The View
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)
The 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')
)
The Model
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()
Answer
@dirkgroten pointed me in the right direction. The code solution is the following-
def test_form_valid_with_object(self):
user = User.objects.create_superuser('foo', '[email protected]', '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)
You're doing this the wrong way. You should test your form and views in separate tests.
Test your form by instantiating it with data
and no instance
for object creation and adding the instance
for object updates. Check validity of form for valid and invalid input. E.g:
form = ReviewForm(data=kwargs, instance=self.submission)
self.assertFalse(form.is_valid())
self.assertTrue(form.errors['some_field']) # check some_field has an error
Then test your views by just making a request to them and testing the response:
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)