Wednesday 21 July 2021

Wagtail - Disable deletion of Snippets that are assigned to Model

 This task ran me around in circles somewhat, until we found the solution which was literally 1 line of code. 
Hopefully if you've stumbled across this blog it may save you losing the time that I did on it. 


Let's have a closer look at the problem we're looking to fix here.  

So let's say we have a 'ElectricCarPage' and when creating the Page one of our fields is for 'Manufacturer' .  Which we are using Snippets for.   
On the  'ElectricCarPage'  we have this set as 'required' .  So one has to be added, however there is a way that the Administration could leave our Page without a Manufacturer ( and possible breaking the site ) .  And that would be to do through the Snippets admin and remove the Snippet here 


The Solution 
___________

So let's cut straight to the chase and give you the solution and then I'll go through the other methods that I'd been through and their gotchas. 

+ Add  'on_delete=models.PROTECT'  to the Manufacturer field in the 'ElectricCarPage' class.  Like

class CarPage(Page):
manufacturer = ForeignKeyField(
"car.Manufacturer", verbose_name=_("Manufacturer"), on_delete=models.PROTECT, related_name="manufacturer", required=True, help_text=_( "The car manufacturer " ), )





This solution gives you the above screen when you try and delete the Snippet. 



THE SOLUTIONS THAT DIDN'T WORK


FIX ATTEMPT 1


on_delete=models.RESTRICT

This is to be added in the GamePage model like so.


class CarPage(Page):
manufacturer = ForeignKeyField(
"car.Manufacturer", verbose_name=_("Manufacturer"), on_delete=models.RESTRICT, related_name="manufacturer", required=True, help_text=_( "The car manufacturer " ), )

However rather than the screenshot above after you try and delete it throws an error. FIX ATTEMPT 2

The next solution is to use the following code in the Developer Snippet

1def delete(self, *args, **kwargs): 2 CarPage = apps.get_model("cars", "CarPage") 3 carpages = CarPage.objects.filter(manufacturer=self) 4 if carpages: 5 print("@todo Let the user know that they can't delete!") 6 return 7 else: 8 super().delete(*args, **kwargs)

 

However I met another brick wall when trying to complete this and that’s in that I can output to a message as there is no request to use in this method. I really feel that there should be a way to resolve this method, so if you have one then please let me know, it'd be very useful for future development. FIX ATTEMPT 3

So I then thought I could use this code in a snippet hook .
Hooks — Wagtail Documentation 2.13.4 documentation

1from django.http import HttpResponse 2 3from wagtail.core import hooks 4 5@hooks.register('before_delete_snippet') 6def before_snippet_delete(request, instances): 7 # "instances" is a QuerySet 8 total = len(instances) 9 10 if request.method == 'POST': 11 # Override the deletion behaviour 12 instances.delete() 13 14 return HttpResponse(f"{total} snippets have been deleted", content_type="text/plain")

 

That hook works if you go through  'Admin>Snippets>Manufacturer' . However I also have a custom Admin menu items for ' Manufacturer' and if you navigate through that way the hook isn't called.

I think this is a real bug for Snippet Hooks. Even if you’re not using separate Admin menu items then whose not to say it may not be requested in the future.





No comments: