Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Admin - how to prevent deletion of some of the inlines

I have 2 models - for example, Book and Page. Page has a foreign key to Book.

Each page can be marked as "was_read" (boolean), and I want to prevent deleting pages that were read (in the admin).

In the admin - Page is an inline within Book (I don't want Page to be a standalone model in the admin).

My problem - how can I achieve the behavior that a page that was read won't be deleted? I'm using Django 1.4 and I tried several options:

  1. Override "delete" to throw a ValidationError - the problem is that the admin doesn't "catch" the ValidationError on delete and you get an error page, so this is not a good option.
  2. Override in the PageAdminInline the method - has_delete_permission - the problem here -it's per type so either I allow to delete all pages or I don't.

Are there any other good options without overriding the html code?

Thanks, Li

like image 960
Lin Avatar asked Jan 22 '13 20:01

Lin


3 Answers

The solution is as follows (no HTML code is required):

In admin file, define the following:

from django.forms.models import BaseInlineFormSet

class PageFormSet(BaseInlineFormSet):

    def clean(self):
        super(PageFormSet, self).clean()

        for form in self.forms:
            if not hasattr(form, 'cleaned_data'):
                continue                     

            data = form.cleaned_data
            curr_instance = form.instance
            was_read = curr_instance.was_read


            if (data.get('DELETE') and was_read):            
                raise ValidationError('Error')



class PageInline(admin.TabularInline):
    model = Page
    formset = PageFormSet
like image 107
Lin Avatar answered Nov 19 '22 20:11

Lin


You could disable the delete checkbox UI-wise by creating your own custom formset for the inline model, and set can_delete to False there. For example:

from django.forms import models 
from django.contrib import admin 

class MyInline(models.BaseInlineFormSet): 
    def __init__(self, *args, **kwargs): 
        super(MyInline, self).__init__(*args, **kwargs) 
        self.can_delete = False 

class InlineOptions(admin.StackedInline): 
    model = InlineModel 
    formset = MyInline 

class MainOptions(admin.ModelAdmin): 
    model = MainModel 
    inlines = [InlineOptions]
like image 3
chubao Avatar answered Nov 19 '22 20:11

chubao


Another technique is to disable the DELETE checkbox. This solution has the benefit of giving visual feedback to the user because she will see a grayed-out checkbox.

from django.forms.models import BaseInlineFormSet

class MyInlineFormSet(BaseInlineFormSet):

    def add_fields(self, form, index):
        super().add_fields(form, index)
        if some_criteria_to_prevent_deletion:
            form.fields['DELETE'].disabled = True

This code leverages the Field.disabled property added in Django 1.9. As the documentation says, "even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data," so you don't need to add more code to prevent deletion.

like image 3
Benoit Blanchon Avatar answered Nov 19 '22 21:11

Benoit Blanchon