From 1b718c10066557540770bb0960a773dce0ad4462 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Tue, 11 Oct 2011 12:29:05 +0200 Subject: [PATCH] teh publish button again --- apps/apiclient/__init__.py | 12 ++++-- apps/apiclient/urls.py | 4 +- apps/apiclient/views.py | 2 +- apps/catalogue/models/book.py | 40 +++++++++++++------ apps/catalogue/models/chunk.py | 2 +- .../templates/catalogue/book_detail.html | 36 +++++++++-------- apps/catalogue/tests.py | 31 ++++++++++++++ apps/catalogue/views.py | 14 +++++++ redakcja/localsettings.sample | 7 +++- redakcja/settings/test.py | 4 +- requirements-test.txt | 1 + 11 files changed, 111 insertions(+), 42 deletions(-) create mode 100755 apps/catalogue/tests.py diff --git a/apps/apiclient/__init__.py b/apps/apiclient/__init__.py index 7913ac3c..d44e016d 100644 --- a/apps/apiclient/__init__.py +++ b/apps/apiclient/__init__.py @@ -22,25 +22,29 @@ class NotAuthorizedError(BaseException): 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") diff --git a/apps/apiclient/urls.py b/apps/apiclient/urls.py index 5e54965e..87d9997d 100755 --- a/apps/apiclient/urls.py +++ b/apps/apiclient/urls.py @@ -1,6 +1,6 @@ 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'), ) diff --git a/apps/apiclient/views.py b/apps/apiclient/views.py index f8515904..d4960148 100644 --- a/apps/apiclient/views.py +++ b/apps/apiclient/views.py @@ -33,7 +33,7 @@ def oauth(request): 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) diff --git a/apps/catalogue/models/book.py b/apps/catalogue/models/book.py index fb593889..f4e025ed 100755 --- a/apps/catalogue/models/book.py +++ b/apps/catalogue/models/book.py @@ -3,10 +3,15 @@ # 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 @@ -113,7 +118,7 @@ class Book(models.Model): 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) @@ -186,13 +191,25 @@ class Book(models.Model): 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('.') @@ -277,13 +294,10 @@ class Book(models.Model): """ 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: diff --git a/apps/catalogue/models/chunk.py b/apps/catalogue/models/chunk.py index d373e044..8b8f56b3 100755 --- a/apps/catalogue/models/chunk.py +++ b/apps/catalogue/models/chunk.py @@ -63,7 +63,7 @@ class Chunk(dvcs_models.Document): # 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) diff --git a/apps/catalogue/templates/catalogue/book_detail.html b/apps/catalogue/templates/catalogue/book_detail.html index 8e9c1f10..19e673c5 100755 --- a/apps/catalogue/templates/catalogue/book_detail.html +++ b/apps/catalogue/templates/catalogue/book_detail.html @@ -68,7 +68,7 @@

{% trans "Last published" %}: {{ book.last_published }}

-{% if book.publishable %} +{% if publishable %}

{% trans "Full XML" %}
{% trans "HTML version" %}
@@ -79,23 +79,25 @@ {% endcomment %}

- {% trans "This book cannot be published yet" %} - {% comment %} - -
{% csrf_token %} - - - -
- {% endcomment %} + {% if user.is_authenticated %} + +
{% csrf_token %} + + + +
+ {% else %} + {% trans "Log in to publish." %} + {% endif %} {% else %} - {% trans "This book cannot be published yet" %} +

{% trans "This book can't be published yet, because:" %}

+ {% endif %} {% endblock leftcolumn %} diff --git a/apps/catalogue/tests.py b/apps/catalogue/tests.py new file mode 100755 index 00000000..b73ea353 --- /dev/null +++ b/apps/catalogue/tests.py @@ -0,0 +1,31 @@ +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\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'}) diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index ef042de2..487307d5 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -19,6 +19,7 @@ from django.views.generic.simple import direct_to_template import librarian.html import librarian.text +from apiclient import NotAuthorizedError from catalogue import forms from catalogue import helpers from catalogue.helpers import active_tab @@ -294,8 +295,19 @@ def book(request, slug): 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, @@ -390,6 +402,8 @@ def publish(request, slug): 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: diff --git a/redakcja/localsettings.sample b/redakcja/localsettings.sample index c1b1a19b..140c52b8 100644 --- a/redakcja/localsettings.sample +++ b/redakcja/localsettings.sample @@ -12,7 +12,7 @@ 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" @@ -25,4 +25,7 @@ IMAGE_DIR = 'images' 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 diff --git a/redakcja/settings/test.py b/redakcja/settings/test.py index 1997c967..47622577 100644 --- a/redakcja/settings/test.py +++ b/redakcja/settings/test.py @@ -25,10 +25,10 @@ INSTALLED_APPS += ('django_nose', 'dvcs.tests') 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', diff --git a/requirements-test.txt b/requirements-test.txt index 2ec68c0d..3a0f1648 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,3 +1,4 @@ django-nose==0.1.3 nose nosexcover +mock -- 2.20.1