"publish" button
authorJan Szejko <jan.szejko@gmail.com>
Thu, 16 Jun 2016 14:33:42 +0000 (16:33 +0200)
committerJan Szejko <jan.szejko@gmail.com>
Thu, 16 Jun 2016 14:33:42 +0000 (16:33 +0200)
apps/apiclient/__init__.py
apps/apiclient/settings.py
apps/apiclient/views.py
apps/catalogue/models/book.py
apps/catalogue/templates/catalogue/book_list/book.html
apps/catalogue/templates/catalogue/book_list/book_list.html
apps/catalogue/views.py
apps/wiki/static/wiki/editor
lib/librarian
redakcja/xslt/wl2to1.xslt [new file with mode: 0644]
requirements.txt

index 376b66e..485bf60 100644 (file)
@@ -1,10 +1,12 @@
+# -*- coding: utf-8 -*-
+import json
 import urllib
 
-from django.utils import simplejson
 import oauth2
 
 from apiclient.models import OAuthConnection
 from apiclient.settings import WL_CONSUMER_KEY, WL_CONSUMER_SECRET, WL_API_URL
+from django.conf import settings
 
 
 if WL_CONSUMER_KEY and WL_CONSUMER_SECRET:
@@ -28,23 +30,23 @@ def api_call(user, path, data=None):
     token = oauth2.Token(conn.token, conn.token_secret)
     client = oauth2.Client(wl_consumer, token)
     if data is not None:
-        data = simplejson.dumps(data)
+        data = json.dumps(data)
         data = urllib.urlencode({"data": data})
         resp, content = client.request(
                 "%s%s" % (WL_API_URL, path),
                 method="POST",
                 body=data)
     else:
-        resp, content = client.request(
-                "%s%s" % (WL_API_URL, path))
+        resp, content = client.request("%s%s" % (WL_API_URL, path))
     status = resp['status']
 
     if status == '200':
-        return simplejson.loads(content)
+        return json.loads(content)
     elif status.startswith('2'):
         return
+    elif settings.DEBUG:
+        raise ApiError(content)
     elif status == '401':
         raise ApiError('User not authorized for publishing.')
     else:
         raise ApiError("WL API call error [code %s]" % status)
-
index f1eb34a..fc0a63c 100755 (executable)
@@ -1,15 +1,12 @@
+# -*- coding: utf-8 -*-
 from django.conf import settings
 
 
 WL_CONSUMER_KEY = getattr(settings, 'APICLIENT_WL_CONSUMER_KEY', None)
 WL_CONSUMER_SECRET = getattr(settings, 'APICLIENT_WL_CONSUMER_SECRET', None)
 
-WL_API_URL = getattr(settings, 'APICLIENT_WL_API_URL', 
-        'http://wolnelektury.pl/api/')
+WL_API_URL = getattr(settings, 'APICLIENT_WL_API_URL', 'https://edukacjamedialna.edu.pl/api/')
 
-WL_REQUEST_TOKEN_URL = getattr(settings, 'APICLIENT_WL_REQUEST_TOKEN_URL', 
-        WL_API_URL + 'oauth/request_token/')
-WL_ACCESS_TOKEN_URL = getattr(settings, 'APICLIENT_WL_ACCESS_TOKEN_URL', 
-        WL_API_URL + 'oauth/access_token/')
-WL_AUTHORIZE_URL = getattr(settings, 'APICLIENT_WL_AUTHORIZE_URL', 
-        WL_API_URL + 'oauth/authorize/')
+WL_REQUEST_TOKEN_URL = getattr(settings, 'APICLIENT_WL_REQUEST_TOKEN_URL', WL_API_URL + 'oauth/request_token/')
+WL_ACCESS_TOKEN_URL = getattr(settings, 'APICLIENT_WL_ACCESS_TOKEN_URL', WL_API_URL + 'oauth/access_token/')
+WL_AUTHORIZE_URL = getattr(settings, 'APICLIENT_WL_AUTHORIZE_URL', WL_API_URL + 'oauth/authorize/')
index d496014..88f4b49 100644 (file)
@@ -1,4 +1,5 @@
-import cgi
+# -*- coding: utf-8 -*-
+import urlparse
 
 from django.contrib.auth.decorators import login_required
 from django.core.urlresolvers import reverse
@@ -7,8 +8,7 @@ import oauth2
 
 from apiclient.models import OAuthConnection
 from apiclient import wl_consumer
-from apiclient.settings import (WL_REQUEST_TOKEN_URL, WL_ACCESS_TOKEN_URL, 
-        WL_AUTHORIZE_URL)
+from apiclient.settings import (WL_REQUEST_TOKEN_URL, WL_ACCESS_TOKEN_URL, WL_AUTHORIZE_URL)
 
 
 @login_required
@@ -21,7 +21,7 @@ def oauth(request):
     if resp['status'] != '200':
         raise Exception("Invalid response %s." % resp['status'])
 
-    request_token = dict(cgi.parse_qsl(content))
+    request_token = dict(urlparse.parse_qsl(content))
     
     conn = OAuthConnection.get(request.user)
     # this might reset existing auth!
@@ -50,11 +50,14 @@ def oauth_callback(request):
     token.set_verifier(oauth_verifier)
     client = oauth2.Client(wl_consumer, token)
     resp, content = client.request(WL_ACCESS_TOKEN_URL, method="POST")
-    access_token = dict(cgi.parse_qsl(content))
-
-    conn.access = True
-    conn.token = access_token['oauth_token']
-    conn.token_secret = access_token['oauth_token_secret']
-    conn.save()
+    access_token = dict(urlparse.parse_qsl(content))
+
+    if 'oauth_token' in access_token:
+        conn.access = True
+        conn.token = access_token['oauth_token']
+        conn.token_secret = access_token['oauth_token_secret']
+        conn.save()
+    else:
+        return HttpResponse(request.GET.get('error'))
 
     return HttpResponseRedirect('/')
index f40a368..b5a3bf3 100755 (executable)
@@ -246,7 +246,7 @@ class Book(models.Model):
     def assert_publishable(self):
         assert self.chunk_set.exists(), _('No chunks in the book.')
         try:
-            changes = self.get_current_changes(publishable=True)
+            changes = self.get_current_changes()
         except self.NoTextError:
             raise AssertionError(_('Not all chunks have publishable revisions.'))
 
@@ -254,6 +254,10 @@ class Book(models.Model):
 
         try:
             bi = self.wldocument(changes=changes, strict=True).book_info
+            if not bi.audience:
+                raise ValidationError('No audience specified')
+            if not bi.type:
+                raise ValidationError('No type specified')
         except ParseError, e:
             raise AssertionError(_('Invalid XML') + ': ' + unicode(e))
         except NoDublinCore:
@@ -399,7 +403,7 @@ class Book(models.Model):
         from librarian.parser import WLDocument
 
         return WLDocument.from_string(
-                self.materialize(publishable=publishable, changes=changes),
+                self.wl1_xml(publishable=publishable, changes=changes),
                 provider=RedakcjaDocProvider(publishable=publishable),
                 parse_dublincore=parse_dublincore,
                 strict=strict)
@@ -409,11 +413,72 @@ class Book(models.Model):
             Publishes a book on behalf of a (local) user.
         """
         self.assert_publishable()
-        changes = self.get_current_changes(publishable=True)
-        book_xml = self.materialize(changes=changes)
-        apiclient.api_call(user, "books/", {"book_xml": book_xml})
+        changes = self.get_current_changes()
+        book_xml = self.wl1_xml(changes=changes)
+        apiclient.api_call(user, "lessons/", {"lesson_xml": book_xml})
         # record the publish
         br = BookPublishRecord.objects.create(book=self, user=user)
         for c in changes:
             ChunkPublishRecord.objects.create(book_record=br, change=c)
         post_publish.send(sender=br)
+
+    def wl1_xml(self, publishable=True, changes=None):
+        from lxml import etree
+        import re
+        from StringIO import StringIO
+        from urllib import unquote
+        import os.path
+        from django.conf import settings
+        from fnpdjango.utils.text.slughifi import slughifi
+
+        def _register_function(f):
+            """ Register extension function with lxml """
+            ns = etree.FunctionNamespace('http://wolnelektury.pl/functions')
+            ns[f.__name__] = f
+            return f
+
+        @_register_function
+        def slugify(context, text):
+            """Remove unneeded whitespace from beginning and end"""
+            if isinstance(text, list):
+                text = ''.join(text)
+            return slughifi(text)
+
+        @_register_function
+        def rmext(context, text):
+            if isinstance(text, list):
+                text = ''.join(text)
+            text = unquote(text)
+            if '.' in text:
+                name, ext = text.rsplit('.', 1)
+                if ext.lower() in ('doc', 'docx', 'odt', 'pdf', 'jpg', 'jpeg'):
+                    text = name
+            return text
+
+        t = etree.parse(os.path.join(settings.PROJECT_ROOT, 'xslt/wl2to1.xslt'))
+        ft = self.materialize(publishable=publishable, changes=changes)
+        ft = ft.replace('&nbsp;', ' ')
+        f2 = StringIO(ft)
+        i1 = etree.parse(f2)
+
+        for sect in i1.findall('//section'):
+            if sect[0].text == u'Przebieg zajęć':
+                # Prostujemy.
+                first = sect.find('section')
+                subs = first.findall('.//section')
+                for sub in subs:
+                    sect.append(sub)
+                break
+        else:
+            # print 'BRAK PRZEBIEGU'
+            raise ValueError('Brak przebiegu')
+
+        i1.getroot().attrib['redslug'] = self.slug
+        i1.getroot().attrib['wlslug'] = self.slug  # THIS!
+        # print '.',
+        w1t = i1.xslt(t)
+        for h in w1t.findall('//aktywnosc/opis'):
+            if not re.match(r'\d\.\s', h[0].text):
+                raise AssertionError('Niepoprawny nagłówek (aktywnosc/opis): %s' % repr(h[0].text))
+            h[0].text = h[0].text[3:]
+        return etree.tostring(w1t, encoding='utf-8')
index 3ad8a65..f18efc1 100755 (executable)
@@ -4,6 +4,7 @@
     {% with book.0 as chunk %}
     <tr>
         <td><input type="checkbox" name="select_book" value="{{book.id}}" data-chunk-id="{{chunk.id}}"/></td>
+        <td><a href="{% url 'catalogue_book' book.slug %}" title='{% trans "Lesson settings" %}'>[L]</a></td>
         <td><a target="_blank"
                     href="{% url 'wiki_editor' book.slug %}">
                     {{ book.title }}</a></td>
index ded5922..2a4ea73 100755 (executable)
@@ -16,7 +16,7 @@
 
 <table id="file-list"{% if viewed_user %} class="book-list-user"{% endif %}>
     <thead><tr>
-       <th></th>
+       <th></th><th></th>
         <th class='book-search-column'>
             <form>
             <input title='Szukaj w tytułach modułów' name="title"
index d07303f..94710ca 100644 (file)
@@ -21,7 +21,7 @@ from django.utils.translation import ugettext_lazy as _
 from django.views.decorators.http import require_POST
 from django.template import RequestContext
 
-from apiclient import NotAuthorizedError
+from apiclient import NotAuthorizedError, ApiError
 from catalogue import forms
 from catalogue import helpers
 from catalogue.helpers import active_tab, ajax
@@ -485,7 +485,7 @@ def publish(request, slug):
         book.publish(request.user)
     except NotAuthorizedError:
         return http.HttpResponseRedirect(reverse('apiclient_oauth'))
-    except BaseException, e:
+    except ApiError, e:
         return http.HttpResponse(e)
     else:
         return http.HttpResponseRedirect(book.get_absolute_url())
index 9d7a8f4..309c5b2 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 9d7a8f4916f03faa473d31a219102558f1844475
+Subproject commit 309c5b2981dcc3536cfb82352e3b1dbb5db5167d
index 85ab1e2..d0475d3 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 85ab1e2d4b363dc747eacda08fa254586905cf25
+Subproject commit d0475d381f12b2c89c7c514c4f7f7d2ebc421d0a
diff --git a/redakcja/xslt/wl2to1.xslt b/redakcja/xslt/wl2to1.xslt
new file mode 100644 (file)
index 0000000..8e91bb5
--- /dev/null
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+    xmlns:wl="http://wolnelektury.pl/functions"
+    xmlns:dc="http://purl.org/dc/elements/1.1/" >
+<xsl:output encoding="utf-8" indent="yes" omit-xml-declaration = "yes" version="2.0" />
+
+<xsl:template match="section">
+    <xsl:choose>
+        <xsl:when test="not(ancestor::*)">
+        <utwor xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:wl="http://wolnelektury.pl/functions">
+        <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+        <rdf:Description>
+            <xsl:attribute name="rdf:about">
+                <xsl:text>http://redakcja.edukacjamedialna.edu.pl/documents/book/</xsl:text>
+                <xsl:value-of select="@redslug" />
+                <xsl:text>/</xsl:text>
+            </xsl:attribute>
+        <dc:title xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/"><xsl:apply-templates select="header/text()" /></dc:title>
+        <dc:identifier.url xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">
+            <xsl:text>http://edukacjamedialna.edu.pl/lekcje/</xsl:text>
+            <xsl:value-of select="@wlslug" />
+            <xsl:text>/</xsl:text>
+        </dc:identifier.url>
+
+        <!--dc:creator.expert xml:lang="pl" ></dc:creator.expert>
+        <dc:creator.methodologist xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/"></dc:creator.methodologist>
+        <dc:creator.scenario xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/"></dc:creator.scenario>
+        <dc:creator.textbook xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/"></dc:creator.textbook-->
+
+        <xsl:apply-templates select="metadata" mode="meta" />
+
+        <dc:publisher xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">Fundacja Nowoczesna Polska</dc:publisher>
+        <dc:rights xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">Creative Commons Uznanie autorstwa - Na tych samych warunkach 3.0</dc:rights>
+        <dc:rights.license xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">http://creativecommons.org/licenses/by-sa/3.0/</dc:rights.license>
+        <dc:format xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">xml</dc:format>
+        <!--dc:type xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">added-var</dc:type-->
+        <dc:date xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">2015-01-12</dc:date>
+       <!--dc:audience xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/"><!- -liceum - -><xsl:value-of select="//dc:audience/text()" /></dc:audience-->
+        <dc:language xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">pol</dc:language>
+        </rdf:Description>
+        </rdf:RDF>
+        <powiesc>
+            <xsl:apply-templates />
+        </powiesc>
+        </utwor>
+    </xsl:when>
+    <xsl:when test="count(ancestor::*) = 3">
+        <aktywnosc>
+            <opis><xsl:apply-templates select="header" /><xsl:apply-templates select="div" mode="opis" /></opis>
+            <xsl:apply-templates select="div" mode="aktywnosc" />
+            <xsl:apply-templates select="section" mode="error" />
+        </aktywnosc>
+</xsl:when>
+<xsl:when test='header/text() = "Zadanie dla ucznia..."'>
+
+</xsl:when>
+    <xsl:otherwise><xsl:apply-templates /></xsl:otherwise>
+    </xsl:choose>
+</xsl:template>
+
+<!-- TODO language-dependent: description, audience, requires (subject.competence?) -->
+<xsl:template match="dc:creator.expert|dc:creator.scenario|dc:creator.textbook|dc:description|dc:subject.curriculum|dc:creator.methodologist|dc:subject.competence|dc:audience|dc:type|dc:requires" mode="meta">
+    <xsl:copy><xsl:apply-templates /></xsl:copy>
+</xsl:template>
+
+<xsl:template match="metadata"></xsl:template>
+<xsl:template match="aside"></xsl:template>
+
+
+<xsl:template match="header">
+    <xsl:choose>
+        <xsl:when test="count(ancestor::*) = 1">
+            <nazwa_utworu><xsl:apply-templates /></nazwa_utworu>
+        </xsl:when>
+        <xsl:when test="count(ancestor::*) = 2">
+            <naglowek_rozdzial><xsl:apply-templates /></naglowek_rozdzial>
+        </xsl:when>
+        <xsl:when test="count(ancestor::*) = 3">
+            <naglowek_podrozdzial><xsl:apply-templates /></naglowek_podrozdzial>
+        </xsl:when>
+        <xsl:when test="count(ancestor::*) = 4">
+            <akap><xsl:apply-templates /></akap>
+        </xsl:when>
+    </xsl:choose>
+</xsl:template>
+
+
+<xsl:template match="div">
+    <xsl:choose>
+        <xsl:when test="@class = 'p'">
+            <akap><xsl:apply-templates /></akap>
+        </xsl:when>
+        <xsl:when test="@class = 'list'">
+            <lista typ="punkt"><xsl:apply-templates /></lista>
+        </xsl:when>
+        <xsl:when test="@class = 'list.itemized'">
+            <lista typ="punkt"><xsl:apply-templates /></lista>
+        </xsl:when>
+       <xsl:when test="@class = 'list.enum'">
+           <lista typ="num"><xsl:apply-templates /></lista>
+       </xsl:when>
+        <xsl:when test="@class = 'list.definitions'">
+            <lista typ="slowniczek"><xsl:apply-templates /></lista>
+        </xsl:when>
+        <xsl:when test="@class = 'list.bibliography'">
+            <lista typ="czytelnia"><xsl:apply-templates /></lista>
+        </xsl:when>
+        <xsl:when test="@class = 'item'">
+            <punkt><xsl:apply-templates /></punkt>
+        </xsl:when>
+        <xsl:when test="@class = 'defined'">
+            <xsl:choose>
+                <xsl:when test="count(ancestor::*) = 4 ">
+                    <definiendum><xsl:apply-templates /></definiendum>
+                </xsl:when>
+                <xsl:otherwise>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:when>
+        <xsl:otherwise>
+            <NIEZNANY_DIV><xsl:value-of select="@class" /></NIEZNANY_DIV>
+        </xsl:otherwise>
+    </xsl:choose>
+</xsl:template>
+
+<xsl:template match="div" mode="opis">
+    <xsl:choose>
+        <xsl:when test="@class = 'p'">
+            <akap><xsl:apply-templates /></akap>
+        </xsl:when>
+        <xsl:when test="@class = 'list'">
+            <lista typ="punkt"><xsl:apply-templates /></lista>
+        </xsl:when>
+        <xsl:when test="@class = 'list.itemized'">
+            <lista typ="punkt"><xsl:apply-templates /></lista>
+        </xsl:when>
+        <xsl:when test="@class = 'item'">
+            <punkt><xsl:apply-templates /></punkt>
+        </xsl:when>
+    </xsl:choose>
+</xsl:template>
+
+<xsl:template match="div" mode="aktywnosc">
+    <xsl:choose>
+        <xsl:when test="@class = 'list.definitions'">
+            <xsl:apply-templates mode="defs" />
+        </xsl:when>
+    </xsl:choose>
+</xsl:template>
+
+
+<xsl:template match="div" mode="defs">
+    <xsl:choose>
+        <xsl:when test="div/text() = 'Czas'"><czas><xsl:apply-templates /></czas></xsl:when>
+        <xsl:when test="div/text() = 'Metoda'"><forma><xsl:apply-templates /></forma></xsl:when>
+        <xsl:when test="div/text() = 'Pomoce'"><pomoce><xsl:apply-templates /></pomoce></xsl:when>
+    </xsl:choose>
+</xsl:template>
+
+<xsl:template match="span">
+    <xsl:choose>
+        <xsl:when test="@class = 'link'">
+            <link>
+                <xsl:choose>
+                    <xsl:when test="starts-with(@href, 'file://')">
+                        <xsl:attribute name="material">
+                            <xsl:value-of select="wl:rmext(substring(@href, 8))" />
+                        </xsl:attribute>
+                   </xsl:when>
+                   <xsl:when test="starts-with(@href, 'http')">
+                                   <xsl:attribute name="url">
+                                           <xsl:value-of select="@href" />
+                                   </xsl:attribute>
+                   </xsl:when>
+                   <xsl:otherwise>
+                                   <xsl:attribute name="url">
+                                           <xsl:value-of select="text()" />
+                                   </xsl:attribute>
+                    </xsl:otherwise>
+           </xsl:choose>
+                               <xsl:apply-templates />
+            </link>
+        </xsl:when>
+        <xsl:when test="@class = 'emp'">
+            <wyroznienie><xsl:apply-templates /></wyroznienie>
+    </xsl:when>
+    <xsl:when test="@class='cite'">
+           <dlugi_cytat><xsl:apply-templates /></dlugi_cytat>
+    </xsl:when>
+        <xsl:otherwise>
+            <NIEZNANY_SPAN><xsl:value-of select="@class" /></NIEZNANY_SPAN>
+        </xsl:otherwise>
+    </xsl:choose>
+</xsl:template>
+
+
+<xsl:template match="section" mode="error">
+    NIEZNANA_SEKCJA
+</xsl:template>
+
+</xsl:stylesheet>
index 263d419..ce3795a 100644 (file)
@@ -7,6 +7,7 @@ PyYAML>=3.0
 Pillow
 oauth2
 httplib2 # oauth2 dependency
+texml
 
 ## Book conversion library
 # git+git://github.com/fnp/librarian.git@master#egg=librarian