diff --git a/README.md b/README.md index a7099b9..ecf08a2 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,46 @@ class PhotoAlbumAdmin(admin.ModelAdmin): `ReadOnlyAttachmentInlineAdmin` is useful when attachments should be provided only by REST API. You may consider extending the classes in order to handle additional permission checks. +The package provides a custom `DynamicallyDisabledAttachmentInlineForm` that allows disablement (setting readonly) of +single attachments depending on its `disable_inline_fields` method. Override it to define your own logic to decide when +an inline attachment should not be editable. +In addition to that a custom `DynamicallyDisabledAttachmentInlineFormSet` class can also disable the DELETE checkbox +of single attachments. + +Example usage: +``` +# customize the DynamicallyDisabledAttachmentInlineForm to determine which inline attachments should be readonly +MyCustomDynamicallyDisabledAttachmentInlineForm(DynamicallyDisabledAttachmentInlineForm): + def disable_inline_fields(self): + """ + Attachments with context IMMUTABLE should be disabled / readonly + """ + return self.instance and self.instance.context == "IMMUTABLE" + + +# customize the DynamicallyDisabledAttachmentInlineFormSet to determine which inline attachments' DELETE checkbox +# should be readonly +MyCustomDynamicallyDisabledAttachmentInlineFormSet(DynamicallyDisabledAttachmentInlineFormSet): + super().add_fields(form, index) + """ + DELETE checkbox for Attachments that return True for + MyCustomDynamicallyDisabledAttachmentInlineForm's disable_inline_fields method should be disabled + """ + if hasattr(form, 'disable_inline_fields') and form.disable_inline_fields(): + form.fields['DELETE'].disabled = True + + +class MyCustomDynamicallyDisabledAttachmentInlineAdmin(AttachmentInlineAdmin): + form = MyCustomDynamicallyDisabledAttachmentInlineForm + formset = MyCustomDynamicallyDisabledAttachmentInlineFormSet + + def has_add_permission(self, request, obj=None): + return False + + show_change_link = False +``` + + ## Usage with DRF (ToDo: API needs to be simplified) diff --git a/drf_attachments/admin.py b/drf_attachments/admin.py index a1b409f..18a4358 100644 --- a/drf_attachments/admin.py +++ b/drf_attachments/admin.py @@ -1,9 +1,9 @@ from content_disposition import rfc5987_content_disposition from django.contrib import admin -from django.contrib.admin import AdminSite -from django.contrib.admin.views.decorators import staff_member_required from django.contrib.contenttypes.admin import GenericTabularInline +from django.contrib.contenttypes.forms import BaseGenericInlineFormSet from django.forms import ChoiceField, ModelForm +from django.forms.utils import ErrorList from django.http import StreamingHttpResponse from django.urls import NoReverseMatch, path, reverse from django.utils.safestring import mark_safe @@ -117,6 +117,65 @@ def download_view(self, request, object_id): return response +class DynamicallyDisabledAttachmentInlineForm(AttachmentForm): + class Meta: + model = Attachment + fields = "__all__" + + def disable_inline_fields(self): + """ + Override to return True/False when custom condition is met + and change field readonly status in inline admin list on True + """ + raise NotImplementedError() + + def __init__( + self, + data=None, + files=None, + auto_id="id_%s", + prefix=None, + initial=None, + error_class=ErrorList, + label_suffix=None, + empty_permitted=False, + instance=None, + use_required_attribute=None, + renderer=None, + ): + super().__init__( + data, + files, + auto_id, + prefix, + initial, + error_class, + label_suffix, + empty_permitted, + instance, + use_required_attribute, + renderer, + ) + + # make all fields of the inline attachment read only + if self.disable_inline_fields(): + for field in self.fields: + self.fields[field].disabled = True + + +class DynamicallyDisabledAttachmentInlineFormSet(BaseGenericInlineFormSet): + def add_fields(self, form, index): + """ + Override to disable the DELETE checkbox of entries matching a specific custom condition. + E.g. to make this dependent of the DynamicallyDisabledAttachmentInlineForm's disable_inline_fields method: + + super().add_fields(form, index) + if hasattr(form, 'disable_inline_fields') and form.disable_inline_fields(): + form.fields['DELETE'].disabled = True + """ + raise NotImplementedError() + + class BaseAttachmentInlineAdmin(GenericTabularInline, AttachmentAdminMixin): model = Attachment form = AttachmentForm @@ -129,12 +188,14 @@ class BaseAttachmentInlineAdmin(GenericTabularInline, AttachmentAdminMixin): "mime_type", "extension", "creation_date", + "last_modification_date", ) readonly_fields = ( "size", "mime_type", "extension", "creation_date", + "last_modification_date", )