What's the best approach for handling deletion of an object with some validation before the object is deleted? For example, in my setup have two models - Game
and Team
(which are obviously related). Users should only be able to delete teams that are NOT tied to any games.
I created a form (without any fields) for deleting a team...
class TeamDeleteForm(ModelForm):
class Meta:
model = Team
fields = []
def clean(self):
# Check to see if this team is tied to any existing games
if self.instance.gameteams_set.exists():
raise ValidationError("This team is tied to 1 or more games")
return super().clean()
But then I realized that the class based view DeleteView doesn't have any sort of form_valid() method. Should I extend the generic FormView instead of DeleteView or is there a better approach that I'm missing?
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 Team
s with associated Game
s.
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.
One other way to do it would be to work with django.db IntegrityError!
from django.db import IntegrityError
class TeamDeleteView(DeleteView):
model = Team
def delete(self, request, *args, **kwargs):
"""If DB Integrity Error, display msg and redirect to list"""
try:
return(super().delete(request, *args, **kwargs))
except IntegrityError:
messages.error(request, "This team is tied to 1 or more games")
return render(request, template_name=self.template_name, context=self.get_context_data())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With