user_media.views: 193 total statements, 100.0% covered

Generated: Mon 2014-08-04 18:55 CEST

Source file: /home/tobi/Projects/django-user-media/src/user_media/views.py

Stats: 178 executed, 0 missed, 15 excluded, 205 ignored

  1. """Views for the ``django-user-media`` app."""
  2. from django.conf import settings
  3. from django.contrib.auth.decorators import login_required
  4. from django.contrib.contenttypes.models import ContentType
  5. from django.db.models import ObjectDoesNotExist
  6. from django.http import Http404, HttpResponse
  7. from django.template import RequestContext
  8. from django.template.loader import render_to_string
  9. from django.utils.decorators import method_decorator
  10. from django.utils.translation import ugettext_lazy as _
  11. from django.views.generic import CreateView, DeleteView, FormView, UpdateView
  12. from django_libs.views_mixins import AjaxResponseMixin
  13. from easy_thumbnails.files import get_thumbnailer
  14. from simplejson import dumps
  15. from user_media.forms import UserMediaImageForm, UserMediaImageSingleUploadForm
  16. from user_media.models import UserMediaImage
  17. class UserMediaImageViewMixin(object):
  18. """
  19. Mixin for views that deal with `UserMediaImage` objects.
  20. When using this mixin please make sure that you call `_add_next_and_user()`
  21. in your `dispatch()` method.
  22. """
  23. model = UserMediaImage
  24. def _add_next_and_user(self, request):
  25. self.next = request.POST.get('next', '') or request.GET.get('next', '')
  26. self.user = request.user
  27. def get_context_data(self, **kwargs):
  28. """
  29. Adds `next` to the context.
  30. This makes sure that the `next` parameter doesn't get lost if the
  31. form was submitted invalid.
  32. """
  33. ctx = super(UserMediaImageViewMixin, self).get_context_data(**kwargs)
  34. ctx.update({
  35. 'action': self.action,
  36. 'next': self.next,
  37. })
  38. return ctx
  39. def get_success_url(self):
  40. """
  41. Returns the success URL.
  42. This is either the given `next` URL parameter or the content object's
  43. `get_absolute_url` method's return value.
  44. """
  45. if self.next:
  46. return self.next
  47. if self.object and self.object.content_object:
  48. return self.object.content_object.get_absolute_url()
  49. raise Exception(
  50. 'No content object given. Please provide ``next`` in your POST'
  51. ' data')
  52. class CreateImageView(AjaxResponseMixin, UserMediaImageViewMixin, CreateView):
  53. action = 'create'
  54. form_class = UserMediaImageForm
  55. ajax_template_prefix = 'partials/ajax_'
  56. @method_decorator(login_required)
  57. def dispatch(self, request, *args, **kwargs):
  58. """Adds useful objects to the class and performs security checks."""
  59. self._add_next_and_user(request)
  60. self.content_object = None
  61. self.content_type = None
  62. self.object_id = kwargs.get('object_id', None)
  63. if kwargs.get('content_type'):
  64. # Check if the user forged the URL and posted a non existant
  65. # content type
  66. try:
  67. self.content_type = ContentType.objects.get(
  68. model=kwargs.get('content_type'))
  69. except ContentType.DoesNotExist:
  70. raise Http404
  71. if self.content_type:
  72. # Check if the user forged the URL and tries to append the image to
  73. # an object that does not exist
  74. try:
  75. self.content_object = \
  76. self.content_type.get_object_for_this_type(
  77. pk=self.object_id)
  78. except ObjectDoesNotExist:
  79. raise Http404
  80. if self.content_object and hasattr(self.content_object, 'user'):
  81. # Check if the user forged the URL and tries to append the image to
  82. # an object that does not belong to him
  83. if not self.content_object.user == self.user:
  84. raise Http404
  85. return super(CreateImageView, self).dispatch(request, *args, **kwargs)
  86. def get_context_data(self, **kwargs):
  87. ctx = super(CreateImageView, self).get_context_data(**kwargs)
  88. ctx.update({
  89. 'content_type': self.content_type,
  90. 'object_id': self.object_id,
  91. })
  92. return ctx
  93. def get_form_kwargs(self):
  94. kwargs = super(CreateImageView, self).get_form_kwargs()
  95. kwargs.update({
  96. 'user': self.user,
  97. 'content_type': self.content_type,
  98. 'object_id': self.object_id,
  99. })
  100. return kwargs
  101. class DeleteImageView(AjaxResponseMixin, UserMediaImageViewMixin, DeleteView):
  102. """Deletes an `UserMediaImage` object."""
  103. action = 'delete'
  104. model = UserMediaImage
  105. ajax_template_prefix = 'partials/ajax_'
  106. @method_decorator(login_required)
  107. def dispatch(self, request, *args, **kwargs):
  108. """Adds useful objects to the class."""
  109. self._add_next_and_user(request)
  110. return super(DeleteImageView, self).dispatch(request, *args, **kwargs)
  111. def get_context_data(self, **kwargs):
  112. ctx = super(DeleteImageView, self).get_context_data(**kwargs)
  113. ctx.update({
  114. 'image_pk': self.object.pk,
  115. })
  116. return ctx
  117. def get_queryset(self):
  118. """
  119. Making sure that a user can only delete his own images.
  120. Even when he forges the request URL.
  121. """
  122. queryset = super(DeleteImageView, self).get_queryset()
  123. queryset = queryset.filter(user=self.user)
  124. return queryset
  125. class UpdateImageView(AjaxResponseMixin, UserMediaImageViewMixin, UpdateView):
  126. """Updates an existing `UserMediaImage` object."""
  127. action = 'update'
  128. model = UserMediaImage
  129. form_class = UserMediaImageForm
  130. ajax_template_prefix = 'partials/ajax_'
  131. @method_decorator(login_required)
  132. def dispatch(self, request, *args, **kwargs):
  133. """Adds useful objects to the class."""
  134. self._add_next_and_user(request)
  135. return super(UpdateImageView, self).dispatch(request, *args, **kwargs)
  136. def get_context_data(self, **kwargs):
  137. ctx = super(UpdateImageView, self).get_context_data(**kwargs)
  138. ctx.update({
  139. 'content_type': self.object.content_type,
  140. 'object_id': self.object.object_id,
  141. 'image_pk': self.object.pk,
  142. })
  143. return ctx
  144. def get_form_kwargs(self):
  145. kwargs = super(UpdateImageView, self).get_form_kwargs()
  146. kwargs.update({
  147. 'user': self.user,
  148. 'content_type': self.object.content_type,
  149. 'object_id': self.object.object_id,
  150. })
  151. return kwargs
  152. def get_queryset(self):
  153. """
  154. Making sure that a user can only edit his own images.
  155. Even when he forges the request URL.
  156. """
  157. queryset = super(UpdateImageView, self).get_queryset()
  158. queryset = queryset.filter(user=self.user)
  159. return queryset
  160. class AJAXMultipleImageUploadView(CreateView):
  161. """Ajax view to handle the multiple image upload."""
  162. model = UserMediaImage
  163. form_class = UserMediaImageForm
  164. @method_decorator(login_required)
  165. def dispatch(self, request, *args, **kwargs):
  166. self.obj_id = kwargs.get('obj_id', None)
  167. self.user = request.user
  168. if not request.is_ajax():
  169. # Since we use a jquery modal and a jquery upload we should only
  170. # allow ajax calls
  171. raise Http404
  172. # Check if the user posted a non existant content type
  173. try:
  174. self.c_type = ContentType.objects.get(model=kwargs.get('c_type'))
  175. except ContentType.DoesNotExist:
  176. raise Http404
  177. # Check if the content object exists
  178. try:
  179. self.content_object = self.c_type.get_object_for_this_type(
  180. pk=self.obj_id)
  181. except ObjectDoesNotExist:
  182. raise Http404
  183. # Check for permissions
  184. # Add a single user to the content object or prepare a user_can_edit
  185. # function.
  186. if (not hasattr(self.content_object, 'user')
  187. or not self.content_object.user == self.user):
  188. if (not hasattr(self.content_object, 'user_can_edit')
  189. or not self.content_object.user_can_edit(self.user)):
  190. raise Http404
  191. return super(AJAXMultipleImageUploadView, self).dispatch(
  192. request, *args, **kwargs)
  193. def get_form_kwargs(self):
  194. kwargs = super(AJAXMultipleImageUploadView, self).get_form_kwargs()
  195. # Prepare context for UserMediaImage form
  196. kwargs.update({
  197. 'user': self.user,
  198. 'content_type': self.c_type,
  199. 'object_id': self.obj_id,
  200. })
  201. return kwargs
  202. def form_valid(self, form):
  203. # Check if maximum amount of pictures has been reached
  204. try:
  205. max_pictures = int(self.request.POST.get('maximum'))
  206. except (TypeError, ValueError):
  207. max_pictures = getattr(settings, 'USER_MEDIA_UPLOAD_MAXIMUM', 3)
  208. stored_images = self.user.usermediaimage_set.filter(
  209. object_id=self.obj_id, content_type=self.c_type)
  210. if stored_images.count() >= max_pictures:
  211. return HttpResponse(_('Maximum amount limit exceeded.'))
  212. # Save the UserMediaImage
  213. self.object = form.save()
  214. f = self.request.FILES.get('image')
  215. # Generate and get the thumbnail of the uploaded image
  216. thumbnailer = get_thumbnailer(self.object.image.name)
  217. thumb = thumbnailer.get_thumbnail({
  218. 'crop': True, 'upscale': True,
  219. 'size': self.object.small_size(as_string=False),
  220. })
  221. # Prepare context for the list item html
  222. context_data = {
  223. 'image': self.object,
  224. 'mode': 'multiple',
  225. }
  226. context = RequestContext(self.request, context_data)
  227. # Prepare the json response
  228. data = {'files': [{
  229. 'name': f.name,
  230. 'url': self.object.image.url,
  231. 'thumbnail_url': thumb.url,
  232. 'list_item_html': render_to_string(
  233. 'user_media/partials/image.html', context),
  234. }]}
  235. response = HttpResponse(dumps(data), mimetype='application/json')
  236. response['Content-Disposition'] = 'inline; filename=files.json'
  237. return response
  238. class AJAXSingleImageUploadView(FormView):
  239. """Ajax view to handle the single image upload."""
  240. form_class = UserMediaImageSingleUploadForm
  241. template_name = 'user_media/partials/image.html'
  242. @method_decorator(login_required)
  243. def dispatch(self, request, *args, **kwargs):
  244. if not request.is_ajax() or not request.method == 'POST':
  245. raise Http404
  246. self.user = request.user
  247. # Check if the user posted a non existant content type
  248. try:
  249. self.c_type = ContentType.objects.get(model=kwargs.get('c_type'))
  250. except ContentType.DoesNotExist:
  251. raise Http404
  252. # Check if the content object exists
  253. try:
  254. self.content_object = self.c_type.get_object_for_this_type(
  255. pk=kwargs.get('obj_id'))
  256. except ObjectDoesNotExist:
  257. raise Http404
  258. # Check if content_object has the requested image field
  259. if hasattr(self.content_object, kwargs.get('field')):
  260. self.field_name = kwargs.get('field')
  261. else:
  262. raise Http404
  263. # Check for permissions
  264. if (not hasattr(self.content_object, 'user')
  265. or not self.content_object.user == self.user):
  266. if (not hasattr(self.content_object, 'user_can_edit')
  267. or not self.content_object.user_can_edit(self.user)):
  268. raise Http404
  269. return super(AJAXSingleImageUploadView, self).dispatch(
  270. request, *args, **kwargs)
  271. def get_form_kwargs(self):
  272. kwargs = super(AJAXSingleImageUploadView, self).get_form_kwargs()
  273. kwargs.update({
  274. 'instance': self.content_object,
  275. 'image_field': self.field_name,
  276. })
  277. return kwargs
  278. def form_valid(self, form):
  279. # Save the image
  280. self.content_object = form.save()
  281. f = self.request.FILES.get(self.field_name)
  282. image = getattr(self.content_object, self.field_name)
  283. size = getattr(settings, 'USER_MEDIA_THUMB_SIZE_LARGE', (150, 150))
  284. # Generate and get the thumbnail of the uploaded image
  285. thumbnailer = get_thumbnailer(image.name)
  286. thumb = thumbnailer.get_thumbnail({
  287. 'crop': True, 'upscale': True,
  288. 'size': size,
  289. })
  290. # Prepare context for the list item html
  291. context_data = {
  292. 'image': image,
  293. 'mode': 'single',
  294. 'size': (self.request.POST.get('size')
  295. or u'{}x{}'.format(size[0], size[1])),
  296. }
  297. context = RequestContext(self.request, context_data)
  298. # Prepare the json response
  299. data = {'files': [{
  300. 'name': f.name,
  301. 'url': image.url,
  302. 'thumbnail_url': thumb.url,
  303. 'list_item_html': render_to_string(self.template_name, context),
  304. }]}
  305. response = HttpResponse(dumps(data), mimetype='application/json')
  306. response['Content-Disposition'] = 'inline; filename=files.json'
  307. return response
  308. class AJAXImageCropView(UserMediaImageViewMixin, UpdateView):
  309. """Ajax view to update an image's crop attributes."""
  310. @method_decorator(login_required)
  311. def dispatch(self, request, *args, **kwargs):
  312. if not request.is_ajax() or not request.method == 'POST':
  313. raise Http404
  314. self.user = request.user
  315. self.kwargs = kwargs
  316. self.object = self.get_object()
  317. if not self.object.user == self.user:
  318. raise Http404
  319. for field in ['x', 'x2', 'y', 'y2', 'w', 'h']:
  320. # Save the Jcrop values to the image
  321. setattr(self.object, 'thumb_' + field, request.POST.get(field))
  322. self.object.save()
  323. box = (
  324. int(self.object.thumb_x),
  325. int(self.object.thumb_y),
  326. int(self.object.thumb_x2),
  327. int(self.object.thumb_y2),
  328. )
  329. thumbnailer = get_thumbnailer(self.object.image.name)
  330. thumb = thumbnailer.get_thumbnail({
  331. 'box': box,
  332. 'size': self.object.small_size(as_string=False),
  333. })
  334. return HttpResponse(thumb.url)