images.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import os
  2. from django.conf import settings
  3. from django.core.paginator import Paginator
  4. from django.http import HttpResponse, JsonResponse
  5. from django.shortcuts import get_object_or_404, redirect, render
  6. from django.urls import reverse
  7. from django.urls.exceptions import NoReverseMatch
  8. from django.utils.translation import ugettext as _
  9. from django.views.decorators.vary import vary_on_headers
  10. from wagtail.admin import messages
  11. from wagtail.admin.auth import PermissionPolicyChecker, permission_denied
  12. from wagtail.admin.forms.search import SearchForm
  13. from wagtail.admin.models import popular_tags_for_model
  14. from wagtail.core.models import Collection, Site
  15. from wagtail.images import get_image_model
  16. from wagtail.images.exceptions import InvalidFilterSpecError
  17. from wagtail.images.forms import URLGeneratorForm, get_image_form
  18. from wagtail.images.models import Filter, SourceImageIOError
  19. from wagtail.images.permissions import permission_policy
  20. from wagtail.images.views.serve import generate_signature
  21. from wagtail.search import index as search_index
  22. permission_checker = PermissionPolicyChecker(permission_policy)
  23. INDEX_PAGE_SIZE = getattr(settings, 'WAGTAILIMAGES_INDEX_PAGE_SIZE', 20)
  24. USAGE_PAGE_SIZE = getattr(settings, 'WAGTAILIMAGES_USAGE_PAGE_SIZE', 20)
  25. @permission_checker.require_any('add', 'change', 'delete')
  26. @vary_on_headers('X-Requested-With')
  27. def index(request):
  28. Image = get_image_model()
  29. # Get images (filtered by user permission)
  30. images = permission_policy.instances_user_has_any_permission_for(
  31. request.user, ['change', 'delete']
  32. ).order_by('-created_at')
  33. # Search
  34. query_string = None
  35. if 'q' in request.GET:
  36. form = SearchForm(request.GET, placeholder=_("Search images"))
  37. if form.is_valid():
  38. query_string = form.cleaned_data['q']
  39. images = images.search(query_string)
  40. else:
  41. form = SearchForm(placeholder=_("Search images"))
  42. # Filter by collection
  43. current_collection = None
  44. collection_id = request.GET.get('collection_id')
  45. if collection_id:
  46. try:
  47. current_collection = Collection.objects.get(id=collection_id)
  48. images = images.filter(collection=current_collection)
  49. except (ValueError, Collection.DoesNotExist):
  50. pass
  51. # Filter by tag
  52. current_tag = request.GET.get('tag')
  53. if current_tag:
  54. try:
  55. images = images.filter(tags__name=current_tag)
  56. except (AttributeError):
  57. current_tag = None
  58. paginator = Paginator(images, per_page=INDEX_PAGE_SIZE)
  59. images = paginator.get_page(request.GET.get('p'))
  60. collections = permission_policy.collections_user_has_any_permission_for(
  61. request.user, ['add', 'change']
  62. )
  63. if len(collections) < 2:
  64. collections = None
  65. else:
  66. collections = Collection.order_for_display(collections)
  67. # Create response
  68. if request.is_ajax():
  69. return render(request, 'wagtailimages/images/results.html', {
  70. 'images': images,
  71. 'query_string': query_string,
  72. 'is_searching': bool(query_string),
  73. })
  74. else:
  75. return render(request, 'wagtailimages/images/index.html', {
  76. 'images': images,
  77. 'query_string': query_string,
  78. 'is_searching': bool(query_string),
  79. 'search_form': form,
  80. 'popular_tags': popular_tags_for_model(Image),
  81. 'current_tag': current_tag,
  82. 'collections': collections,
  83. 'current_collection': current_collection,
  84. 'user_can_add': permission_policy.user_has_permission(request.user, 'add'),
  85. })
  86. @permission_checker.require('change')
  87. def edit(request, image_id):
  88. Image = get_image_model()
  89. ImageForm = get_image_form(Image)
  90. image = get_object_or_404(Image, id=image_id)
  91. if not permission_policy.user_has_permission_for_instance(request.user, 'change', image):
  92. return permission_denied(request)
  93. if request.method == 'POST':
  94. original_file = image.file
  95. form = ImageForm(request.POST, request.FILES, instance=image, user=request.user)
  96. if form.is_valid():
  97. if 'file' in form.changed_data:
  98. # Set new image file size
  99. image.file_size = image.file.size
  100. # Set new image file hash
  101. image.file.seek(0)
  102. image._set_file_hash(image.file.read())
  103. image.file.seek(0)
  104. form.save()
  105. if 'file' in form.changed_data:
  106. # if providing a new image file, delete the old one and all renditions.
  107. # NB Doing this via original_file.delete() clears the file field,
  108. # which definitely isn't what we want...
  109. original_file.storage.delete(original_file.name)
  110. image.renditions.all().delete()
  111. # Reindex the image to make sure all tags are indexed
  112. search_index.insert_or_update_object(image)
  113. messages.success(request, _("Image '{0}' updated.").format(image.title), buttons=[
  114. messages.button(reverse('wagtailimages:edit', args=(image.id,)), _('Edit again'))
  115. ])
  116. return redirect('wagtailimages:index')
  117. else:
  118. messages.error(request, _("The image could not be saved due to errors."))
  119. else:
  120. form = ImageForm(instance=image, user=request.user)
  121. # Check if we should enable the frontend url generator
  122. try:
  123. reverse('wagtailimages_serve', args=('foo', '1', 'bar'))
  124. url_generator_enabled = True
  125. except NoReverseMatch:
  126. url_generator_enabled = False
  127. if image.is_stored_locally():
  128. # Give error if image file doesn't exist
  129. if not os.path.isfile(image.file.path):
  130. messages.error(request, _(
  131. "The source image file could not be found. Please change the source or delete the image."
  132. ).format(image.title), buttons=[
  133. messages.button(reverse('wagtailimages:delete', args=(image.id,)), _('Delete'))
  134. ])
  135. try:
  136. filesize = image.get_file_size()
  137. except SourceImageIOError:
  138. filesize = None
  139. return render(request, "wagtailimages/images/edit.html", {
  140. 'image': image,
  141. 'form': form,
  142. 'url_generator_enabled': url_generator_enabled,
  143. 'filesize': filesize,
  144. 'user_can_delete': permission_policy.user_has_permission_for_instance(
  145. request.user, 'delete', image
  146. ),
  147. })
  148. def url_generator(request, image_id):
  149. image = get_object_or_404(get_image_model(), id=image_id)
  150. if not permission_policy.user_has_permission_for_instance(request.user, 'change', image):
  151. return permission_denied(request)
  152. form = URLGeneratorForm(initial={
  153. 'filter_method': 'original',
  154. 'width': image.width,
  155. 'height': image.height,
  156. })
  157. return render(request, "wagtailimages/images/url_generator.html", {
  158. 'image': image,
  159. 'form': form,
  160. })
  161. def generate_url(request, image_id, filter_spec):
  162. # Get the image
  163. Image = get_image_model()
  164. try:
  165. image = Image.objects.get(id=image_id)
  166. except Image.DoesNotExist:
  167. return JsonResponse({
  168. 'error': "Cannot find image."
  169. }, status=404)
  170. # Check if this user has edit permission on this image
  171. if not permission_policy.user_has_permission_for_instance(request.user, 'change', image):
  172. return JsonResponse({
  173. 'error': "You do not have permission to generate a URL for this image."
  174. }, status=403)
  175. # Parse the filter spec to make sure its valid
  176. try:
  177. Filter(spec=filter_spec).operations
  178. except InvalidFilterSpecError:
  179. return JsonResponse({
  180. 'error': "Invalid filter spec."
  181. }, status=400)
  182. # Generate url
  183. signature = generate_signature(image_id, filter_spec)
  184. url = reverse('wagtailimages_serve', args=(signature, image_id, filter_spec))
  185. # Get site root url
  186. try:
  187. site_root_url = Site.objects.get(is_default_site=True).root_url
  188. except Site.DoesNotExist:
  189. site_root_url = Site.objects.first().root_url
  190. # Generate preview url
  191. preview_url = reverse('wagtailimages:preview', args=(image_id, filter_spec))
  192. return JsonResponse({'url': site_root_url + url, 'preview_url': preview_url}, status=200)
  193. def preview(request, image_id, filter_spec):
  194. image = get_object_or_404(get_image_model(), id=image_id)
  195. try:
  196. response = HttpResponse()
  197. image = Filter(spec=filter_spec).run(image, response)
  198. response['Content-Type'] = 'image/' + image.format_name
  199. return response
  200. except InvalidFilterSpecError:
  201. return HttpResponse("Invalid filter spec: " + filter_spec, content_type='text/plain', status=400)
  202. @permission_checker.require('delete')
  203. def delete(request, image_id):
  204. image = get_object_or_404(get_image_model(), id=image_id)
  205. if not permission_policy.user_has_permission_for_instance(request.user, 'delete', image):
  206. return permission_denied(request)
  207. if request.method == 'POST':
  208. image.delete()
  209. messages.success(request, _("Image '{0}' deleted.").format(image.title))
  210. return redirect('wagtailimages:index')
  211. return render(request, "wagtailimages/images/confirm_delete.html", {
  212. 'image': image,
  213. })
  214. @permission_checker.require('add')
  215. def add(request):
  216. ImageModel = get_image_model()
  217. ImageForm = get_image_form(ImageModel)
  218. if request.method == 'POST':
  219. image = ImageModel(uploaded_by_user=request.user)
  220. form = ImageForm(request.POST, request.FILES, instance=image, user=request.user)
  221. if form.is_valid():
  222. # Set image file size
  223. image.file_size = image.file.size
  224. # Set image file hash
  225. image.file.seek(0)
  226. image._set_file_hash(image.file.read())
  227. image.file.seek(0)
  228. form.save()
  229. # Reindex the image to make sure all tags are indexed
  230. search_index.insert_or_update_object(image)
  231. messages.success(request, _("Image '{0}' added.").format(image.title), buttons=[
  232. messages.button(reverse('wagtailimages:edit', args=(image.id,)), _('Edit'))
  233. ])
  234. return redirect('wagtailimages:index')
  235. else:
  236. messages.error(request, _("The image could not be created due to errors."))
  237. else:
  238. form = ImageForm(user=request.user)
  239. return render(request, "wagtailimages/images/add.html", {
  240. 'form': form,
  241. })
  242. def usage(request, image_id):
  243. image = get_object_or_404(get_image_model(), id=image_id)
  244. paginator = Paginator(image.get_usage(), per_page=USAGE_PAGE_SIZE)
  245. used_by = paginator.get_page(request.GET.get('p'))
  246. return render(request, "wagtailimages/images/usage.html", {
  247. 'image': image,
  248. 'used_by': used_by
  249. })