From 691434b403a273c98e975d17863fe79d37cf6afe Mon Sep 17 00:00:00 2001 From: rizotas Date: Fri, 24 Jul 2015 00:18:16 -0500 Subject: [PATCH 1/5] #74 --- tests/test_git.py | 8 ++++---- waliki/git/__init__.py | 22 +++++++++++++++++++++- waliki/git/views.py | 13 +++++++------ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/tests/test_git.py b/tests/test_git.py index a590e99..0282c1b 100644 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -32,7 +32,7 @@ def test_commit_existent_page_with_no_previous_commits(self): data["message"] = "testing :)" response = self.client.post(self.edit_url, data) self.assertEqual(self.page.raw, "test content") - self.assertEqual(Git().version(self.page, 'HEAD'), "test content") + self.assertEqual(Git().version(self.page, 'HEAD')["raw"], "test content") self.assertIn("testing :)", git.log('-s', '--format=%s', self.page.path)) def test_commit_existent_page_with_previous_commits(self): @@ -44,7 +44,7 @@ def test_commit_existent_page_with_previous_commits(self): data["raw"] = "test content" response = self.client.post(self.edit_url, data) self.assertEqual(self.page.raw, "test content") - self.assertEqual(Git().version(self.page, 'HEAD'), "test content") + self.assertEqual(Git().version(self.page, 'HEAD')["raw"], "test content") def test_commit_new_page(self): assert not Page.objects.filter(slug='test').exists() @@ -60,7 +60,7 @@ def test_commit_new_page(self): page = Page.objects.get(slug='test') self.assertEqual(page.title, "Test Page") self.assertEqual(page.raw, "hey there\n") - self.assertEqual(Git().version(page, 'HEAD'), "hey there\n") + self.assertEqual(Git().version(page, 'HEAD')["raw"], "hey there\n") def test_concurrent_edition_with_no_conflict(self): self.page.raw = "\n- item1\n" @@ -170,7 +170,7 @@ def test_unicode_content(self): data['message'] = 'testing :)' response = self.client.post(self.edit_url, data) self.assertRedirects(response, reverse('waliki_detail', args=(self.page.slug,))) - self.assertEqual(Git().version(self.page, 'HEAD'), u'Ω') + self.assertEqual(Git().version(self.page, 'HEAD')["raw"], u'Ω') def test_commit_page_with_no_changes(self): self.page.raw = 'lala' diff --git a/waliki/git/__init__.py b/waliki/git/__init__.py index 094b7c9..3695e2b 100644 --- a/waliki/git/__init__.py +++ b/waliki/git/__init__.py @@ -1,6 +1,7 @@ import os import re import json +from io import StringIO from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ from django.utils import six @@ -98,7 +99,26 @@ def history(self, page): def version(self, page, version): try: - return git.show('%s:%s' % (version, page.path)).stdout.decode('utf8') + out = str(git.show('-s', "--pretty=format:%aN%n%aD%n%B%n%h%e", version)) + + buf = StringIO() + buf.write(out) + buf.seek(0) + author = str(buf.readline()) + date = str(buf.readline()) + message = str(buf.readline()) + buf.close() + + raw = str(git.show('%s:%s' % (version, page.path))) + + data = { + "author": author, + "date": date, + "message": message, + "raw": raw, + } + + return data except: return '' diff --git a/waliki/git/views.py b/waliki/git/views.py index 57d7a58..157a235 100644 --- a/waliki/git/views.py +++ b/waliki/git/views.py @@ -45,15 +45,16 @@ def history(request, slug, pag=1): def version(request, slug, version, raw=False): page = get_object_or_404(Page, slug=slug) content = Git().version(page, version) - if not content: - raise Http404 + if not content["raw"]: + return HttpResponse(json.dumps(content), content_type='application/json') + form = PageForm(instance=page, initial={'message': _('Restored version @%s') % version, 'raw': content}, is_hidden=True) if raw: - return HttpResponse(content, content_type='text/plain; charset=utf-8') + return HttpResponse(json.dumps(content), content_type='application/json') - content = Page.preview(page.markup, content) + content = Page.preview(page.markup, content["raw"]) return render(request, 'waliki/version.html', {'page': page, 'content': content, 'slug': slug, @@ -69,8 +70,8 @@ def diff(request, slug, old, new, raw=False): return HttpResponse(content, content_type='text/plain; charset=utf-8') space = smart_text(b'\xc2\xa0', encoding='utf-8') # non-breaking space character tab = space * 4 - old_content = Git().version(page, old).replace('\t', tab).replace(' ', space) - new_content = Git().version(page, new).replace('\t', tab).replace(' ', space) + old_content = Git().version(page, old)["raw"].replace('\t', tab).replace(' ', space) + new_content = Git().version(page, new)["raw"].replace('\t', tab).replace(' ', space) return render(request, 'waliki/diff.html', {'page': page, 'old_content': old_content, 'new_content': new_content, From b32831cd62aa29d19d03b6d4108f9c11be23739e Mon Sep 17 00:00:00 2001 From: rizotas Date: Fri, 24 Jul 2015 00:29:51 -0500 Subject: [PATCH 2/5] json response to page empty --- waliki/git/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/waliki/git/views.py b/waliki/git/views.py index 157a235..fadf719 100644 --- a/waliki/git/views.py +++ b/waliki/git/views.py @@ -45,8 +45,6 @@ def history(request, slug, pag=1): def version(request, slug, version, raw=False): page = get_object_or_404(Page, slug=slug) content = Git().version(page, version) - if not content["raw"]: - return HttpResponse(json.dumps(content), content_type='application/json') form = PageForm(instance=page, initial={'message': _('Restored version @%s') % version, 'raw': content}, is_hidden=True) @@ -54,7 +52,10 @@ def version(request, slug, version, raw=False): if raw: return HttpResponse(json.dumps(content), content_type='application/json') - content = Page.preview(page.markup, content["raw"]) + if content["raw"]: + content = Page.preview(page.markup, content["raw"]) + else: + content = '' return render(request, 'waliki/version.html', {'page': page, 'content': content, 'slug': slug, From b512c202f82486857734e844fd7d829479e7af25 Mon Sep 17 00:00:00 2001 From: rizotas Date: Fri, 24 Jul 2015 15:31:15 -0500 Subject: [PATCH 3/5] add rest api --- runtests.py | 1 + setup.py | 3 +- tests/tests_rest.py | 119 ++++++++++++ waliki/rest/__init__.py | 0 waliki/rest/permissions.py | 35 ++++ waliki/rest/serializers.py | 113 ++++++++++++ waliki/rest/urls.py | 18 ++ waliki/rest/views.py | 211 ++++++++++++++++++++++ waliki/rest/waliki_plugin.py | 9 + waliki/settings.py | 2 + waliki_project/waliki_project/settings.py | 2 + 11 files changed, 512 insertions(+), 1 deletion(-) create mode 100644 tests/tests_rest.py create mode 100644 waliki/rest/__init__.py create mode 100644 waliki/rest/permissions.py create mode 100644 waliki/rest/serializers.py create mode 100644 waliki/rest/urls.py create mode 100644 waliki/rest/views.py create mode 100644 waliki/rest/waliki_plugin.py diff --git a/runtests.py b/runtests.py index fbfc9a7..3d57c5e 100755 --- a/runtests.py +++ b/runtests.py @@ -26,6 +26,7 @@ "waliki.git", "waliki.attachments", "waliki.slides", + "waliki.rest", ), TEMPLATE_CONTEXT_PROCESSORS = ( "django.contrib.auth.context_processors.auth", diff --git a/setup.py b/setup.py index 075dcd1..57ba236 100755 --- a/setup.py +++ b/setup.py @@ -30,7 +30,8 @@ 'markdown': ['markdown'], 'attachments': ['django-sendfile'], 'pdf': ['rst2pdf'], - 'slides': ['hovercraft'] + 'slides': ['hovercraft'], + 'rest': ['djangorestframework'] } everything = set() diff --git a/tests/tests_rest.py b/tests/tests_rest.py new file mode 100644 index 0000000..a1b9726 --- /dev/null +++ b/tests/tests_rest.py @@ -0,0 +1,119 @@ +from django.core.urlresolvers import reverse +from django.conf import settings + +from rest_framework import status +from rest_framework.test import APITestCase + +from waliki.models import Page + + +class PageCreateTests(APITestCase): + + def test_create_page_anonymous(self): + """ + #Ensure a new Page can't be created by a Anonymous user without permission + """ + url = reverse('page_new') + data = {'title': 'Title', 'slug':'title', 'markup': 'Markdown'} + response = self.client.post(url, data) + + if 'add_page' in settings.WALIKI_ANONYMOUS_USER_PERMISSIONS: + #if anonymous user can add page + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + else: + #anonymous user can't add page + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + +class PageRetrieveTests(APITestCase): + title = 'My little Title' + slug = 'my-little-title' + markup = 'Markdown' + + raw = 'My hack' + message = 'Fuck you' + + def setUp(self): + Page.objects.create(title=self.title, slug=self.slug, markup=self.markup) + + def test_detail_page_anonymous(self): + """ + Ensure a Page can't be watched by a Anonymous user without permission + """ + url = reverse('page_detail', args=(self.slug,)) + response = self.client.get(url) + + if 'view_page' in settings.WALIKI_ANONYMOUS_USER_PERMISSIONS: + #if anonymous user can view a page + self.assertEqual(response.status_code, status.HTTP_200_OK) + else: + #if anonymous user can't view a page + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + +class PageEditTests(APITestCase): + title = 'My little Title' + slug = 'my-little-title' + markup = 'Markdown' + + raw = 'My hack' + message = 'Fuck you' + + def setUp(self): + Page.objects.create(title=self.title, slug=self.slug, markup=self.markup) + + def test_edit_page_anonymous(self): + """ + #Ensure a Page can't be edited by a Anonymous user without permission + """ + url = reverse('page_edit', args=(self.slug,)) + data = { + 'title': self.title, + 'slug': self.slug, + 'markup': self.markup, + 'raw': self.raw, + 'message': self.message } + + response = self.client.post(url, data) + + if 'change_page' in settings.WALIKI_ANONYMOUS_USER_PERMISSIONS: + #if anonymous user can view a page + self.assertEqual(response.status_code, status.HTTP_200_OK) + else: + #if anonymous user can't view a page + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_move_page_anonymous(self): + """ + #Ensure a Page can't be moved by a Anonymous user without permission + """ + url = reverse('page_move', args=(self.slug,)) + data = { + 'slug': 'self-slug' + } + + response = self.client.post(url, data) + + if 'change_page' in settings.WALIKI_ANONYMOUS_USER_PERMISSIONS: + #if anonymous user can view a page + self.assertEqual(response.status_code, status.HTTP_200_OK) + else: + #if anonymous user can't view a page + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_delete_page_anonymous(self): + """ + #Ensure a Page can't be deleted by a Anonymous user without permission + """ + url = reverse('page_delete', args=(self.slug,)) + data = { + 'what': 'this' + } + response = self.client.post(url, data) + + if 'change_page' in settings.WALIKI_ANONYMOUS_USER_PERMISSIONS: + #if anonymous user can view a page + self.assertEqual(response.status_code, status.HTTP_200_OK) + else: + #if anonymous user can't view a page + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/waliki/rest/__init__.py b/waliki/rest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/waliki/rest/permissions.py b/waliki/rest/permissions.py new file mode 100644 index 0000000..4a429c9 --- /dev/null +++ b/waliki/rest/permissions.py @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- +from django.contrib.auth.models import AnonymousUser + +from waliki import settings +from waliki.acl import check_perms + +from rest_framework.permissions import BasePermission + +class WalikiPermission(BasePermission): + """ + Base Permission Class for Waliki default and ACL rules + """ + permission = '' + + def has_permission(self, request, view, *args, **kwargs): + slug = request.resolver_match.kwargs.get('slug', ' ') + if check_perms((self.permission), request.user, slug): + return True + else: + if isinstance(request.user, AnonymousUser): + if self.permission in settings.WALIKI_ANONYMOUS_USER_PERMISSIONS: + return True + else: + if self.permission in settings.WALIKI_LOGGED_USER_PERMISSIONS: + return True + + +class WalikiPermission_AddPage(WalikiPermission): + permission = 'add_page' + +class WalikiPermission_ViewPage(WalikiPermission): + permission = 'view_page' + +class WalikiPermission_ChangePage(WalikiPermission): + permission = 'change_page' \ No newline at end of file diff --git a/waliki/rest/serializers.py b/waliki/rest/serializers.py new file mode 100644 index 0000000..1fa225a --- /dev/null +++ b/waliki/rest/serializers.py @@ -0,0 +1,113 @@ +from django.utils.translation import ugettext_lazy as _ + +from waliki import views +from waliki.models import Page +from waliki.signals import page_preedit +from waliki.views import edit + +import json +from rest_framework import serializers, request +from rest_framework.exceptions import PermissionDenied + + +class PageRetrieveSerializer(serializers.ModelSerializer): + """ + Serializer Class to retrieve a Page. + """ + class Meta(): + model = Page + fields = ('id', 'title', 'slug', 'raw', 'markup', ) + read_only_fields = fields + + +class PageListRetrieveSerializer(serializers.ModelSerializer): + """ + Serializer class to show a list of Pages + """ + + class Meta(): + model = Page + fields = ('id', 'title', 'slug', ) + read_only_fields = ('id', 'title', 'slug', ) + + +class PageCreateSerializer(serializers.ModelSerializer): + """ + Serializer Class to create a Page. + """ + + def save(self, *args, **kwargs): + """call to waliki new function""" + #call waliki new function + response = views.new(self.context['request']._request, *args, **kwargs) + + + class Meta(): + model = Page + fields = ('id', 'title', 'slug', 'markup' ) + read_only_fields = ('id', ) + + +class PageEditSerializer(serializers.HyperlinkedModelSerializer): + """ + Serializer Class to edit a Page. + """ + raw = serializers.CharField() + message = serializers.CharField(write_only=True) + extra_data = serializers.SerializerMethodField() + + + def get_extra_data(self, page): + form_extra_data = {} + receivers_responses = page_preedit.send(sender=edit, page=page) + for r in receivers_responses: + if isinstance(r[1], dict) and 'form_extra_data' in r[1]: + form_extra_data.update(r[1]['form_extra_data']) + return json.dumps(form_extra_data) + + + def save(self, *args, **kwargs): + """call to waliki edit function""" + #call waliki new function + #if 'extra_data' no comming in payload + if not self.context['request'].POST.get('extra_data', False): + mutable = self.context['request'].POST._mutable + self.context['request'].POST._mutable = True + self.context['request'].POST['extra_data'] = self.get_extra_data(self.instance) + self.context['request'].POST._mutable = mutable + + kwargs['slug'] = self.instance.slug + + response = views.edit(self.context['request'],*args, **kwargs) + + + class Meta(): + model = Page + fields = ('id', 'title', 'slug', 'raw', 'markup' ,'message', 'extra_data', ) + read_only_fields = ('id', 'slug', ) + + +class PageDeleteSerializer(serializers.ModelSerializer): + """ + Serializer Class to delete a Page or namespace. + """ + + what = serializers.ChoiceField( + choices=( + ('this', _('Just this page')), + ('namespace', _('This page and all the namespace'))) + ) + + class Meta(): + model = Page + fields = ('id', 'title', 'slug', 'what') + read_only_fields = fields + + +class PageMoveSerializer(serializers.ModelSerializer): + """ + Serializer Class to move a page. + """ + class Meta(): + model = Page + fields = ('slug', ) \ No newline at end of file diff --git a/waliki/rest/urls.py b/waliki/rest/urls.py new file mode 100644 index 0000000..5863dbc --- /dev/null +++ b/waliki/rest/urls.py @@ -0,0 +1,18 @@ +from django.conf.urls import patterns, url +from waliki.settings import WALIKI_SLUG_PATTERN, WALIKI_API_ROOT + +from .views import PageListView, PageCreateView, PageEditView, PageDeleteView, PageHistoryView, PageVersionView, PageDiffView, PageRetrieveView, PageMoveView + +urlpatterns = patterns('waliki.rest.views', + url(r'^' + WALIKI_API_ROOT + '/all$', PageListView.as_view() , name='page_list'), + url(r'^' + WALIKI_API_ROOT + '/new$', PageCreateView.as_view() , name='page_new'), + url(r'^' + WALIKI_API_ROOT + '/(?P' + WALIKI_SLUG_PATTERN + ')/edit$', PageEditView.as_view() , name='page_edit'), + url(r'^' + WALIKI_API_ROOT + '/(?P' + WALIKI_SLUG_PATTERN + ')/delete$', PageDeleteView.as_view() , name='page_delete'), + url(r'^' + WALIKI_API_ROOT + '/(?P' + WALIKI_SLUG_PATTERN + ')/move$', PageMoveView.as_view() , name='page_move'), + url(r'^' + WALIKI_API_ROOT + '/(?P' + WALIKI_SLUG_PATTERN + ')/history/(?P\d+)/$', PageHistoryView.as_view(), name='page_history'), + url(r'^' + WALIKI_API_ROOT + '/(?P' + WALIKI_SLUG_PATTERN + ')/history$', PageHistoryView.as_view(), name='page_history'), + url(r'^' + WALIKI_API_ROOT + '/(?P' + WALIKI_SLUG_PATTERN + ')/version/(?P[0-9a-f\^]{4,40})/$', PageVersionView.as_view(), name='page_version'), + url(r'^' + WALIKI_API_ROOT + '/(?P' + WALIKI_SLUG_PATTERN + ')/diff/(?P[0-9a-f\^]{4,40})\.\.(?P[0-9a-f\^]{4,40})$', PageDiffView.as_view(), name='page_diff'), + url(r'^' + WALIKI_API_ROOT + '/(?P' + WALIKI_SLUG_PATTERN + ')$', PageRetrieveView.as_view(), name='page_detail'), + +) \ No newline at end of file diff --git a/waliki/rest/views.py b/waliki/rest/views.py new file mode 100644 index 0000000..e0290f7 --- /dev/null +++ b/waliki/rest/views.py @@ -0,0 +1,211 @@ +from django.contrib import messages +from waliki import settings +from django.http import HttpResponse, HttpResponseRedirect + +from waliki.models import Page +from waliki import views +from waliki.git import Git +from waliki.git.views import version, diff, history as git_history + +from rest_framework import generics, permissions, mixins, status +from rest_framework.response import Response + +from .serializers import PageListRetrieveSerializer, PageCreateSerializer, PageEditSerializer, PageDeleteSerializer, PageRetrieveSerializer, PageMoveSerializer +from .permissions import WalikiPermission_AddPage, WalikiPermission_ViewPage, WalikiPermission_ChangePage + +class PageRetrieveView( + mixins.RetrieveModelMixin, + generics.GenericAPIView): + """ + A simple View to retrieve a Page. + """ + lookup_field = 'slug' + serializer_class = PageRetrieveSerializer + permission_classes = (WalikiPermission_ViewPage, ) + + def get_queryset(self): + return Page.objects.filter(slug=self.kwargs['slug']) + + def get(self, request, *args, **kwargs): + response = views.detail(request._request, raw=True, *args, **kwargs) + + if 302 ==response.status_code: + return HttpResponseRedirect(request.path.rstrip('/'+kwargs['slug'])+response.url) + + return self.retrieve(request, *args, **kwargs) + + + +class PageListView(generics.ListAPIView): + """ + A simple View to list all Pages. + """ + queryset = Page.objects.all() + serializer_class = PageListRetrieveSerializer + permission_classes = (WalikiPermission_ViewPage, ) + + +class PageCreateView(generics.CreateAPIView): + """ + A simple View to create a new Page. + """ + serializer_class = PageCreateSerializer + permission_classes = (WalikiPermission_AddPage, ) + + +class PageEditView( + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + generics.GenericAPIView): + """ + A View to edit a Page. + """ + lookup_field = 'slug' + serializer_class = PageEditSerializer + permission_classes = (WalikiPermission_ChangePage, ) + + def get_queryset(self): + return Page.objects.filter(slug=self.kwargs['slug']) + + def get(self, request, *args, **kwargs): + return self.retrieve(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + return self.update(request, *args, **kwargs) + + +class PageDeleteView(generics.GenericAPIView): + """ + A no much simple View to delete a Page or namespace. + """ + lookup_field = 'slug' + serializer_class = PageDeleteSerializer + permission_classes = (WalikiPermission_ChangePage, ) + + def get_queryset(self): + return Page.objects.filter(slug=self.kwargs['slug']) + + def post(self, request, *args, **kwargs): + #call waliki.view.delete + response = views.delete(request._request,*args, **kwargs) + + django_messages = [] + + for message in messages.get_messages(request): + django_messages.append({ + "level": message.level, + "message": message.message, + "extra_tags": message.tags, + }) + return Response(django_messages, status=status.HTTP_200_OK) + + +class PageHistoryView( + mixins.RetrieveModelMixin, + generics.GenericAPIView): + """ + A complex and c+p View to get history of one Page. + """ + lookup_field = 'slug' + serializer_class = PageListRetrieveSerializer + permission_classes = (WalikiPermission_ViewPage, ) + + def get_queryset(self): + return Page.objects.filter(slug=self.kwargs['slug'])[0] + + def get(self, request, *args, **kwargs): + pag = kwargs.get('pag', 1) + page = self.get_queryset() + + #same code of waliki.git.views.history + pag = int(pag or 1) + skip = (pag - 1) * settings.WALIKI_PAGINATE_BY + max_count = settings.WALIKI_PAGINATE_BY + + history = Git().history(page) + max_changes = max([(v['insertion'] + v['deletion']) for v in history]) + data = {'page': self.get_serializer(page, many=False).data, + 'history': history[skip:(skip+max_count)], + 'max_changes': max_changes, + 'prev': pag - 1 if pag > 1 else None, + 'next': pag + 1 if skip + max_count < len(history) else None} + return Response(data) + + +class PageVersionView( + mixins.RetrieveModelMixin, + generics.GenericAPIView): + """ + A View to retrieve a version of a Page. + """ + lookup_field = 'slug' + serializer_class = PageListRetrieveSerializer + permission_classes = (WalikiPermission_ViewPage, ) + + def get_queryset(self): + return Page.objects.filter(slug=self.kwargs['slug'])[0] + + def get(self, request, *args, **kwargs): + page = self.get_queryset() + + #call waliki.git version + response = version(request, raw=True, *args, **kwargs) + + data = self.get_serializer(page, many=False).data + data['raw'] = response.content.decode("utf8") + data['version'] = kwargs['version'] + + return Response(data) + +class PageDiffView( + mixins.RetrieveModelMixin, + generics.GenericAPIView): + """ + to view diff between versions. + """ + lookup_field = 'slug' + serializer_class = PageListRetrieveSerializer + permission_classes = (WalikiPermission_ViewPage, ) + + def get_queryset(self): + return Page.objects.filter(slug=self.kwargs['slug'])[0] + + def get(self, request, *args, **kwargs): + page = self.get_queryset() + + #call waliki.git diff + response = diff(request, raw=True, *args, **kwargs) + + data = self.get_serializer(page, many=False).data + data['new'] = kwargs['new'] + data['old'] = kwargs['old'] + data['raw'] = response.content.decode("utf8") + + return Response(data) + + + +class PageMoveView(generics.GenericAPIView): + """ + View to move a Page. + """ + lookup_field = 'slug' + serializer_class = PageMoveSerializer + permission_classes = (WalikiPermission_ChangePage, ) + + def get_queryset(self): + return Page.objects.filter(slug=self.kwargs['slug']) + + def post(self, request, *args, **kwargs): + #call waliki.view.delete + response = views.move(request._request,*args, **kwargs) + + django_messages = [] + + for message in messages.get_messages(request): + django_messages.append({ + "level": message.level, + "message": message.message, + "extra_tags": message.tags, + }) + return Response(django_messages, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/waliki/rest/waliki_plugin.py b/waliki/rest/waliki_plugin.py new file mode 100644 index 0000000..7e43732 --- /dev/null +++ b/waliki/rest/waliki_plugin.py @@ -0,0 +1,9 @@ +from django.utils.translation import ugettext_lazy as _ + +from waliki.plugins import BasePlugin, register + +class RestPlugin(BasePlugin): + slug = 'rest' + urls_page = ['waliki.rest.urls'] + +register(RestPlugin) \ No newline at end of file diff --git a/waliki/settings.py b/waliki/settings.py index 9d64f12..a88bcac 100644 --- a/waliki/settings.py +++ b/waliki/settings.py @@ -117,6 +117,8 @@ def _get_markup_settings(user_settings): WALIKI_USE_MATHJAX = getattr(settings, 'WALIKI_USE_MATHJAX', False) +WALIKI_API_ROOT = getattr(settings, 'WALIKI_API_ROOT', 'API') + def WALIKI_UPLOAD_TO(instance, filename): return os.path.join(WALIKI_ATTACHMENTS_DIR, diff --git a/waliki_project/waliki_project/settings.py b/waliki_project/waliki_project/settings.py index 9b18715..b0b0145 100644 --- a/waliki_project/waliki_project/settings.py +++ b/waliki_project/waliki_project/settings.py @@ -63,12 +63,14 @@ 'waliki.slides', 'waliki.attachments', 'waliki.togetherjs', + 'waliki.rest', 'analytical', 'allauth', 'allauth.account', 'allauth.socialaccount', 'allauth.socialaccount.providers.github', 'allauth.socialaccount.providers.twitter', + 'rest_framework', ) MIDDLEWARE_CLASSES = ( From 9aa636f90ca3a856178cc2e6f9c0cdee5c02429e Mon Sep 17 00:00:00 2001 From: rizotas Date: Fri, 24 Jul 2015 15:36:53 -0500 Subject: [PATCH 4/5] add docs --- README.rst | 2 ++ docs/rest.rst | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 docs/rest.rst diff --git a/README.rst b/README.rst index 8687a17..a5cf958 100644 --- a/README.rst +++ b/README.rst @@ -40,6 +40,7 @@ At a glance, Waliki has these features: * A very simple *per slug* `ACL system `_ * A nice `attachments manager `_ (that respects the permissions over the page) * Realtime `collaborative edition `_ via togetherJS +* REST `API `_ * Wiki content embeddable in any django template (as a "`dummy CMS `_") * Few helpers to migrate content (particularly from MoinMoin, using moin2git_) * It `works `_ with Python 2.7, 3.3, 3.4 or PyPy in Django 1.6 or newer @@ -74,6 +75,7 @@ Add ``waliki`` and the optionals plugins to your INSTALLED_APPS:: 'waliki.pdf', # optional 'waliki.slides', # optional 'waliki.togetherjs', # optional + 'waliki.rest', # optional ... ) diff --git a/docs/rest.rst b/docs/rest.rst new file mode 100644 index 0000000..528f4d8 --- /dev/null +++ b/docs/rest.rst @@ -0,0 +1,64 @@ +.. _rest: + +========= +REST API +========= +The ``waliki.rest`` plugin together ``waliki.git`` provides a set of REST API endpoints. + +With this plugin you'll get: + +URLs +---- + +| List all Pages +| ``GET http://yoursite.com[/]//all`` +| +| Add Page +| ``POST http://yoursite.com[/]//new`` +| +| Retrieve Page +| ``GET http://yoursite.com[/]//`` +| +| Edit Page +| ``POST http://yoursite.com[/]///edit`` +| +| Move Page +| ``POST http://yoursite.com[/]///move`` +| +| Delete Page +| ``POST http://yoursite.com[/]///delete`` +| +| History of changes +| ``GET http://yoursite.com[/]///history`` +| +| Retrieve a version +| ``GET http://yoursite.com[/]///version//`` +| +| Diff +| ``GET http://yoursite.com[/]///diff/..`` + +Setup +------- + +It requires `djangorestframework`_ as requirement. Install it via pip:: + + $ pip install djangorestframework + +To install it, add ``'waliki.rest'`` and ``'rest_framework'`` after ``'waliki.git'`` in your ``settings.INSTALLED_APPS``:: + + INSTALLED_APPS = ( + ... + 'waliki', + ... + 'waliki.git', + 'waliki.rest', + ... + 'rest_framework', + ... + ) + +| Default url for restful service: + +``WALIKI_API_ROOT = 'API'`` + +.. _djangorestframework: https://github.com/tomchristie/django-rest-framework From 400af26f87aec46102dfc5b2a00fae887ebdd79b Mon Sep 17 00:00:00 2001 From: rizotas Date: Wed, 9 Sep 2015 22:49:40 -0500 Subject: [PATCH 5/5] fix git-show parser --- waliki/git/__init__.py | 14 +++++--------- waliki/git/views.py | 2 +- waliki/rest/views.py | 4 +++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/waliki/git/__init__.py b/waliki/git/__init__.py index 3695e2b..83c81cc 100644 --- a/waliki/git/__init__.py +++ b/waliki/git/__init__.py @@ -1,7 +1,6 @@ import os import re import json -from io import StringIO from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ from django.utils import six @@ -101,14 +100,12 @@ def version(self, page, version): try: out = str(git.show('-s', "--pretty=format:%aN%n%aD%n%B%n%h%e", version)) - buf = StringIO() - buf.write(out) - buf.seek(0) - author = str(buf.readline()) - date = str(buf.readline()) - message = str(buf.readline()) - buf.close() + lines = out.splitlines() + author = lines[0] + date = lines[1] + message = lines[2] + raw = str(git.show('%s:%s' % (version, page.path))) data = { @@ -117,7 +114,6 @@ def version(self, page, version): "message": message, "raw": raw, } - return data except: return '' diff --git a/waliki/git/views.py b/waliki/git/views.py index fadf719..87ae643 100644 --- a/waliki/git/views.py +++ b/waliki/git/views.py @@ -46,7 +46,7 @@ def version(request, slug, version, raw=False): page = get_object_or_404(Page, slug=slug) content = Git().version(page, version) - form = PageForm(instance=page, initial={'message': _('Restored version @%s') % version, 'raw': content}, + form = PageForm(instance=page, initial={'message': _('Restored version @%s') % version, 'raw': content['raw']}, is_hidden=True) if raw: diff --git a/waliki/rest/views.py b/waliki/rest/views.py index e0290f7..4cfd962 100644 --- a/waliki/rest/views.py +++ b/waliki/rest/views.py @@ -10,6 +10,8 @@ from rest_framework import generics, permissions, mixins, status from rest_framework.response import Response +import json + from .serializers import PageListRetrieveSerializer, PageCreateSerializer, PageEditSerializer, PageDeleteSerializer, PageRetrieveSerializer, PageMoveSerializer from .permissions import WalikiPermission_AddPage, WalikiPermission_ViewPage, WalikiPermission_ChangePage @@ -152,7 +154,7 @@ def get(self, request, *args, **kwargs): response = version(request, raw=True, *args, **kwargs) data = self.get_serializer(page, many=False).data - data['raw'] = response.content.decode("utf8") + data['raw'] = json.loads(response.content) data['version'] = kwargs['version'] return Response(data)