user_media.views: 191 total statements, 100.0% covered

Generated: Wed 2014-02-05 09:19 CET

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

Stats: 176 executed, 0 missed, 15 excluded, 201 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. if (not hasattr(self.content_object, 'user')
  185. or not self.content_object.user == self.user):
  186. raise Http404
  187. return super(AJAXMultipleImageUploadView, self).dispatch(
  188. request, *args, **kwargs)
  189. def get_form_kwargs(self):
  190. kwargs = super(AJAXMultipleImageUploadView, self).get_form_kwargs()
  191. # Prepare context for UserMediaImage form
  192. kwargs.update({
  193. 'user': self.user,
  194. 'content_type': self.c_type,
  195. 'object_id': self.obj_id,
  196. })
  197. return kwargs
  198. def form_valid(self, form):
  199. # Check if maximum amount of pictures has been reached
  200. try:
  201. max_pictures = int(self.request.POST.get('maximum'))
  202. except (TypeError, ValueError):
  203. max_pictures = getattr(settings, 'USER_MEDIA_UPLOAD_MAXIMUM', 3)
  204. stored_images = self.user.usermediaimage_set.filter(
  205. object_id=self.obj_id, content_type=self.c_type)
  206. if stored_images.count() >= max_pictures:
  207. return HttpResponse(_('Maximum amount limit exceeded.'))
  208. # Save the UserMediaImage
  209. self.object = form.save()
  210. f = self.request.FILES.get('image')
  211. # Generate and get the thumbnail of the uploaded image
  212. thumbnailer = get_thumbnailer(self.object.image.name)
  213. thumb = thumbnailer.get_thumbnail({
  214. 'crop': True, 'upscale': True,
  215. 'size': self.object.small_size(as_string=False),
  216. })
  217. # Prepare context for the list item html
  218. context_data = {
  219. 'image': self.object,
  220. 'mode': 'multiple',
  221. }
  222. context = RequestContext(self.request, context_data)
  223. # Prepare the json response
  224. data = {'files': [{
  225. 'name': f.name,
  226. 'url': self.object.image.url,
  227. 'thumbnail_url': thumb.url,
  228. 'list_item_html': render_to_string(
  229. 'user_media/partials/image.html', context),
  230. }]}
  231. response = HttpResponse(dumps(data), mimetype='application/json')
  232. response['Content-Disposition'] = 'inline; filename=files.json'
  233. return response
  234. class AJAXSingleImageUploadView(FormView):
  235. """Ajax view to handle the single image upload."""
  236. form_class = UserMediaImageSingleUploadForm
  237. template_name = 'user_media/partials/image.html'
  238. @method_decorator(login_required)
  239. def dispatch(self, request, *args, **kwargs):
  240. if not request.is_ajax() or not request.method == 'POST':
  241. raise Http404
  242. self.user = request.user
  243. # Check if the user posted a non existant content type
  244. try:
  245. self.c_type = ContentType.objects.get(model=kwargs.get('c_type'))
  246. except ContentType.DoesNotExist:
  247. raise Http404
  248. # Check if the content object exists
  249. try:
  250. self.content_object = self.c_type.get_object_for_this_type(
  251. pk=kwargs.get('obj_id'))
  252. except ObjectDoesNotExist:
  253. raise Http404
  254. # Check if content_object has the requested image field
  255. if hasattr(self.content_object, kwargs.get('field')):
  256. self.field_name = kwargs.get('field')
  257. else:
  258. raise Http404
  259. # Check for permissions
  260. if (not hasattr(self.content_object, 'user')
  261. or not self.content_object.user == self.user):
  262. raise Http404
  263. return super(AJAXSingleImageUploadView, self).dispatch(
  264. request, *args, **kwargs)
  265. def get_form_kwargs(self):
  266. kwargs = super(AJAXSingleImageUploadView, self).get_form_kwargs()
  267. kwargs.update({
  268. 'instance': self.content_object,
  269. 'image_field': self.field_name,
  270. })
  271. return kwargs
  272. def form_valid(self, form):
  273. # Save the image
  274. self.content_object = form.save()
  275. f = self.request.FILES.get(self.field_name)
  276. image = getattr(self.content_object, self.field_name)
  277. size = getattr(settings, 'USER_MEDIA_THUMB_SIZE_LARGE', (150, 150))
  278. # Generate and get the thumbnail of the uploaded image
  279. thumbnailer = get_thumbnailer(image.name)
  280. thumb = thumbnailer.get_thumbnail({
  281. 'crop': True, 'upscale': True,
  282. 'size': size,
  283. })
  284. # Prepare context for the list item html
  285. context_data = {
  286. 'image': image,
  287. 'mode': 'single',
  288. 'size': (self.request.POST.get('size')
  289. or u'{}x{}'.format(size[0], size[1])),
  290. }
  291. context = RequestContext(self.request, context_data)
  292. # Prepare the json response
  293. data = {'files': [{
  294. 'name': f.name,
  295. 'url': image.url,
  296. 'thumbnail_url': thumb.url,
  297. 'list_item_html': render_to_string(self.template_name, context),
  298. }]}
  299. response = HttpResponse(dumps(data), mimetype='application/json')
  300. response['Content-Disposition'] = 'inline; filename=files.json'
  301. return response
  302. class AJAXImageCropView(UserMediaImageViewMixin, UpdateView):
  303. """Ajax view to update an image's crop attributes."""
  304. @method_decorator(login_required)
  305. def dispatch(self, request, *args, **kwargs):
  306. if not request.is_ajax() or not request.method == 'POST':
  307. raise Http404
  308. self.user = request.user
  309. self.kwargs = kwargs
  310. self.object = self.get_object()
  311. if not self.object.user == self.user:
  312. raise Http404
  313. for field in ['x', 'x2', 'y', 'y2', 'w', 'h']:
  314. # Save the Jcrop values to the image
  315. setattr(self.object, 'thumb_' + field, request.POST.get(field))
  316. self.object.save()
  317. box = (
  318. int(self.object.thumb_x),
  319. int(self.object.thumb_y),
  320. int(self.object.thumb_x2),
  321. int(self.object.thumb_y2),
  322. )
  323. thumbnailer = get_thumbnailer(self.object.image.name)
  324. thumb = thumbnailer.get_thumbnail({
  325. 'box': box,
  326. 'size': self.object.small_size(as_string=False),
  327. })
  328. return HttpResponse(thumb.url)