misc: add image/file upload for live edit (sync with panikdb)
[chloro.git] / chloro / phyll / views.py
1 # chloro - personal space
2 # Copyright (C) 2019  Frederic Peters
3 #
4 # This program is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published
6 # by the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 import os
18 import urllib.parse
19
20 from django.conf import settings
21 from django.contrib.syndication.views import Feed
22 from django.core.exceptions import PermissionDenied
23 from django.core.files.storage import default_storage
24 from django.http import HttpResponse, Http404, JsonResponse
25 from django.utils.feedgenerator import Atom1Feed
26 from django.views import View
27 from django.views.decorators.csrf import csrf_exempt
28 from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView, TemplateView
29
30 from sorl.thumbnail.shortcuts import get_thumbnail
31
32 from .models import Note
33
34
35 class NoteView(DetailView):
36     model = Note
37
38     def get(self, request, *args, **kwargs):
39         note = self.get_object()
40         if kwargs.get('year'):
41             # check date does match
42             creation = self.get_object().creation_timestamp
43             if (creation.year, creation.month, creation.day) != (
44                 int(kwargs['year']),
45                 int(kwargs['month']),
46                 int(kwargs['day']),
47             ):
48                 raise Http404()
49         if not note.published and not request.user.is_staff:
50             raise PermissionDenied()
51         return super(NoteView, self).get(request, *args, **kwargs)
52
53
54 class NoteEditView(UpdateView):
55     model = Note
56     fields = ['title', 'slug', 'text', 'tags', 'published']
57
58
59 class NoteApiSaveView(View):
60     http_method_names = ['post']
61
62     def post(self, request, *args, **kwargs):
63         note = Note.objects.get(slug=kwargs['slug'])
64         note.text = request.POST['text']
65         note.save()
66         return HttpResponse('ok')
67
68
69 class NoteAddView(CreateView):
70     model = Note
71     fields = ['title', 'slug', 'text', 'tags', 'published']
72
73
74 class NoteDeleteView(DeleteView):
75     model = Note
76
77     def get_success_url(self):
78         return '/'
79
80
81 class HomeView(TemplateView):
82     template_name = 'phyll/home.html'
83
84     def get_context_data(self, **kwargs):
85         context = super(HomeView, self).get_context_data(**kwargs)
86         context['posts'] = Note.objects.filter(published=True).order_by('-creation_timestamp')[:5]
87         return context
88
89
90 class ArchivesView(ListView):
91     model = Note
92
93     def get_queryset(self):
94         qs = super(ArchivesView, self).get_queryset()
95         if not self.request.user.is_staff:
96             qs = qs.filter(published=True)
97         return qs
98
99
100 class ListOnTagView(ListView):
101     model = Note
102
103     def get_queryset(self):
104         qs = Note.objects.filter(tags__name__in=[self.kwargs['tag']])
105         if not self.request.user.is_staff:
106             qs = qs.filter(published=True)
107         return qs
108
109
110 class Atom1FeedWithBaseXml(Atom1Feed):
111     def root_attributes(self):
112         root_attributes = super(Atom1FeedWithBaseXml, self).root_attributes()
113         scheme, netloc, path, params, query, fragment = urllib.parse.urlparse(self.feed['feed_url'])
114         root_attributes['xml:base'] = urllib.parse.urlunparse((scheme, netloc, '/', params, query, fragment))
115         return root_attributes
116
117
118 class AtomFeed(Feed):
119     title = settings.SITE_TITLE
120     link = '/'
121     feed_type = Atom1FeedWithBaseXml
122     author_name = settings.SITE_AUTHOR
123
124     def get_object(self, request, *args, **kwargs):
125         self.sub = kwargs.get('sub', 'default')
126         return super(AtomFeed, self).get_object(request, *args, **kwargs)
127
128     def items(self):
129         qs = Note.objects.filter(published=True)
130         if self.sub == 'default':
131             pass
132         elif self.sub == 'gnome-en':
133             qs = qs.filter(tags__name__in=['gnome']).filter(tags__name__in=['lang-en'])
134         else:
135             qs = qs.filter(tags__name__in=[self.sub])
136         return qs.select_related()[:20]
137
138     def item_description(self, item):
139         return item.text
140
141     def item_guid(self, item):
142         for tag in item.tags.all():
143             if tag.name.startswith('old-post-id-'):
144                 return 'http://www.0d.be/posts/%s' % tag.name.split('-')[-1]
145         return 'https://www.0d.be' + item.get_absolute_url()
146
147     def item_title(self, item):
148         return item.title
149
150     def item_pubdate(self, item):
151         return item.creation_timestamp
152
153
154 @csrf_exempt
155 def ajax_upload(request, *args, **kwargs):
156     upload = request.FILES['upload']
157     upload_path = 'uploads'
158     if os.path.splitext(upload.name.lower())[-1] in ('.jpg', '.jpeg', '.png', '.gif', '.svg'):
159         upload_path = 'images'
160     saved_path = default_storage.save('%s/%s' % (upload_path, upload.name), upload)
161     url = '/media/' + saved_path
162     response = {'url': url, 'filename': upload.name}
163     if upload_path == 'images':
164         if default_storage.size(saved_path) > 500_000 and not upload.name.endswith('.svg'):
165             response['orig_url'] = url
166             try:
167                 response['url'] = get_thumbnail(saved_path, '1000', upscale=False).url
168             except OSError:
169                 pass
170     return JsonResponse(response)