Validation using DeleteView before deleting instance

I think the best approach will be overriding the model's delete method. For example:

class Team(models.Model):
    ...
    def delete(self, *args, **kwargs):
        if Game.objects.filter(team__pk= self.pk).exists():
            raise Exception('This team is related to a game.')  # or you can throw your custom exception here.
        super(Team, self).delete(*args, **kwargs)

For your particular case I would simply override the queryset attribute of your view to exclude Teams with associated Games.

class TeamDeleteView(DeleteView):
    queryset = Team.objects.distinct().exclude(games__isnull=False)

There's a Django ticket opened to make the DeleteView behave like other form views but until the proposed patch is merged and released (It won't make it in 1.8) you'll have to completely override the delete method of your view like the following:

class TeamDeleteView(DeleteView):
    model = Team

    def delete(request, *args, **kwargs):
        self.object = self.get_object()
        if self.object.gameteams_set.exists():
            # Return the appropriate response
        success_url = self.get_success_url()
        self.object.delete()
        return HttpResponseRedirect(success_url)

Edit:

From your accepted solution it looks like you're trying to prevent deletion at the model level. Such enforcement should be done by using a PROTECT on_delete handler.

from django.db import models

class Team(models.Model):
    pass

class Game(models.Model):
    team = models.ForeignKey(Team, on_delete=models.PROTECT)

You'll still have to deal with the raised ProtectedError in your view:

from django.db import models
from django.http.response import HttpResponseForbidden

class TeamDeleteView(DeleteView):
    model = Team

    def delete(request, *args, **kwargs):
        try:
            return super(TeamDeleteView, self).delete(
                request, *args, **kwargs
            )
        except models.ProtectedError as e:
            # Return the appropriate response
            return HttpResponseForbidden(
                "This team is tied to 1 or more games"
            )

You could even use the protected_objects property of e to display a more meaningful error message just like the admin does.


I've used both DeleteView and FormView for this scenario. Both have their pros and cons.

DeleteView is nice because it's based on the SingleObjectMixin and you can easily get access to the object you want to delete. One nice way of doing this is raising an exception in get_object. This makes it so that you can raise exception on both get and post.

def get_object(self, qs):
  obj = super(FooView, self).get_object(qs)
  if obj.can_delete():
    return obj
  raise PermissionDenied

FormView is nice because you can leverage form_invalid and the clean methods, but then you still have to do the work to get the object, setup some sort of form (not needed in deleteview).

It's really a matter of how you want to tackle it. Some other questions are: do you raise an exception on GET, or do you want to show a nice page letting the user know they can't delete the object. This can be done in both View types.

Update your question if you have more points to go over and i'll update my response.