from django.contrib import messages from django.db import transaction from django.utils.translation import gettext_lazy as _ from misago.admin.views import generic from misago.threads.forms import SearchAttachmentsForm from misago.threads.models import Attachment, Post class AttachmentAdmin(generic.AdminBaseMixin): root_link = "misago:admin:system:attachments:index" model = Attachment templates_dir = "misago/admin/attachments" message_404 = _("Requested attachment could not be found.") def get_queryset(self): qs = super().get_queryset() return qs.select_related( "filetype", "uploader", "post", "post__thread", "post__category" ) class AttachmentsList(AttachmentAdmin, generic.ListView): items_per_page = 20 ordering = [ ("-id", _("From newest")), ("id", _("From oldest")), ("filename", _("A to z")), ("-filename", _("Z to a")), ("size", _("Smallest files")), ("-size", _("Largest files")), ] selection_label = _("With attachments: 0") empty_selection_label = _("Select attachments") mass_actions = [ { "action": "delete", "name": _("Delete attachments"), "icon": "fa fa-times-circle", "confirmation": _("Are you sure you want to delete selected attachments?"), "is_atomic": False, } ] def get_search_form(self, request): return SearchAttachmentsForm def action_delete(self, request, attachments): deleted_attachments = [] desynced_posts = [] for attachment in attachments: if attachment.post: deleted_attachments.append(attachment.pk) desynced_posts.append(attachment.post_id) if desynced_posts: with transaction.atomic(): for post in Post.objects.filter(id__in=desynced_posts): self.delete_from_cache(post, deleted_attachments) for attachment in attachments: attachment.delete() message = _("Selected attachments have been deleted.") messages.success(request, message) def delete_from_cache(self, post, attachments): if not post.attachments_cache: return # admin action may be taken due to desynced state clean_cache = [] for a in post.attachments_cache: if a["id"] not in attachments: clean_cache.append(a) post.attachments_cache = clean_cache or None post.save(update_fields=["attachments_cache"]) class DeleteAttachment(AttachmentAdmin, generic.ButtonView): def button_action(self, request, target): if target.post: self.delete_from_cache(target) target.delete() message = _('Attachment "%(filename)s" has been deleted.') messages.success(request, message % {"filename": target.filename}) def delete_from_cache(self, attachment): if not attachment.post.attachments_cache: return # admin action may be taken due to desynced state clean_cache = [] for a in attachment.post.attachments_cache: if a["id"] != attachment.id: clean_cache.append(a) attachment.post.attachments_cache = clean_cache or None attachment.post.save(update_fields=["attachments_cache"])