# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from django.views.generic import CreateView, DeleteView, DetailView, UpdateView, TemplateView
+import os
+import urllib.parse
+
+from django.conf import settings
+from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
+from django.contrib.syndication.views import Feed
+from django.core.exceptions import PermissionDenied
+from django.core.files.storage import default_storage
+from django.http import Http404, HttpResponse, JsonResponse
+from django.utils.feedgenerator import Atom1Feed
+from django.utils.text import slugify
+from django.views import View
+from django.views.decorators.csrf import csrf_exempt
+from django.views.generic import CreateView, DeleteView, DetailView, ListView, TemplateView, UpdateView
+from sorl.thumbnail.shortcuts import get_thumbnail
from .models import Note
class NoteView(DetailView):
model = Note
+ def get(self, request, *args, **kwargs):
+ note = self.get_object()
+ if kwargs.get('year'):
+ # check date does match
+ creation = self.get_object().creation_timestamp
+ if (creation.year, creation.month, creation.day) != (
+ int(kwargs['year']),
+ int(kwargs['month']),
+ int(kwargs['day']),
+ ):
+ raise Http404()
+ if not note.published and not request.user.is_staff:
+ raise PermissionDenied()
+ return super().get(request, *args, **kwargs)
+
class NoteEditView(UpdateView):
model = Note
- fields = ['title', 'slug', 'text', 'tags']
+ fields = ['title', 'slug', 'text', 'tags', 'published', 'included_in_feed']
+
+
+class NoteApiSaveView(View):
+ http_method_names = ['post']
+
+ def post(self, request, *args, **kwargs):
+ note = Note.objects.get(slug=kwargs['slug'])
+ note.text = request.POST['text']
+ note.save()
+ return HttpResponse('ok')
class NoteAddView(CreateView):
model = Note
- fields = ['title', 'slug', 'text', 'tags']
+ fields = ['title', 'slug', 'text', 'tags', 'published', 'included_in_feed']
class NoteDeleteView(DeleteView):
class HomeView(TemplateView):
template_name = 'phyll/home.html'
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['posts'] = Note.objects.filter(published=True, included_in_feed=True).order_by(
+ '-creation_timestamp'
+ )[:5]
+ context['recent_changes'] = Note.objects.filter(published=True, included_in_feed=True).order_by(
+ '-last_update_timestamp'
+ )
+ context['index'] = Note.objects.filter(slug='index').first()
+ return context
+
+
+class ArchivesView(ListView):
+ model = Note
+
+ def get_queryset(self):
+ qs = super().get_queryset()
+ if not self.request.user.is_staff:
+ qs = qs.filter(published=True, included_in_feed=True)
+ return qs
+
+
+class ListOnTagView(ListView):
+ model = Note
+
+ def get_queryset(self):
+ qs = Note.objects.filter(tags__name__in=[self.kwargs['tag']])
+ if not self.request.user.is_staff:
+ qs = qs.filter(published=True)
+ return qs
+
+
+class Atom1FeedWithBaseXml(Atom1Feed):
+ def root_attributes(self):
+ root_attributes = super().root_attributes()
+ scheme, netloc, path, params, query, fragment = urllib.parse.urlparse(self.feed['feed_url'])
+ root_attributes['xml:base'] = urllib.parse.urlunparse((scheme, netloc, '/', params, query, fragment))
+ return root_attributes
+
+
+class AtomFeed(Feed):
+ title = settings.SITE_TITLE
+ link = '/'
+ feed_type = Atom1FeedWithBaseXml
+ author_name = settings.SITE_AUTHOR
+
+ def get_object(self, request, *args, **kwargs):
+ self.sub = kwargs.get('sub', 'default')
+ return super().get_object(request, *args, **kwargs)
+
+ def items(self):
+ qs = Note.objects.filter(published=True, included_in_feed=True)
+ if self.sub == 'default':
+ pass
+ elif self.sub == 'gnome-en':
+ qs = qs.filter(tags__name__in=['gnome']).filter(tags__name__in=['lang-en'])
+ else:
+ qs = qs.filter(tags__name__in=[self.sub])
+ return qs.select_related()[:20]
+
+ def item_description(self, item):
+ return item.text
+
+ def item_guid(self, item):
+ for tag in item.tags.all():
+ if tag.name.startswith('old-post-id-'):
+ return 'http://www.0d.be/posts/%s' % tag.name.split('-')[-1]
+ return 'https://www.0d.be' + item.get_absolute_url()
+
+ def item_title(self, item):
+ return item.title
+
+ def item_pubdate(self, item):
+ return item.creation_timestamp
+
+
+@csrf_exempt
+def ajax_upload(request, *args, **kwargs):
+ upload = request.FILES['upload']
+ upload_path = 'uploads'
+ if os.path.splitext(upload.name.lower())[-1] in ('.jpg', '.jpeg', '.png', '.gif', '.svg'):
+ upload_path = 'images'
+ saved_path = default_storage.save('%s/%s' % (upload_path, upload.name), upload)
+ url = '/media/' + saved_path
+ response = {'url': url, 'filename': upload.name}
+ if upload_path == 'images':
+ if default_storage.size(saved_path) > 500_000 and not upload.name.endswith('.svg'):
+ response['orig_url'] = url
+ try:
+ response['url'] = get_thumbnail(saved_path, '1000', upscale=False).url
+ except OSError:
+ pass
+ return JsonResponse(response)
+
+
+@csrf_exempt
+def ajax_new_page(request, *args, **kwargs):
+ title = request.POST['title']
+ slug = slugify(title)
+ note, created = Note.objects.get_or_create(slug=slug)
+ if created:
+ note.title = title
+ note.text = '<p>...</p>'
+ note.save()
+ return JsonResponse(
+ {
+ 'url': '/%s/' % note.slug,
+ 'request_id': request.POST['request_id'],
+ }
+ )
+
+
+def ajax_search(request, *args, **kwargs):
+ vector = SearchVector('title', weight='A', config='french') + SearchVector(
+ 'plain_text', weight='B', config='french'
+ )
+ query = SearchQuery(request.GET.get('q', ''), config='french')
+ results = (
+ Note.objects.annotate(rank=SearchRank(vector, query)).filter(rank__gte=0.1).order_by('-rank')[:10]
+ )
+ return JsonResponse(
+ {'data': [{'title': x.title, 'rank': x.rank, 'url': x.get_absolute_url()} for x in results]}
+ )