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