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