attachments.py 3.4 KB

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