Handle image reupload correctly.
[redakcja.git] / apps / fileupload / views.py
1 import json
2 import os
3 from urllib import quote
4 from django.conf import settings
5 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404
6 from django.utils.decorators import method_decorator
7 from django.views.decorators.vary import vary_on_headers
8 from django.views.generic import FormView, View
9 from .forms import UploadForm
10
11
12 # Use sorl.thumbnail if available.
13 try:
14     from sorl.thumbnail import default
15 except ImportError:
16     def thumbnail(relpath):
17         return settings.MEDIA_URL + relpath
18 else:
19     def thumbnail(relpath):
20         try:
21             return default.backend.get_thumbnail(relpath, "x50").url
22         except IOError:
23             # That's not an image. No thumb.
24             return None
25
26
27 class JSONResponse(HttpResponse):
28     """JSON response class."""
29     def __init__(self, obj='', mimetype="application/json", *args, **kwargs):
30         content = json.dumps(obj)
31         super(JSONResponse, self).__init__(content, mimetype, *args, **kwargs)
32
33
34 class UploadView(FormView):
35     template_name = "fileupload/picture_form.html"
36     form_class = UploadForm
37
38     def get_object(self, request, *args, **kwargs):
39         """Get any data for later use."""
40         return None
41
42     def get_directory(self):
43         """Directory relative to MEDIA_ROOT. Must end with a slash."""
44         return self.kwargs['path']
45
46     def breadcrumbs(self):
47         """List of tuples (name, url) or just (name,) for breadcrumbs.
48
49         Probably only the last item (representing currently browsed dir)
50         should lack url.
51
52         """
53         directory = self.get_directory()
54         now_path = os.path.dirname(self.request.get_full_path())
55         directory = os.path.dirname(directory)
56         if directory:
57             crumbs = [
58                 (os.path.basename(directory),)
59             ]
60             directory = os.path.dirname(directory)
61             now_path = (os.path.dirname(now_path))
62             while directory:
63                 crumbs.insert(0, (os.path.basename(directory), now_path+'/'))
64                 directory = os.path.dirname(directory)
65                 now_path = os.path.dirname(now_path)
66             crumbs.insert(0, ('media', now_path))
67         else:
68             crumbs = [('media',)]
69         return crumbs
70
71     def get_safe_path(self, filename=""):
72         """Finds absolute filesystem path of the browsed dir of file.
73         
74         Makes sure it's inside MEDIA_ROOT.
75         
76         """
77         path = os.path.abspath(os.path.join(
78                 settings.MEDIA_ROOT,
79                 self.get_directory(),
80                 filename))
81         if not path.startswith(settings.MEDIA_ROOT):
82             raise Http404
83         if filename:
84             if not path.startswith(self.get_safe_path()):
85                 raise Http404
86         return path
87
88     def get_url(self, filename):
89         """Finds URL of a file in browsed dir."""
90         return settings.MEDIA_URL + self.get_directory() + quote(filename.encode('utf-8'))
91
92     @method_decorator(vary_on_headers('Accept'))
93     def dispatch(self, request, *args, **kwargs):
94         self.object = self.get_object(request, *args, **kwargs)
95         return super(UploadView, self).dispatch(request, *args, **kwargs)
96
97     def get(self, request, *args, **kwargs):
98         if request.is_ajax():
99             files = []
100             path = self.get_safe_path()
101             if os.path.isdir(path):
102                 for f in sorted(os.listdir(path)):
103                     file_info = {
104                         "name": f,
105                     }
106                     if os.path.isdir(os.path.join(path, f)):
107                         file_info.update({
108                             "url": "%s%s/" % (request.get_full_path(), f),
109                         })
110                     else:
111                         file_info.update({
112                             "url": self.get_url(f),
113                             'thumbnail_url': thumbnail(self.get_directory() + f),
114                             'delete_url': "%s?file=%s" % (
115                                 request.get_full_path(),
116                                 quote(f.encode('utf-8'))),
117                             'delete_type': "DELETE"
118                         })
119                         thumbnail_url = thumbnail(self.get_directory() + f),
120                     files.append(file_info)
121             return JSONResponse(files)
122         else:
123             return super(UploadView, self).get(request, *args, **kwargs)
124
125     def form_valid(self, form):
126         flist = self.request.FILES.getlist('files')
127         path = self.get_safe_path()
128         if not os.path.isdir(path):
129             os.makedirs(path)
130         data = []
131         for f in flist:
132             with open(self.get_safe_path(f.name), 'w') as destination:
133                 for chunk in f.chunks():
134                     destination.write(chunk)
135             data.append({
136                 'name': f.name, 
137                 'url': self.get_url(f.name),
138                 'thumbnail_url': thumbnail(self.get_directory() + f.name),
139                         'delete_url': "%s?file=%s" % (
140                             self.request.get_full_path(),
141                             quote(f.name.encode('utf-8'))),
142                 'delete_type': "DELETE"
143             })
144         response = JSONResponse(data)
145         response['Content-Disposition'] = 'inline; filename=files.json'
146         return response
147
148     def delete(self, request, *args, **kwargs):
149         os.unlink(self.get_safe_path(request.GET.get('file')))
150         response = JSONResponse(True)
151         response['Content-Disposition'] = 'inline; filename=files.json'
152         return response