def api_call(user, path, data=None):
- # what if not verified?
conn = OAuthConnection.get(user)
if not conn.access:
raise NotAuthorizedError("No WL authorization for user %s." % user)
token = oauth2.Token(conn.token, conn.token_secret)
client = oauth2.Client(wl_consumer, token)
if data is not None:
+ data = simplejson.dumps(data)
+ data = urllib.urlencode({"data": data})
resp, content = client.request(
- "%s%s.json" % (WL_API_URL, path),
+ "%s%s" % (WL_API_URL, path),
method="POST",
- body=urllib.urlencode(data))
+ body=data)
else:
resp, content = client.request(
- "%s%s.json" % (WL_API_URL, path))
+ "%s%s" % (WL_API_URL, path))
status = resp['status']
+
if status == '200':
return simplejson.loads(content)
elif status.startswith('2'):
return
+ elif status == '401':
+ raise ApiError('User not authorized for publishing.')
else:
raise ApiError("WL API call error")
from django.conf.urls.defaults import *
urlpatterns = patterns('apiclient.views',
- url(r'^oauth/$', 'oauth', name='users_oauth'),
- url(r'^oauth_callback/$', 'oauth_callback', name='users_oauth_callback'),
+ url(r'^oauth/$', 'oauth', name='apiclient_oauth'),
+ url(r'^oauth_callback/$', 'oauth_callback', name='apiclient_oauth_callback'),
)
url = "%s?oauth_token=%s&oauth_callback=%s" % (
WL_AUTHORIZE_URL,
request_token['oauth_token'],
- request.build_absolute_uri(reverse("users_oauth_callback")),
+ request.build_absolute_uri(reverse("apiclient_oauth_callback")),
)
return HttpResponseRedirect(url)
# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
+from django.contrib.sites.models import Site
from django.db import models
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _
from slughifi import slughifi
+from librarian import NoDublinCore, ParseError, ValidationError
+from librarian.dcparser import BookInfo
+
+import apiclient
from catalogue.helpers import cached_in_field
from catalogue.models import BookPublishRecord, ChunkPublishRecord
from catalogue.signals import post_publish
chunk.title = title[:255]
chunk.save()
else:
- chunk = instance.add(slug, title, adjust_slug=True)
+ chunk = instance.add(slug, title)
chunk.commit(text, **commit_args)
except IndexError:
return None
- def publishable(self):
- if not self.chunk_set.exists():
- return False
- for chunk in self:
- if not chunk.publishable():
- return False
- return True
+ def assert_publishable(self):
+ assert self.chunk_set.exists(), _('No chunks in the book.')
+ try:
+ changes = self.get_current_changes(publishable=True)
+ except self.NoTextError:
+ raise AssertionError(_('Not all chunks have publishable revisions.'))
+ book_xml = self.materialize(changes=changes)
+
+ try:
+ bi = BookInfo.from_string(book_xml.encode('utf-8'))
+ except ParseError, e:
+ raise AssertionError(_('Invalid XML') + ': ' + str(e))
+ except NoDublinCore:
+ raise AssertionError(_('No Dublin Core found.'))
+ except ValidationError, e:
+ raise AssertionError(_('Invalid Dublin Core') + ': ' + str(e))
+
+ valid_about = "http://%s%s" % (Site.objects.get_current().domain, self.get_absolute_url())
+ assert bi.about == valid_about, _("rdf:about is not") + " " + valid_about
def hidden(self):
return self.slug.startswith('.')
"""
Publishes a book on behalf of a (local) user.
"""
- raise NotImplementedError("Publishing not possible yet.")
-
- from apiclient import api_call
-
+ self.assert_publishable()
changes = self.get_current_changes(publishable=True)
book_xml = self.materialize(changes=changes)
- #api_call(user, "books", {"book_xml": book_xml})
+ apiclient.api_call(user, "books/", {"book_xml": book_xml})
# record the publish
br = BookPublishRecord.objects.create(book=self, user=user)
for c in changes:
# Creating and manipulation
# =========================
- def split(self, slug, title='', adjust_slug=False, **kwargs):
+ def split(self, slug, title='', **kwargs):
""" Create an empty chunk after this one """
self.book.chunk_set.filter(number__gt=self.number).update(
number=models.F('number')+1)
<p>{% trans "Last published" %}: {{ book.last_published }}</p>
-{% if book.publishable %}
+{% if publishable %}
<p>
<a href="{% url catalogue_book_xml book.slug %}">{% trans "Full XML" %}</a><br/>
<a target="_blank" href="{% url catalogue_book_html book.slug %}">{% trans "HTML version" %}</a><br/>
{% endcomment %}
</p>
- {% trans "This book cannot be published yet" %}
- {% comment %}
- <!--
- Angel photos:
- Angels in Ely Cathedral (http://www.flickr.com/photos/21804434@N02/4483220595/) /
- mira66 (http://www.flickr.com/photos/21804434@N02/) /
- CC BY 2.0 (http://creativecommons.org/licenses/by/2.0/)
- -->
- <form method="POST" action="{% url catalogue_publish book.slug %}">{% csrf_token %}
- <img src="{{ STATIC_URL }}img/angel-left.png" style="vertical-align: middle" />
- <button id="publish-button" type="submit">
- <span>{% trans "Publish" %}</span></button>
- <img src="{{ STATIC_URL }}img/angel-right.png" style="vertical-align: middle" />
- </form>
- {% endcomment %}
+ {% if user.is_authenticated %}
+ <!--
+ Angel photos:
+ Angels in Ely Cathedral (http://www.flickr.com/photos/21804434@N02/4483220595/) /
+ mira66 (http://www.flickr.com/photos/21804434@N02/) /
+ CC BY 2.0 (http://creativecommons.org/licenses/by/2.0/)
+ -->
+ <form method="POST" action="{% url catalogue_publish book.slug %}">{% csrf_token %}
+ <img src="{{ STATIC_URL }}img/angel-left.png" style="vertical-align: middle" />
+ <button id="publish-button" type="submit">
+ <span>{% trans "Publish" %}</span></button>
+ <img src="{{ STATIC_URL }}img/angel-right.png" style="vertical-align: middle" />
+ </form>
+ {% else %}
+ <a href="{% url login %}">{% trans "Log in to publish." %}</a>
+ {% endif %}
{% else %}
- {% trans "This book cannot be published yet" %}
+ <p>{% trans "This book can't be published yet, because:" %}</p>
+ <ul><li>{{ publishable_error }}</li></ul>
{% endif %}
{% endblock leftcolumn %}
--- /dev/null
+from nose.tools import *
+from mock import patch
+from django.test import TestCase
+from django.contrib.auth.models import User
+from catalogue.models import Book, BookPublishRecord
+
+class PublishTests(TestCase):
+
+ def setUp(self):
+ self.user = User.objects.create(username='tester')
+ self.book = Book.create(self.user, 'publish me')
+
+ @patch('apiclient.api_call')
+ def test_unpublishable(self, api_call):
+ with self.assertRaises(Book.NoTextError):
+ self.book.publish(self.user)
+
+ @patch('apiclient.api_call')
+ def test_publish(self, api_call):
+ self.book[0].head.set_publishable(True)
+ self.book.publish(self.user)
+ api_call.assert_called_with(self.user, 'books', {"book_xml": 'publish me'})
+
+ @patch('apiclient.api_call')
+ def test_publish_multiple(self, api_call):
+ self.book[0].head.set_publishable(True)
+ self.book[0].split(slug='part-2')
+ self.book[1].commit('take me \n<!-- TRIM_BEGIN -->\n too')
+ self.book[1].head.set_publishable(True)
+ self.book.publish(self.user)
+ api_call.assert_called_with(self.user, 'books', {"book_xml": 'publish me\n too'})
import librarian.html
import librarian.text
+from apiclient import NotAuthorizedError
from catalogue import forms
from catalogue import helpers
from catalogue.helpers import active_tab
else:
form = None
+ try:
+ book.assert_publishable()
+ except AssertionError, e:
+ publishable = False
+ publishable_error = e
+ else:
+ publishable = True
+ publishable_error = None
+
return direct_to_template(request, "catalogue/book_detail.html", extra_context={
"book": book,
+ "publishable": publishable,
+ "publishable_error": publishable_error,
"chunks": chunks,
"need_fixing": need_fixing,
"choose_master": choose_master,
book = get_object_or_404(Book, slug=slug)
try:
book.publish(request.user)
+ except NotAuthorizedError:
+ return http.HttpResponseRedirect(reverse('apiclient_oauth'))
except BaseException, e:
return http.HttpResponse(e)
else:
from redakcja.settings import *
# Path to repository with managed documents
-WIKI_REPOSITORY_PATH = '/srv/redakcja/books'
+CATALOGUE_REPO_PATH = '/srv/redakcja/books'
LOGGING_CONFIG_FILE = "/srv/redakcja/logging.cfg.dev"
CAS_SERVER_URL = 'http://logowanie.wolnelektury.pl/cas/'
REDMINE_URL = 'http://redmine.nowoczesnapolska.org.pl/'
DEBUG = True
-COMPRESS = False
\ No newline at end of file
+COMPRESS = False
+
+APICLIENT_WL_CONSUMER_KEY = None
+APICLIENT_WL_CONSUMER_SECRET = None
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
TEST_MODULES = ('catalogue', 'dvcs.tests', 'wiki', 'toolbar')
-#COVER_APPS = ('catalogue', 'dvcs', 'wiki', 'toolbar')
+COVER_APPS = ('catalogue', 'dvcs', 'wiki', 'toolbar')
NOSE_ARGS = (
'--tests=' + ','.join(TEST_MODULES),
- '--cover-package=' + ','.join(TEST_MODULES),
+ '--cover-package=' + ','.join(COVER_APPS),
'-d',
'--with-doctest',
'--with-xunit',
django-nose==0.1.3
nose
nosexcover
+mock