attachments.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. from django.contrib import messages
  2. from django.db import transaction
  3. from django.utils.translation import gettext_lazy as _
  4. from misago.admin.views import generic
  5. from misago.threads.forms import SearchAttachmentsForm
  6. from misago.threads.models import Attachment, Post
  7. class AttachmentAdmin(generic.AdminBaseMixin):
  8. root_link = "misago:admin:system:attachments:index"
  9. model = Attachment
  10. templates_dir = "misago/admin/attachments"
  11. message_404 = _("Requested attachment could not be found.")
  12. def get_queryset(self):
  13. qs = super().get_queryset()
  14. return qs.select_related(
  15. "filetype", "uploader", "post", "post__thread", "post__category"
  16. )
  17. class AttachmentsList(AttachmentAdmin, generic.ListView):
  18. items_per_page = 20
  19. ordering = [
  20. ("-id", _("From newest")),
  21. ("id", _("From oldest")),
  22. ("filename", _("A to z")),
  23. ("-filename", _("Z to a")),
  24. ("size", _("Smallest files")),
  25. ("-size", _("Largest files")),
  26. ]
  27. selection_label = _("With attachments: 0")
  28. empty_selection_label = _("Select attachments")
  29. mass_actions = [
  30. {
  31. "action": "delete",
  32. "name": _("Delete attachments"),
  33. "icon": "fa fa-times-circle",
  34. "confirmation": _("Are you sure you want to delete selected attachments?"),
  35. "is_atomic": False,
  36. }
  37. ]
  38. def get_search_form(self, request):
  39. return SearchAttachmentsForm
  40. def action_delete(self, request, attachments):
  41. deleted_attachments = []
  42. desynced_posts = []
  43. for attachment in attachments:
  44. if attachment.post:
  45. deleted_attachments.append(attachment.pk)
  46. desynced_posts.append(attachment.post_id)
  47. if desynced_posts:
  48. with transaction.atomic():
  49. for post in Post.objects.filter(id__in=desynced_posts):
  50. self.delete_from_cache(post, deleted_attachments)
  51. for attachment in attachments:
  52. attachment.delete()
  53. message = _("Selected attachments have been deleted.")
  54. messages.success(request, message)
  55. def delete_from_cache(self, post, attachments):
  56. if not post.attachments_cache:
  57. return # admin action may be taken due to desynced state
  58. clean_cache = []
  59. for a in post.attachments_cache:
  60. if a["id"] not in attachments:
  61. clean_cache.append(a)
  62. post.attachments_cache = clean_cache or None
  63. post.save(update_fields=["attachments_cache"])
  64. class DeleteAttachment(AttachmentAdmin, generic.ButtonView):
  65. def button_action(self, request, target):
  66. if target.post:
  67. self.delete_from_cache(target)
  68. target.delete()
  69. message = _('Attachment "%(filename)s" has been deleted.')
  70. messages.success(request, message % {"filename": target.filename})
  71. def delete_from_cache(self, attachment):
  72. if not attachment.post.attachments_cache:
  73. return # admin action may be taken due to desynced state
  74. clean_cache = []
  75. for a in attachment.post.attachments_cache:
  76. if a["id"] != attachment.id:
  77. clean_cache.append(a)
  78. attachment.post.attachments_cache = clean_cache or None
  79. attachment.post.save(update_fields=["attachments_cache"])