Skip to content

Commit d4df21c

Browse files
Allow use of can_delete flag directly in inline definition
The previous README had instructions about preventing the deletion of inlined models by overriding the `has_delete_permission` method on a custom subclass of the appropriate `StackedInline` or `TabularInline` class, and then passing that subclass as the `admin_class` option of `inline_reverse`. However, that method no longer works with newer versions of Django (at least with Django 4.2 and later). This commit updates the `ReverseInlineModelAdmin` class to determine `can_delete` in the same way that Django's `InlineModelAdmin` currently does, and then passes that into the custom formset factory. In that way, consumers of this library can simply set the `can_delete` option directly on their `inline_reverse` specification without subclassing one of the built-in `InlineModelAdmin` implementations. Note that this approach was necessary because the custom formset factory in `django_reverse_admin` lives outside of the `ReverseInlineModelAdmin` class, and doesn't have access to the parent class' `can_delete` property and `has_delete_permission` method.
1 parent 6fae3c4 commit d4df21c

File tree

4 files changed

+24
-17
lines changed

4 files changed

+24
-17
lines changed

README.md

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,29 +105,17 @@ class PersonAdmin(ReverseModelAdmin):
105105

106106
#### Do not allow deletion
107107

108-
django-reverse-admin uses the standard features of in-line forms (Stacked and Tabular). When used in the ReverseAdmin co-text, it releases the option to delete the associated object.
109-
110-
To remove this option (thus making the object unable to be deleted), it is necessary to add the admin_class inheriting from one of the native resources (Stacked and Tabular). Example:
108+
To prevent deletion of the inlined object, use the `can_delete` option:
111109

112110
```py
113-
from .forms import CustomBusinessAddrForm
114-
115-
116-
class BarInline(admin.StackedInline):
117-
model = Address
118-
119-
def has_delete_permission(self, request, obj=None):
120-
return False
121-
122-
123111
class PersonAdmin(ReverseModelAdmin):
124112
inline_type = 'tabular'
125113
inline_reverse = [
126114
'business_addr',
127115
{
128116
'field_name': 'home_addr',
129117
'fields': ('zip_code',),
130-
'admin_class': BarInline
118+
'can_delete': False,
131119
}
132120
]
133121
```

django_reverse_admin/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,15 @@ def reverse_inlineformset_factory(parent_model,
6565
form=ModelForm,
6666
fields=None,
6767
exclude=None,
68-
formfield_callback=lambda f: f.formfield()):
68+
formfield_callback=lambda f: f.formfield(),
69+
**kwargs):
6970

7071
if fields is None and exclude is None:
7172
related_fields = [f for f in model._meta.get_fields() if
7273
(f.one_to_many or f.one_to_one or f.many_to_many) and f.auto_created and not f.concrete]
7374
fields = [f.name for f in model._meta.get_fields() if f not in
7475
related_fields] # ignoring reverse relations
75-
kwargs = {
76+
defaults = {
7677
'form': form,
7778
'formfield_callback': formfield_callback,
7879
'formset': ReverseInlineFormSet,
@@ -83,7 +84,8 @@ def reverse_inlineformset_factory(parent_model,
8384
'exclude': exclude,
8485
'max_num': 1,
8586
}
86-
FormSet = modelformset_factory(model, **kwargs)
87+
defaults.update(**kwargs)
88+
FormSet = modelformset_factory(model, **defaults)
8789
FormSet.parent_fk_name = parent_fk_name
8890
return FormSet
8991

@@ -129,12 +131,14 @@ def get_formset(self, request, obj=None, **kwargs):
129131
exclude.extend(non_editable_fields)
130132
# but need exclude to be None if result is an empty list
131133
exclude = exclude or None
134+
can_delete = self.can_delete and self.has_delete_permission(request, obj)
132135

133136
defaults = {
134137
"form": self.form,
135138
"fields": fields,
136139
"exclude": exclude,
137140
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
141+
"can_delete": can_delete,
138142
}
139143
kwargs.update(defaults)
140144
return reverse_inlineformset_factory(self.parent_model,

tests/polls/admin.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from polls.models import Address
33
from polls.models import NonInlinePerson
44
from polls.models import Person
5+
from polls.models import PersonNoDeleteAddress
56
from polls.models import PersonWithAddressNonId
67
from polls.models import PersonWithTwoAddresses
78
from polls.models import PhoneNumber
@@ -41,6 +42,15 @@ def get_readonly_fields(self, request, obj=None):
4142
return self.readonly_fields
4243

4344

45+
class PersonNoDeleteAddressAdmin(PersonAdmin):
46+
inline_reverse = [
47+
('home_addr', {
48+
'fields': ['street', 'city', 'state', 'zipcode'],
49+
'can_delete': False,
50+
}),
51+
]
52+
53+
4454
class PersonWithAddressNonIdAdmin(ReverseModelAdmin):
4555
list_display = ('name', 'home_addr')
4656

@@ -74,6 +84,7 @@ class AddressAdmin(admin.ModelAdmin):
7484

7585

7686
admin.site.register(Person, PersonAdmin)
87+
admin.site.register(PersonNoDeleteAddress, PersonNoDeleteAddressAdmin)
7788
admin.site.register(PersonWithAddressNonId, PersonWithAddressNonIdAdmin)
7889
admin.site.register(PersonWithTwoAddresses, PersonWithTwoAddressesAdmin)
7990
admin.site.register(PhoneNumber, PhoneNumberAdmin)

tests/polls/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ def __str__(self):
5656
return self.name
5757

5858

59+
class PersonNoDeleteAddress(Person):
60+
pass
61+
62+
5963
class PersonWithAddressNonId(TemporalBase):
6064
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
6165
name = models.CharField(max_length=255)

0 commit comments

Comments
 (0)