Hi all
I am slowly chipping away at a task manager app, and I’ve come across a testing issue that I cannot explain.
By way of background, I have a Task model that stores tasks, including the creator (a FK to a custom User model) of each task. By default, tasks are visible to all users and the model also has a boolean field named ‘private’ that if true means that task private to the creator. (The actual logic to check that is part of a custom QuerySet manager on the model and called from the generic CBV views.) I have a TaskForm (a model form relying on the Task model, used for creating and updating tasks). At the moment, any user can edit a task that is not private, including making a task private - even tasks that were created by another user.
I’ve made some changes to stop a user from making other creator’s tasks private.
In views.py
, I’ve over-ridden get_form_kwargs()
to pass the logged-in user id
from the session, so it is accessible in the form. (All pages require the user to be logged in.)
class TaskUpdateView(UpdateView):
model = Task
form_class = TaskForm
template_name = "tasks/task_detail.html"
success_url = reverse_lazy("tasks:list")
def get_form_kwargs(self):
kwargs = super(TaskUpdateView, self).get_form_kwargs()
kwargs['user_id'] = self.request.session.get("_auth_user_id")
return kwargs
In forms.py
, I’ve overridden __init__()
to bring the logged-in user id from the view and overridden clean_private()
with the logic to check whether the form instance matches the logged-in user from the session. I have opted to override the cleaning of only the private field, rather than the whole clean() method, since this logic only applies when that field is changing in the form.
class TaskForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user_id", None)
super(TaskForm, self).__init__(*args, **kwargs)
def clean_private(self):
private = self.cleaned_data["private"]
user_id = int(self.user)
creator_id = self.instance.creator.id
if user_id != creator_id:
raise ValidationError(
"As you did not create this task, you cannot set it private."
)
return private
If the creator and current user do match, the form is processed and the update proceeds. If they don’t match, a ValidationError is raised and the form re-rendered with the error message in the template.
This all works, as expected, and if I test it in the browser, an error is rendered as a form.field error.
Unfortunately, when I try to test this with Django’s testing system, I cannot get the Validation Error to trigger the test.
def setUp(self):
... [sets up two users] ...
... [logs in as test_user_1] ...
... [instantiates a task created by test_user_2] ...
self.task_form_data = {
"creator": self.test_user_2,
"text": "Basic test task",
"private": "True",
}
def test_user_tries_to_change_another_creators_task_to_private_post_request(self):
with self.assertRaises(ValidationError):
response = self.client.post(
path=reverse("tasks:edit", args=[self.task.pk]), data=self.task_form_data, follow=True
)
I’ve checked the content of the response, which includes the form correctly re-rendered and a status of 200. That response includes the error message in the right place in the template, which suggests the Validation Error was triggered but not picked up in the test.
I’ve tried making follow=False
and I’ve tried replacing the Validation Error in the form with add_error()
(and I also have the template set up to catch non-field errors), but the test still fails.
So, two questions:
- Any suggestions on why the test isn’t passing?
- Are there better ways to approach this problem?