Skip to content

Commit

Permalink
Merge 400af26 into 165d866
Browse files Browse the repository at this point in the history
  • Loading branch information
Aslhey committed Sep 10, 2015
2 parents 165d866 + 400af26 commit 9ebc50c
Show file tree
Hide file tree
Showing 16 changed files with 610 additions and 13 deletions.
2 changes: 2 additions & 0 deletions README.rst
Expand Up @@ -40,6 +40,7 @@ At a glance, Waliki has these features:
* A very simple *per slug* `ACL system <http://waliki.readthedocs.org/en/latest/acl.html>`_
* A nice `attachments manager <http://waliki.readthedocs.org/en/latest/attachments.html>`_ (that respects the permissions over the page)
* Realtime `collaborative edition <http://waliki.readthedocs.org/en/latest/togetherjs.html>`_ via togetherJS
* REST `API <http://waliki.readthedocs.org/en/latest/rest.html>`_
* Wiki content embeddable in any django template (as a "`dummy CMS <http://waliki.readthedocs.org/en/latest/boxes.html>`_")
* Few helpers to migrate content (particularly from MoinMoin, using moin2git_)
* It `works <https://travis-ci.org/mgaitan/waliki>`_ with Python 2.7, 3.3, 3.4 or PyPy in Django 1.6 or newer
Expand Down Expand Up @@ -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
...
)

Expand Down
64 changes: 64 additions & 0 deletions 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[/<waliki_prefix>]/<WALIKI_API_ROOT>/all``
|
| Add Page
| ``POST http://yoursite.com[/<waliki_prefix>]/<WALIKI_API_ROOT>/new``
|
| Retrieve Page
| ``GET http://yoursite.com[/<waliki_prefix>]/<WALIKI_API_ROOT>/<slug>``
|
| Edit Page
| ``POST http://yoursite.com[/<waliki_prefix>]/<WALIKI_API_ROOT>/<slug>/edit``
|
| Move Page
| ``POST http://yoursite.com[/<waliki_prefix>]/<WALIKI_API_ROOT>/<slug>/move``
|
| Delete Page
| ``POST http://yoursite.com[/<waliki_prefix>]/<WALIKI_API_ROOT>/<slug>/delete``
|
| History of changes
| ``GET http://yoursite.com[/<waliki_prefix>]/<WALIKI_API_ROOT>/<slug>/history``
|
| Retrieve a version
| ``GET http://yoursite.com[/<waliki_prefix>]/<WALIKI_API_ROOT>/<slug>/version/<version>/``
|
| Diff
| ``GET http://yoursite.com[/<waliki_prefix>]/<WALIKI_API_ROOT>/<slug>/diff/<new_version>..<old_version>``
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
1 change: 1 addition & 0 deletions runtests.py
Expand Up @@ -26,6 +26,7 @@
"waliki.git",
"waliki.attachments",
"waliki.slides",
"waliki.rest",
),
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -30,7 +30,8 @@
'markdown': ['markdown'],
'attachments': ['django-sendfile'],
'pdf': ['rst2pdf'],
'slides': ['hovercraft']
'slides': ['hovercraft'],
'rest': ['djangorestframework']
}

everything = set()
Expand Down
8 changes: 4 additions & 4 deletions tests/test_git.py
Expand Up @@ -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):
Expand All @@ -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()
Expand All @@ -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"
Expand Down Expand Up @@ -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'
Expand Down
119 changes: 119 additions & 0 deletions 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)
18 changes: 17 additions & 1 deletion waliki/git/__init__.py
Expand Up @@ -98,7 +98,23 @@ 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))

lines = out.splitlines()

author = lines[0]
date = lines[1]
message = lines[2]

raw = str(git.show('%s:%s' % (version, page.path)))

data = {
"author": author,
"date": date,
"message": message,
"raw": raw,
}
return data
except:
return ''

Expand Down
16 changes: 9 additions & 7 deletions waliki/git/views.py
Expand Up @@ -45,15 +45,17 @@ 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
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:
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)
if content["raw"]:
content = Page.preview(page.markup, content["raw"])
else:
content = ''
return render(request, 'waliki/version.html', {'page': page,
'content': content,
'slug': slug,
Expand All @@ -69,8 +71,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,
Expand Down
Empty file added waliki/rest/__init__.py
Empty file.
35 changes: 35 additions & 0 deletions 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'

0 comments on commit 9ebc50c

Please sign in to comment.