Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIANXKE-380: prepare option to disable single attachments in inline #15

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
65 changes: 63 additions & 2 deletions drf_attachments/admin.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Comment on lines +161 to +163

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting disabled = True on a field does not disable the delete checkbox of the surrounding template (it's mentioned as a requirement in the ticket description).

Sadly, has_xxx_permission of the inline refers to the parent model (which makes no sense at all in this case), so the custom form is a valid workaround. To disable the delete checkbox, one would probably need to overwrite the template - which is a rather painful and error-prone process in Django admin.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's true, that the checkbox is still available, but on usage the form will render a corresponding error message and prevent the deletion. I think that is OK. Another option would be to use a custom FormSet where the checkbox is simply disabled - I'll look into a way to combine this with the current approach.



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
Expand All @@ -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",
)


Expand Down