1 # chloro - personal space
2 # Copyright (C) 2019 Frederic Peters
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.
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.
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/>.
20 from django.conf import settings
21 from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
22 from django.contrib.syndication.views import Feed
23 from django.core.exceptions import PermissionDenied
24 from django.core.files.storage import default_storage
25 from django.http import Http404, HttpResponse, JsonResponse
26 from django.utils.feedgenerator import Atom1Feed
27 from django.utils.text import slugify
28 from django.views import View
29 from django.views.decorators.csrf import csrf_exempt
30 from django.views.generic import CreateView, DeleteView, DetailView, ListView, TemplateView, UpdateView
31 from sorl.thumbnail.shortcuts import get_thumbnail
33 from .models import Note
36 class NoteView(DetailView):
39 def get(self, request, *args, **kwargs):
40 note = self.get_object()
41 if kwargs.get('year'):
42 # check date does match
43 creation = self.get_object().creation_timestamp
44 if (creation.year, creation.month, creation.day) != (
50 if not note.published and not request.user.is_staff:
51 raise PermissionDenied()
52 return super().get(request, *args, **kwargs)
55 class NoteEditView(UpdateView):
57 fields = ['title', 'slug', 'text', 'tags', 'published']
60 class NoteApiSaveView(View):
61 http_method_names = ['post']
63 def post(self, request, *args, **kwargs):
64 note = Note.objects.get(slug=kwargs['slug'])
65 note.text = request.POST['text']
67 return HttpResponse('ok')
70 class NoteAddView(CreateView):
72 fields = ['title', 'slug', 'text', 'tags', 'published']
75 class NoteDeleteView(DeleteView):
78 def get_success_url(self):
82 class HomeView(TemplateView):
83 template_name = 'phyll/home.html'
85 def get_context_data(self, **kwargs):
86 context = super().get_context_data(**kwargs)
87 context['posts'] = Note.objects.filter(published=True).order_by('-creation_timestamp')[:5]
88 context['recent_changes'] = Note.objects.filter(published=True).order_by('-last_update_timestamp')
89 context['index'] = Note.objects.filter(slug='index').first()
93 class ArchivesView(ListView):
96 def get_queryset(self):
97 qs = super().get_queryset()
98 if not self.request.user.is_staff:
99 qs = qs.filter(published=True)
103 class ListOnTagView(ListView):
106 def get_queryset(self):
107 qs = Note.objects.filter(tags__name__in=[self.kwargs['tag']])
108 if not self.request.user.is_staff:
109 qs = qs.filter(published=True)
113 class Atom1FeedWithBaseXml(Atom1Feed):
114 def root_attributes(self):
115 root_attributes = super().root_attributes()
116 scheme, netloc, path, params, query, fragment = urllib.parse.urlparse(self.feed['feed_url'])
117 root_attributes['xml:base'] = urllib.parse.urlunparse((scheme, netloc, '/', params, query, fragment))
118 return root_attributes
121 class AtomFeed(Feed):
122 title = settings.SITE_TITLE
124 feed_type = Atom1FeedWithBaseXml
125 author_name = settings.SITE_AUTHOR
127 def get_object(self, request, *args, **kwargs):
128 self.sub = kwargs.get('sub', 'default')
129 return super().get_object(request, *args, **kwargs)
132 qs = Note.objects.filter(published=True)
133 if self.sub == 'default':
135 elif self.sub == 'gnome-en':
136 qs = qs.filter(tags__name__in=['gnome']).filter(tags__name__in=['lang-en'])
138 qs = qs.filter(tags__name__in=[self.sub])
139 return qs.select_related()[:20]
141 def item_description(self, item):
144 def item_guid(self, item):
145 for tag in item.tags.all():
146 if tag.name.startswith('old-post-id-'):
147 return 'http://www.0d.be/posts/%s' % tag.name.split('-')[-1]
148 return 'https://www.0d.be' + item.get_absolute_url()
150 def item_title(self, item):
153 def item_pubdate(self, item):
154 return item.creation_timestamp
158 def ajax_upload(request, *args, **kwargs):
159 upload = request.FILES['upload']
160 upload_path = 'uploads'
161 if os.path.splitext(upload.name.lower())[-1] in ('.jpg', '.jpeg', '.png', '.gif', '.svg'):
162 upload_path = 'images'
163 saved_path = default_storage.save('%s/%s' % (upload_path, upload.name), upload)
164 url = '/media/' + saved_path
165 response = {'url': url, 'filename': upload.name}
166 if upload_path == 'images':
167 if default_storage.size(saved_path) > 500_000 and not upload.name.endswith('.svg'):
168 response['orig_url'] = url
170 response['url'] = get_thumbnail(saved_path, '1000', upscale=False).url
173 return JsonResponse(response)
177 def ajax_new_page(request, *args, **kwargs):
178 title = request.POST['title']
179 slug = slugify(title)
180 note, created = Note.objects.get_or_create(slug=slug)
183 note.text = '<p>...</p>'
187 'url': '/%s/' % note.slug,
188 'request_id': request.POST['request_id'],
193 def ajax_search(request, *args, **kwargs):
194 vector = SearchVector('title', weight='A', config='french') + SearchVector(
195 'plain_text', weight='B', config='french'
197 query = SearchQuery(request.GET.get('q', ''), config='french')
199 Note.objects.annotate(rank=SearchRank(vector, query)).filter(rank__gte=0.1).order_by('-rank')[:10]
202 {'data': [{'title': x.title, 'rank': x.rank, 'url': x.get_absolute_url()} for x in results]}