I have a Booking model with a User foreign key. In the admin the bookings are inlined inside the user change page.
I want to prevent some bookings from being deleted (from the inline) when there are less then 24 hours before the booking AND the logged user is not in SuperStaff group.
So I define the BookingInline something like that:
class BookingInline(admin.TabularInline):
model = Booking
extra = 0
fk_name = 'bookedFor'
def has_delete_permission(self, request, obj=None):
if not request.user.profile.isSuperStaff() and obj.is24hoursFromNow():
return True
return False
This code is reached, but I get a User instance, instead of a Booking one (and an error, of course), thus cannot decide for each inlined booking if it could be deleted or not. Isn't the has_delete_permission() method supposed to get the inlined object instance in this case? There is nothing about in the django docs...
I know the code is reached since I checked it using only the condition on user, and it actually hides the delete box for appropriate users.
I also tried to do it other way, through the Formset and clean() method, but it doesn't have the request parameter, so I get the desired instance, but not the user logged in.
I've searched for a solution for a few hours, but seems like the only way is to put a link from the inline to the full change page of a Booking object, and check the permissions when a user will attempt to regularly delete a Booking.
Any ideas how can that be done in an elegant way would be appreciated.
I was facing exactly the same problem today, and I think I've found an acceptable way to solve it. Here's what I did:
I had to make inlines deletable only if a particular field had a certain value. Specifically, as I'm dealing with generic tasks and assignments, only non-accepted tasks have to be deletable. In model terms:
class Task(models.Model):
STATUS_CHOICES = (
('PND', 'Pending'),
('ACC', 'Accepted'),
)
status = models.CharField( ----> If this != 'PND', inline instance
max_length=3, should not be deletable
choices=STATUS_CHOICES,
default=STATUS_CHOICES[0][0])
Since I couldn't use has_delete_permission
within my admin.TabularInline
class either, as it refers to the whole fieldset (i.e. all the inlines) and not to the single row, I went through the path of template overriding:
tabular.html:44-62 (original)
[...]
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
{% if not field.field.is_hidden %}
<td{% if field.field.name %} class="field-{{ field.field.name }}"{% endif %}>
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field.errors.as_ul }}
{{ field.field }}
{% endif %}
</td>
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% if inline_admin_formset.formset.can_delete %}
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
{% endif %}
[...]
tabular.html (overridden)
[...]
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
{% if not field.field.is_hidden %}
<td{% if field.field.name %} class="field-{{ field.field.name }}"{% endif %}>
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{% include "admin/includes/field.html" with is_tabular=True %}
{% endif %}
</td>
{% endif %}
{% endfor %}
{% endfor %}
<!-- Custom deletion logic, only available for non-accepted objects -->
{% for line in fieldset %}
{% for field in line %}
{% if field.field.name == "status" %}
{% if field.field.value == "PND" %}
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
{% else %}
<td class="delete"><input type="checkbox" disabled="disabled">
<img src="/static/admin/img/icon_alert.gif" data-toggle="tooltip" class="title-starter"
data-original-title="Can't remove accepted tasks" />
</td>
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
<!-- Classic deletion, removed
{% if inline_admin_formset.formset.can_delete %}
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
{% endif %}
-->
[...]
As a result (non-standard graphic as I'm using django-admin-bootstrap):
Strictly talking about "elegance", I have to get through the lines' fields twice to make it work, but I haven't found any better way like directly reading that field's value. I couldn't have anything like {{ line.fields.0.status }}
or {{ line.fields.status }}
work. If anyone could point to the direct syntax, I'd gladly update my solution.
Anyway, since it still works and it's not really that bad, I'll be fine with this method until anything clearly better comes out.
You can check conditions in formset's clean() method.
class BookingFormSet(forms.BaseInlineFormSet):
def clean(self):
super().clean()
has_errors = False
for form in self.deleted_forms:
if form.instance.is24hoursFromNow():
form._errors[NON_FIELD_ERRORS] = self.error_class(['Not allowed to delete'])
has_errors = True
if has_errors:
raise forms.ValidationError('Please correct the errors below')
class BookingInline(admin.TabularInline):
model = Booking
formset = BookingFormSet
Note that you don't have request object here, so can't check for isSuperStaff()
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