user_media.views: 175 total statements, 100.0% covered

Generated: Mon 2013-11-25 14:30 CET

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

Stats: 160 executed, 0 missed, 15 excluded, 182 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({'crop': True, 'upscale': True,
  214. 'size': (50, 50)})
  215. # Prepare context for the list item html
  216. context_data = {
  217. 'image': self.object,
  218. 'mode': 'multiple',
  219. }
  220. context = RequestContext(self.request, context_data)
  221. # Prepare the json response
  222. data = {'files': [{
  223. 'name': f.name,
  224. 'url': self.object.image.url,
  225. 'thumbnail_url': thumb.url,
  226. 'list_item_html': render_to_string(
  227. 'user_media/partials/image.html', context),
  228. }]}
  229. response = HttpResponse(dumps(data), mimetype='application/json')
  230. response['Content-Disposition'] = 'inline; filename=files.json'
  231. return response
  232. class AJAXSingleImageUploadView(FormView):
  233. """Ajax view to handle the single image upload."""
  234. form_class = UserMediaImageSingleUploadForm
  235. template_name = 'user_media/partials/image.html'
  236. @method_decorator(login_required)
  237. def dispatch(self, request, *args, **kwargs):
  238. if not request.is_ajax() or not request.method == 'POST':
  239. raise Http404
  240. self.user = request.user
  241. # Check if the user posted a non existant content type
  242. try:
  243. self.c_type = ContentType.objects.get(model=kwargs.get('c_type'))
  244. except ContentType.DoesNotExist:
  245. raise Http404
  246. # Check if the content object exists
  247. try:
  248. self.content_object = self.c_type.get_object_for_this_type(
  249. pk=kwargs.get('obj_id'))
  250. except ObjectDoesNotExist:
  251. raise Http404
  252. # Check if content_object has the requested image field
  253. if hasattr(self.content_object, kwargs.get('field')):
  254. self.field_name = kwargs.get('field')
  255. self.image_field = getattr(self.content_object, self.field_name)
  256. else:
  257. raise Http404
  258. # Check for permissions
  259. if (not hasattr(self.content_object, 'user')
  260. or not self.content_object.user == self.user):
  261. raise Http404
  262. return super(AJAXSingleImageUploadView, self).dispatch(
  263. request, *args, **kwargs)
  264. def get_form_kwargs(self):
  265. kwargs = super(AJAXSingleImageUploadView, self).get_form_kwargs()
  266. kwargs.update({
  267. 'instance': self.content_object,
  268. 'image_field': self.field_name,
  269. })
  270. return kwargs
  271. def form_valid(self, form):
  272. # Save the image
  273. self.content_object = form.save()
  274. f = self.request.FILES.get(self.field_name)
  275. image = getattr(self.content_object, self.field_name)
  276. # Generate and get the thumbnail of the uploaded image
  277. thumbnailer = get_thumbnailer(image.name)
  278. thumb = thumbnailer.get_thumbnail({'crop': True, 'upscale': True,
  279. 'size': (50, 50)})
  280. # Prepare context for the list item html
  281. context_data = {
  282. 'image': image,
  283. 'mode': 'single',
  284. 'size': self.request.POST.get('size') or '150x150',
  285. }
  286. context = RequestContext(self.request, context_data)
  287. # Prepare the json response
  288. data = {'files': [{
  289. 'name': f.name,
  290. 'url': image.url,
  291. 'thumbnail_url': thumb.url,
  292. 'list_item_html': render_to_string(self.template_name, context),
  293. }]}
  294. response = HttpResponse(dumps(data), mimetype='application/json')
  295. response['Content-Disposition'] = 'inline; filename=files.json'
  296. return response