HEADSUP: I don't know if this is a duplicate. There are many with similar titles one, but they aren't answering my question. If it is a duplicate, however, please inform me.
I have this model called Item:
class Item(models.Model):
title = models.CharField(max_length=120)
date_created = models.DateTimeField(default=timezone.now)
deadline = models.DateTimeField(default=timezone.now() + timedelta(5))
task = models.ForeignKey(Task, on_delete=models.CASCADE)
is_completed = models.BooleanField(default=False)
If you see, Item is referencing a ForeignKey called Task:
class Task(models.Model):
title = models.CharField(max_length=120)
description = models.TextField()
date_created = models.DateTimeField(default=timezone.now)
deadline = models.DateTimeField(default=timezone.now() + timedelta(5))
author = models.ForeignKey(User, on_delete=models.CASCADE)
is_completed = models.BooleanField(default=False)
I have a delete view for Item:
class DeleteItemView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Item
template_name = 'function/delete_item.html'
success_url = reverse('all-items')
def test_func(self):
item = get_object_or_404(Item, id=self.kwargs['pk2'])
if item.task.author == self.request.user and not item.task.is_completed:
return True
return False
def get_context_data(self, **kwargs):
context = super(DeleteItemView, self).get_context_data(**kwargs)
context['task'] = Task.objects.filter(id=self.kwargs['pk'])[0]
return context
The url pattern for the delete view is:
path('task/<int:pk>/item/<int:pk2>/delete/', DeleteItemView.as_view(), name='delete-item'),
pk
is for the Task model and pk2
is for the Item model.
Now, my problem is this. Whenever I try to go to this delete view, it results in an error, saying there is no item found matching the query (404). And it says Raised by: function.views.DeleteItemView
. I am at my wits end, spending almost half a day trying to figure this out, but with no success.
Any help or answers will be immensely and greatly appreciated.
The problem here is a lack of understanding of class based views that deal with single objects. Any view that deals with a single object inherits from SingleObjectMixin
[Django docs] down the line. SingleObjectMixin
gets the relevant object using either (or both) the pk or slug passed as keyword arguments to the view.
The name of the kwarg for pk is denoted by the pk_url_kwarg
which defaults to pk
, (meaning if there is a kwarg pk
passed to the view it will be used for getting the object) and the kwarg for the slug
is denoted by slug_url_kwarg
.
In your view you deal with two objects one Item
and the other Task
. And the generic view is supposed to deal with the Item
. The problem here is you pass the pk for the Item
as pk2
and for the Task
as pk
! The view of course considers that pk
is for the instance it needs to deal with and hence you get the error.
One solution would be to interchange the names of the two kwargs, but we can do better than that. pk
and pk2
are not very descriptive, yes? Lets change their names to be more descriptive.
In your url pattern, change the names of the captured arguments:
path('task/<int:task_pk>/item/<int:item_pk>/delete/', DeleteItemView.as_view(), name='delete-item'),
In your view, set pk_url_kwarg
and also correct the names:
class DeleteItemView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Item
template_name = 'function/delete_item.html'
success_url = reverse('all-items')
pk_url_kwarg = 'item_pk' # set this
def test_func(self):
item = get_object_or_404(Item, id=self.kwargs['item_pk'])
if item.task.author == self.request.user and not item.task.is_completed:
return True
return False
def get_context_data(self, **kwargs):
context = super(DeleteItemView, self).get_context_data(**kwargs)
context['task'] = Task.objects.filter(id=self.kwargs['task_pk'])[0]
return context
In fact one thing to note would be that if the Task
is the one that is related to the Item
you don't even need to pass it in your url, you can simply write:
task = item.task