Publication button works, but need better error messages.
authorŁukasz Rekucki <lrekucki@gmail.com>
Tue, 13 Apr 2010 09:08:27 +0000 (11:08 +0200)
committerŁukasz Rekucki <lrekucki@gmail.com>
Tue, 13 Apr 2010 17:41:40 +0000 (19:41 +0200)
25 files changed:
apps/wiki/constants.py
apps/wiki/forms.py
apps/wiki/helpers.py
apps/wiki/models.py
apps/wiki/nice_diff.py
apps/wiki/templates/wiki/base.html [new file with mode: 0644]
apps/wiki/templates/wiki/document_create_missing.html [new file with mode: 0644]
apps/wiki/templates/wiki/document_list.html
apps/wiki/templates/wiki/history_view.html
apps/wiki/templates/wiki/save_dialog.html
apps/wiki/templates/wiki/summary_view.html
apps/wiki/templates/wiki/tag_dialog.html
apps/wiki/urls.py
apps/wiki/views.py
lib/test_wlapi.py [new file with mode: 0644]
lib/vstorage.py
lib/wlapi.py
platforma/compress_settings.py [new file with mode: 0644]
platforma/context_processors.py
platforma/settings.py
platforma/static/css/summary.css
platforma/static/js/wiki/view_history.js
platforma/static/js/wiki/view_summary.js
platforma/static/js/wiki/wikiapi.js
platforma/urls.py

index 472bbb6..562421a 100644 (file)
@@ -2,23 +2,23 @@
 from django.utils.translation import ugettext_lazy as _
 
 DOCUMENT_TAGS = (
-    ("source", _(u"Tekst źródłowy")),
-    ("first_correction", _(u"Po autokorekcie")),
-    ("tagged", _(u"Tekst otagowany")),
-    ("second_correction", _(u"Po korekcie")),
-    ("source_annotations", _(u"Sprawdzone przypisy źródła")),
-    ("language_updates", _(u"Uwspółcześnienia")),
-    ("ready_to_publish", _(u"Tekst do publikacji")),
+    ("source", _("Tekst źródłowy")),
+    ("first_correction", _("Po autokorekcie")),
+    ("tagged", _("Tekst otagowany")),
+    ("second_correction", _("Po korekcie")),
+    ("source_annotations", _("Sprawdzone przypisy źródła")),
+    ("language_updates", _("Uwspółcześnienia")),
+    ("ready_to_publish", _("Tekst do publikacji")),
 )
 
 DOCUMENT_TAGS_DICT = dict(DOCUMENT_TAGS)
 
 DOCUMENT_STAGES = (
-    ("first_correction", _(u"Autokorekta")),
-    ("tagged", _(u"Tagowanie")),
-    ("second_correction", _(u"Korekta")),
-    ("source_annotations", _(u"Przypisy źródła")),
-    ("language_updates", _(u"Uwspółcześnienia")),
+    ("first_correction", _("Autokorekta")),
+    ("tagged", _("Tagowanie")),
+    ("second_correction", _("Korekta")),
+    ("source_annotations", _("Przypisy źródła")),
+    ("language_updates", _("Uwspółcześnienia")),
 )
 
 DOCUMENT_STAGES_DICT = dict(DOCUMENT_STAGES)
index 4fb71a5..64dac29 100644 (file)
@@ -4,49 +4,46 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django import forms
-from wiki.models import Document, getstorage
 from wiki.constants import DOCUMENT_TAGS, DOCUMENT_STAGES
 from django.utils.translation import ugettext_lazy as _
 
 
-class DocumentForm(forms.Form):
-    """ Old form for saving document's text """
+class DocumentTagForm(forms.Form):
+    """
+        Form for tagging revisions.
+    """
 
-    name = forms.CharField(widget=forms.HiddenInput)
-    text = forms.CharField(widget=forms.Textarea)
+    id = forms.CharField(widget=forms.HiddenInput)
+    tag = forms.ChoiceField(choices=DOCUMENT_TAGS)
     revision = forms.IntegerField(widget=forms.HiddenInput)
-    comment = forms.CharField()
-
-    def __init__(self, *args, **kwargs):
-        document = kwargs.pop('instance', None)
-        super(DocumentForm, self).__init__(*args, **kwargs)
-        if document:
-            self.fields['name'].initial = document.name
-            self.fields['text'].initial = document.text
-            self.fields['revision'].initial = document.revision()
-
-    def save(self, document_author='anonymous'):
-        storage = getstorage()
 
-        document = Document(storage, name=self.cleaned_data['name'], text=self.cleaned_data['text'])
 
-        storage.put(document,
-                author=document_author,
-                comment=self.cleaned_data['comment'],
-                parent=self.cleaned_data['revision'])
+class DocumentCreateForm(forms.Form):
+    """
+        Form used for creating new documents.
+    """
+    title = forms.CharField()
+    id = forms.RegexField(regex=ur"\w+")
+    file = forms.FileField(required=False)
+    text = forms.CharField(required=False, widget=forms.Textarea)
 
-        return storage.get(self.cleaned_data['name'])
+    def clean(self):
+        file = self.cleaned_data['file']
 
+        if file is not None:
+            try:
+                self.cleaned_data['text'] = file.read().decode('utf-8')
+            except UnicodeDecodeError:
+                raise forms.ValidationError("Text file must be UTF-8 encoded.")
 
-class DocumentTagForm(forms.Form):
+        if not self.cleaned_data["text"]:
+            raise forms.ValidationError("You must either enter text or upload a file")
 
-    id = forms.CharField(widget=forms.HiddenInput)
-    tag = forms.ChoiceField(choices=DOCUMENT_TAGS)
-    revision = forms.IntegerField(widget=forms.HiddenInput)
+        return self.cleaned_data
 
 
 class DocumentTextSaveForm(forms.Form):
-    """
+    """if 
     Form for saving document's text:
 
         * name - document's storage identifier.
index 553b1e4..9b326d5 100644 (file)
@@ -24,9 +24,9 @@ class JSONResponse(http.HttpResponse):
         # get rid of mimetype
         kwargs.pop('mimetype', None)
 
-        super(JSONResponse, self).__init__(
-            json.dumps(data, cls=ExtendedEncoder),
-            mimetype="application/json", **kwargs)
+        data = json.dumps(data, cls=ExtendedEncoder)
+        print data
+        super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs)
 
 
 # return errors
index 49bd06c..ad15c5e 100644 (file)
@@ -20,11 +20,12 @@ class DocumentStorage(object):
         self.vstorage = vstorage.VersionedStorage(path)
 
     def get(self, name, revision=None):
-        if revision is None:
-            text = self.vstorage.page_text(name)
-        else:
-            text = self.vstorage.revision_text(name, revision)
-        return Document(self, name=name, text=text)
+        text, rev = self.vstorage.page_text(name, revision)
+        return Document(self, name=name, text=text, revision=rev)
+
+    def get_by_tag(self, name, tag):
+        text, rev = self.vstorage.page_text(name, tag)
+        return Document(self, name=name, text=text, revision=rev)
 
     def get_or_404(self, *args, **kwargs):
         try:
@@ -40,6 +41,18 @@ class DocumentStorage(object):
                 comment=comment,
                 parent=parent)
 
+        return document
+
+    def create_document(self, id, text, title=None):
+        if title is None:
+            title = id.title()
+
+        if text is None:
+            text = u''
+
+        document = Document(self, name=id, text=text, title=title)
+        return self.put(document, u"<wiki>", u"Document created.", None)
+
     def delete(self, name, author, comment):
         self.vstorage.delete_page(name, author, comment)
 
@@ -61,12 +74,6 @@ class Document(object):
         for attr, value in kwargs.iteritems():
             setattr(self, attr, value)
 
-    def revision(self):
-        try:
-            return self.storage._info(self.name)[0]
-        except DocumentNotFound:
-            return - 1
-
     def add_tag(self, tag, revision, author):
         """ Add document specific tag """
         logger.debug("Adding tag %s to doc %s version %d", tag, self.name, revision)
index b228fad..355600b 100755 (executable)
@@ -17,15 +17,12 @@ NAMES = {'+': 'added', '-': 'removed', '^': 'changed'}
 def diff_replace(match):
     return """<span class="diff_mark diff_mark_%s">""" % NAMES[match.group(1)]
 
-
 def filter_line(line):
     return  DIFF_RE.sub(diff_replace, html_escape(line)).replace('\x01', '</span>')
 
-
 def format_changeset(a, b, change):
     return (a[0], filter_line(a[1]), b[0], filter_line(b[1]), change)
 
-
 def html_diff_table(la, lb, context=None):
     all_changes = difflib._mdiff(la, lb)
 
diff --git a/apps/wiki/templates/wiki/base.html b/apps/wiki/templates/wiki/base.html
new file mode 100644 (file)
index 0000000..5680e0d
--- /dev/null
@@ -0,0 +1,24 @@
+{% extends "base.html" %}
+{% load compressed %}
+
+{% block title %}{{ document_name }} - {{ block.super }}{% endblock %}
+
+{% block extrahead %}
+{% compressed_css 'listing' %}
+{% endblock %}
+
+{% block extrabody %}
+{% compressed_js 'listing' %}
+{% endblock %}
+
+{% block maincontent %}
+<h1><img src="{{STATIC_URL}}/img/logo.png">Platforma Redakcyjna</h1>
+<div id="wiki_layout_left_column">
+       {% block leftcolumn %}
+       {% endblock leftcolumn %}
+</div>
+<div id="wiki_layout_right_column">
+       {% block rightcolumn %} 
+       {% endblock rightcolumn %}
+</div>
+{% endblock maincontent %}
\ No newline at end of file
diff --git a/apps/wiki/templates/wiki/document_create_missing.html b/apps/wiki/templates/wiki/document_create_missing.html
new file mode 100644 (file)
index 0000000..a230133
--- /dev/null
@@ -0,0 +1,13 @@
+{% extends "wiki/base.html" %}
+
+{% block leftcolumn %}
+       <form enctype="multipart/form-data" method="POST" action="">
+       {{ form.as_p }}
+       
+       <p><button type="submit">Stwórz utwór</button></p>
+       </form>
+{% endblock leftcolumn %}
+
+{% block rightcolumn %}        
+
+{% endblock rightcolumn %}
\ No newline at end of file
index a47609a..2ab5ab9 100644 (file)
@@ -1,12 +1,8 @@
-{% extends "base.html" %}
-{% load compressed %}
+{% extends "wiki/base.html" %}
 
-{% block extrahead %}
-{% compressed_css 'listing' %}
-{% endblock extrahead %}
 
 {% block extrabody %}
-{% compressed_js 'listing' %}
+{{ block.super }}
 <script type="text/javascript" charset="utf-8">
 $(function() {
        function search(event) {
@@ -23,11 +19,7 @@ $(function() {
 </script>
 {% endblock %}
 
-{% block maincontent %}
-<h1><img src="{{STATIC_URL}}/img/logo.png">Platforma Redakcyjna</h1>
-
-
-<div id="document-list">
+{% block leftcolumn %}
        <form method="get" action="#">
     <table  id="file-list">
        <thead>
@@ -46,7 +38,9 @@ $(function() {
                </tbody>
     </table>
        </form>
+{% endblock leftcolumn %}
 
+{% block rightcolumn %}
        <div id="last-edited-list">
                <h2>Twoje ostatnio otwierane dokumenty:</h2>
                <ol>
@@ -56,7 +50,4 @@ $(function() {
                        {% endfor %}
                </ol>
        </div>
-</div>
-
-</div>
-{% endblock maincontent %}
+{% endblock rightcolumn %}
index 58b5415..6627171 100644 (file)
@@ -1,26 +1,26 @@
 <div id="history-view-editor" class="editor" style="display: none">
     <div class="toolbar">
-       <button type="button" id="make-diff-button">Porównaj</button>
+       <button type="button" id="make-diff-button">Porównaj</button> 
                <button type="button" id="tag-changeset-button">Oznacz wersje</button>
-       </div>
+       </div> 
     <div id="history-view">
         <p class="message-box" style="display:none;"></p>
-
+               
                <table id="changes-list-container">
-        <tbody id="changes-list">
+        <tbody id="changes-list">              
         </tbody>
                <tbody style="display: none;">
-                       <tr class="entry row-stub">
+                       <tr class="entry row-stub">                     
                        <td data-stub-value="version"></td>
                        <td>
                                <span data-stub-value="description"></span>
                                <br />
                <span data-stub-value="author"></span>, <span data-stub-value="date"></span>
                        </td>
-                       <td data-stub-value="tag">
+                       <td data-stub-value="tag">                              
                        </td>
                </tr>
                </tbody>
-               </table>
+               </table>                  
     </div>
 </div>
index 3003991..aba61cc 100644 (file)
@@ -1,40 +1,40 @@
 <div id="save_dialog" class="dialog" data-ui-jsclass="SaveDialog">
-       <form method="POST" action="#">
+       <form method="POST" action="">
        <p>{{ forms.text_save.comment.label }}</p>
        <p class="help_text">
                {{ forms.text_save.comment.help_text}}
                <span data-ui-error-for="{{ forms.text_save.comment.name }}"> </span>
        </p>
        {{forms.text_save.comment }}
-
-
-
+       
+       
+       
        {% if request.user.is_anonymous %}
        <p>
-               {{ forms.text_save.author.label }}:
+               {{ forms.text_save.author.label }}: 
                {{ forms.text_save.author }}
                <span class="help_text">{{ forms.text_save.author.help_text }}</span>
                <span data-ui-error-for="{{ forms.text_save.author.name }}"> </span>
        </p>
        {% else %}
        <p>
-               {{ forms.text_save.stage_completed.label }}:
+               {{ forms.text_save.stage_completed.label }}: 
                {{ forms.text_save.stage_completed }}
                <span class="help_text">{{ forms.text_save.stage_completed.help_text }}</span>
                <span data-ui-error-for="{{ forms.text_save.stage_completed.name }}"> </span>
        </p>
        {% endif %}
-
-
+       
+       
        {% for f in forms.text_save.hidden_fields %}
                {{ f }}
        {% endfor %}
-
+       
        <p data-ui-error-for="__all__"> </p>
-
+       
        <p class="action_area">
                <button type="submit" class"ok" data-ui-action="save">Zapisz</button>
                <button type="button" class="cancel" data-ui-action="cancel">Anuluj</button>
-       </p>
-       </form>
+       </p>    
+       </form> 
 </div>
index 2e83cea..5e1f82a 100644 (file)
@@ -3,7 +3,7 @@
     </div> -->
     <div id="summary-view">
                <img class="book-cover" src="{{MEDIA_URL}}images/empty.png">
-               <form>
+               
                <h2>
                        <label for="title">Tytuł:</label>
                        <span data-ui-editable="true" data-edit-target="meta.displayTitle"
@@ -25,7 +25,7 @@
                        <span data-ui-editable="true" data-edit-target="meta.galleryLink"
                        >{{ document_meta.gallery}}</span>
                </p>
-               </form>
-
+               
+               <p><button type="button">Publikuj na wolnelektury.pl</button></p>
        </div>
 </div>
\ No newline at end of file
index a79a616..7212e36 100644 (file)
@@ -1,19 +1,19 @@
 <div id="add_tag_dialog" class="dialog" data-ui-jsclass="AddTagDialog">
        <form method="POST" action="#">
                {% for field in forms.add_tag.visible_fields %}
-               <p>{{ field.label_tag }} {{ field }} <span data-ui-error-for="{{ field.name }}"> </span></p>
-               <p>{{ field.help_text }}</p>
+               <p>{{ field.label_tag }} {{ field }} <span data-ui-error-for="{{ field.name }}"> </span></p> 
+               <p>{{ field.help_text }}</p>            
                {% endfor %}
-
-
-               {% for f in forms.add_tag.hidden_fields %}
+                       
+                       
+               {% for f in forms.text_save.hidden_fields %}
                        {{ f }}
                {% endfor %}
-               <p data-ui-error-for="__all__"> </p>
-
+               <p data-ui-error-for="__all__"> </p>    
+               
                <p class="action_area">
                        <button type="submit" class"ok" data-ui-action="save">Zapisz</button>
                        <button type="button" class="cancel" data-ui-action="cancel">Anuluj</button>
-               </p>
+               </p>    
        </form>
 </div>
index fae11ef..d649ba5 100644 (file)
@@ -4,6 +4,8 @@ from django.conf import settings
 urlpatterns = patterns('wiki.views',
     url(r'^$',
         'document_list', name='wiki_doclist'),
+    url(r'^create/(?P<name>[^/]+)',
+        'document_create_missing', name='wiki_create_missing'),
     url(r'^gallery/(?P<directory>[^/]+)$',
         'document_gallery', name="wiki_gallery"),
     url(r'^(?P<name>[^/]+)/history$',
@@ -16,6 +18,8 @@ urlpatterns = patterns('wiki.views',
         'document_diff', name="wiki_diff"),
     url(r'^(?P<name>[^/]+)/tags$',
         'document_add_tag', name="wiki_add_tag"),
+    url(r'^(?P<name>[^/]+)/tags$',
+        'document_publish'),
     url(r'^(?P<name>[^/]+)$',
         'document_detail', name="wiki_details"),
 )
index dbc05df..e466d6a 100644 (file)
@@ -4,11 +4,12 @@ from django.conf import settings
 
 from django.views.generic.simple import direct_to_template
 from django.views.decorators.http import require_POST
+from django.core.urlresolvers import reverse
 from wiki.helpers import JSONResponse, JSONFormInvalid, JSONServerError, ajax_require_permission
 from django import http
 
-from wiki.models import getstorage
-from wiki.forms import DocumentTextSaveForm, DocumentTagForm
+from wiki.models import getstorage, DocumentNotFound
+from wiki.forms import DocumentTextSaveForm, DocumentTagForm, DocumentCreateForm
 from datetime import datetime
 from django.utils.encoding import smart_unicode
 from django.utils.translation import ugettext_lazy as _
@@ -42,7 +43,10 @@ def document_list(request, template_name='wiki/document_list.html'):
 @never_cache
 def document_detail(request, name, template_name='wiki/document_details.html'):
 
-    document = getstorage().get_or_404(name)
+    try:
+        document = getstorage().get(name)
+    except DocumentNotFound:
+        return http.HttpResponseRedirect(reverse("wiki_create_missing", args=[name]))
 
     access_time = datetime.now()
     last_documents = request.session.get("wiki_last_docs", {})
@@ -55,22 +59,51 @@ def document_detail(request, name, template_name='wiki/document_details.html'):
 
     return direct_to_template(request, template_name, extra_context={
         'document': document,
+        'document_name': document.name,
         'document_info': document.info,
         'document_meta': document.meta,
-        'forms': {"text_save": DocumentTextSaveForm(), "add_tag": DocumentTagForm()},
+        'forms': {
+            "text_save": DocumentTextSaveForm(prefix="textsave"),
+            "add_tag": DocumentTagForm(prefix="addtag")
+        },
+    })
+
+
+def document_create_missing(request, name):
+    storage = getstorage()
+
+    if request.method == "POST":
+        form = DocumentCreateForm(request.POST, request.FILES)
+        if form.is_valid():
+            doc = storage.create_document(
+                id=form.cleaned_data['id'],
+                text=form.cleaned_data['text'],
+            )
+
+            return http.HttpResponseRedirect(reverse("wiki_details", args=[doc.name]))
+    else:
+        form = DocumentCreateForm(initial={
+                "id": name.replace(" ", "_"),
+                "title": name.title(),
+        })
+
+    return direct_to_template(request, "wiki/document_create_missing.html", extra_context={
+        "document_name": name,
+        "form": form,
     })
 
 
 @never_cache
 def document_text(request, name):
     storage = getstorage()
-    document = storage.get_or_404(name)
 
     if request.method == 'POST':
-        form = DocumentTextSaveForm(request.POST)
+        form = DocumentTextSaveForm(request.POST, prefix="textsave")
 
         if form.is_valid():
             revision = form.cleaned_data['parent_revision']
+
+            document = storage.get_or_404(name, revision)
             document.text = form.cleaned_data['text']
 
             storage.put(document,
@@ -79,18 +112,34 @@ def document_text(request, name):
                 parent=revision,
             )
 
+            document = storage.get(name)
+
             return JSONResponse({
-                'text': document.plain_text if revision != document.revision() else None,
+                'text': document.plain_text if revision != document.revision else None,
                 'meta': document.meta(),
-                'revision': document.revision(),
+                'revision': document.revision,
             })
         else:
             return JSONFormInvalid(form)
     else:
+        revision = request.GET.get("revision", None)
+
+        try:
+            try:
+                revision = revision and int(revision)
+                logger.info("Fetching %s", revision)
+                document = storage.get(name, revision)
+            except ValueError:
+                # treat as a tag
+                logger.info("Fetching tag %s", revision)
+                document = storage.get_by_tag(name, revision)
+        except DocumentNotFound:
+            raise http.Http404
+
         return JSONResponse({
             'text': document.plain_text,
             'meta': document.meta(),
-            'revision': document.revision(),
+            'revision': document.revision,
         })
 
 
@@ -116,9 +165,8 @@ def document_gallery(request, directory):
         images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)]
         images.sort()
         return JSONResponse(images)
-    except (IndexError, OSError), exc:
-        import traceback
-        traceback.print_exc()
+    except (IndexError, OSError) as e:
+        logger.exception("Unable to fetch gallery")
         raise http.Http404
 
 
@@ -147,7 +195,7 @@ def document_history(request, name):
     storage = getstorage()
 
     # TODO: pagination
-    changesets = storage.history(name)
+    changesets = list(storage.history(name))
 
     return JSONResponse(changesets)
 
@@ -157,7 +205,7 @@ def document_history(request, name):
 def document_add_tag(request, name):
     storage = getstorage()
 
-    form = DocumentTagForm(request.POST)
+    form = DocumentTagForm(request.POST, prefix="addtag")
     if form.is_valid():
         doc = storage.get_or_404(form.cleaned_data['id'])
         doc.add_tag(tag=form.cleaned_data['tag'],
@@ -170,13 +218,12 @@ def document_add_tag(request, name):
 
 @require_POST
 @ajax_require_permission('wiki.can_publish')
-def document_publish(request, name, version):
+def document_publish(request, name):
     storage = getstorage()
+    document = storage.get_by_tag(name, "ready_to_publish")
 
-    # get the document
-    document = storage.get_or_404(name, revision=int(version))
+    api = wlapi.WLAPI(**settings.WL_API_CONFIG)
 
-    api = wlapi.WLAPI(settings.WL_API_CONFIG)
     try:
         return JSONResponse({"result": api.publish_book(document)})
     except wlapi.APICallException, e:
diff --git a/lib/test_wlapi.py b/lib/test_wlapi.py
new file mode 100644 (file)
index 0000000..a12ab06
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+
+from nose.tools import *
+from nose.core import runmodule
+
+import wlapi
+
+
+class FakeDocument():
+
+    def __init__(self):
+        self.text = "Some Text"
+
+
+class TestWLAPI(object):
+
+    def setUp(self):
+        self.api = wlapi.WLAPI(
+            URL="http://localhost:7000/api/",
+            AUTH_REALM="WL API",
+            AUTH_USER="platforma",
+            AUTH_PASSWD="platforma",
+        )
+
+    def test_basic_call(self):
+        assert_equal(self.api.list_books(), [])
+
+    def test_publish_book(self):
+        self.api.publish_book(FakeDocument())
+
+if __name__ == '__main__':
+    runmodule()
index f16496d..4cfd59a 100644 (file)
@@ -197,10 +197,12 @@ class VersionedStorage(object):
 
     @with_working_copy_locked
     @with_storage_locked
-    def save_file(self, title, file_name, author=u'', comment=u'', parent=None):
+    def save_file(self, title, file_name, **kwargs):
         """Save an existing file as specified page."""
-        user = author.encode('utf-8') or u'anonymous'.encode('utf-8')
-        text = comment.encode('utf-8') or u'comment'.encode('utf-8')
+
+        author = kwargs.get('author', u'anonymous').encode('utf-8')
+        comment = kwargs.get('comment', u'Empty comment.').encode('utf-8')
+        parent = kwargs.get('parent', None)
 
         repo_file = self._title_to_file(title)
         file_path = self._file_path(title)
@@ -215,11 +217,11 @@ class VersionedStorage(object):
             current_page_rev = -1
 
         if parent is not None and current_page_rev != parent:
-            msg = self.merge_changes(changectx, repo_file, text, user, parent)
-            user = '<wiki>'
-            text = msg.encode('utf-8')
+            msg = self.merge_changes(changectx, repo_file, comment, author, parent)
+            author = '<wiki>'
+            comment = msg.encode('utf-8')
 
-        self._commit([repo_file], text, user)
+        self._commit([repo_file], comment, author)
 
     def save_data(self, title, data, **kwargs):
         """Save data as specified page."""
@@ -229,7 +231,8 @@ class VersionedStorage(object):
             f = open(file_path, "wb")
             f.write(data)
             f.close()
-            self.save_file(title=title, file_name=file_path, **kwargs)
+
+            return self.save_file(title=title, file_name=file_path, **kwargs)
         finally:
             try:
                 os.unlink(file_path)
@@ -240,23 +243,14 @@ class VersionedStorage(object):
             except OSError:
                 pass
 
-    def save_text(self, text, **kwargs):
+    def save_text(self, **kwargs):
         """Save text as specified page, encoded to charset."""
-        self.save_data(data=text.encode(self.charset), **kwargs)
+        text = kwargs.pop('text')
+        return self.save_data(data=text.encode(self.charset), **kwargs)
 
-    def _commit(self, files, text, user):
+    def _commit(self, files, comment, user):
         match = mercurial.match.exact(self.repo_path, '', list(files))
-        return self.repo.commit(match=match, text=text, user=user, force=True)
-
-    def page_text(self, title):
-        """Read unicode text of a page."""
-        data = self.open_page(title).read()
-        text = unicode(data, self.charset, 'replace')
-        return text
-
-    def page_lines(self, page):
-        for data in page:
-            yield unicode(data, self.charset, 'replace')
+        return self.repo.commit(match=match, text=comment, user=user, force=True)
 
     @with_working_copy_locked
     @with_storage_locked
@@ -272,25 +266,41 @@ class VersionedStorage(object):
         self.repo.remove([repo_file])
         self._commit([repo_file], text, user)
 
-    @with_working_copy_locked
-    def open_page(self, title):
-        if title not in self:
-            raise DocumentNotFound()
+#    @with_working_copy_locked
+#    def _open_page(self, title):
+#        if title not in self:
+#            raise DocumentNotFound()
+#
+#        path = self._title_to_file(title)
+#        logger.debug("Opening page %s", path)
+#        try:
+#            return self.repo.wfile(path, 'rb')
+#        except IOError:
+#            logger.exception("Failed to open page %s", title)
+#            raise DocumentNotFound()
+
+    def page_text(self, title, revision=None):
+        """Read unicode text of a page."""
+        ctx = self._find_filectx(title, revision)
+        return ctx.data().decode(self.charset, 'replace'), ctx.filerev()
+
+    def page_text_by_tag(self, title, tag):
+        """Read unicode text of a taged page."""
+        tag = u"{title}#{tag}".format(**locals()).encode('utf-8')
+        fname = self._title_to_file(title)
 
-        path = self._title_to_file(title)
-        logger.debug("Opening page %s", path)
         try:
-            return self.repo.wfile(path, 'rb')
-        except IOError:
-            logger.exception("Failed to open page %s", title)
+            ctx = self.repo[tag][fname]
+            return ctx.data().decode(self.charset, 'replace'), ctx.filerev()
+        except IndexError:
             raise DocumentNotFound()
 
     @with_working_copy_locked
     def page_file_meta(self, title):
         """Get page's inode number, size and last modification time."""
         try:
-            (st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size,
-             st_atime, st_mtime, st_ctime) = os.stat(self._file_path(title))
+            (_st_mode, st_ino, _st_dev, _st_nlink, _st_uid, _st_gid, st_size,
+             _st_atime, st_mtime, _st_ctime) = os.stat(self._file_path(title))
         except OSError:
             return 0, 0, 0
         return st_ino, st_size, st_mtime
@@ -307,9 +317,8 @@ class VersionedStorage(object):
         rev = filectx_tip.filerev()
         filectx = filectx_tip.filectx(rev)
         date = datetime.datetime.fromtimestamp(filectx.date()[0])
-        author = unicode(filectx.user(), "utf-8",
-                         'replace').split('<')[0].strip()
-        comment = unicode(filectx.description(), "utf-8", 'replace')
+        author = filectx.user().decode("utf-8", 'replace')
+        comment = filectx.description().decode("utf-8", 'replace')
         return rev, date, author, comment
 
     def repo_revision(self):
@@ -329,11 +338,14 @@ class VersionedStorage(object):
         """Find the last revision in which the file existed."""
 
         repo_file = self._title_to_file(title)
-        changectx = self._changectx()
+
+        changectx = self._changectx()  # start with tip
         stack = [changectx]
+
         while repo_file not in changectx:
             if not stack:
                 return None
+
             changectx = stack.pop()
             for parent in changectx.parents():
                 if parent != changectx:
@@ -341,8 +353,13 @@ class VersionedStorage(object):
 
         try:
             fctx = changectx[repo_file]
-            return fctx if rev is None else fctx.filectx(rev)
-        except IndexError, LookupError:
+
+            if rev is not None:
+                fctx = fctx.filectx(rev)
+                fctx.filerev()
+
+            return fctx
+        except (IndexError, LookupError) as e:
             raise DocumentNotFound()
 
     def page_history(self, title):
@@ -355,9 +372,8 @@ class VersionedStorage(object):
         for rev in range(maxrev, minrev - 1, -1):
             filectx = filectx_tip.filectx(rev)
             date = datetime.datetime.fromtimestamp(filectx.date()[0])
-            author = unicode(filectx.user(), "utf-8",
-                             'replace').split('<')[0].strip()
-            comment = unicode(filectx.description(), "utf-8", 'replace')
+            author = filectx.user().decode('utf-8', 'replace')
+            comment = filectx.description().decode("utf-8", 'replace')
             tags = [t.rsplit('#', 1)[-1] for t in filectx.changectx().tags() if '#' in t]
 
             yield {
@@ -368,21 +384,12 @@ class VersionedStorage(object):
                 "tag": tags,
             }
 
-    def page_revision(self, title, rev):
-        """Get unicode contents of specified revision of the page."""
-        return self._find_filectx(title, rev).data()
-
-    def revision_text(self, title, rev):
-        data = self.page_revision(title, rev)
-        text = unicode(data, self.charset, 'replace')
-        return text
-
     @with_working_copy_locked
     def add_page_tag(self, title, rev, tag, user, doctag=True):
         if doctag:
-            tag = "{title}#{tag}".format(**locals())
+            tag = u"{title}#{tag}".format(**locals()).encode('utf-8')
 
-        message = "Assigned tag {tag} to version {rev} of {title}".format(**locals())
+        message = u"Assigned tag {tag!r} to version {rev!r} of {title!r}".format(**locals()).encode('utf-8')
 
         fctx = self._find_filectx(title, rev)
         self.repo.tag(
@@ -399,9 +406,8 @@ class VersionedStorage(object):
         for wiki_rev in range(maxrev, minrev - 1, -1):
             change = self.repo.changectx(wiki_rev)
             date = datetime.datetime.fromtimestamp(change.date()[0])
-            author = unicode(change.user(), "utf-8",
-                             'replace').split('<')[0].strip()
-            comment = unicode(change.description(), "utf-8", 'replace')
+            author = change.user().decode('utf-8', 'replace')
+            comment = change.description().decode("utf-8", 'replace')
             for repo_file in change.files():
                 if repo_file.startswith(self.repo_prefix):
                     title = self._file_to_title(repo_file)
index ba1be14..0a674c8 100644 (file)
@@ -14,7 +14,16 @@ logger = logging.getLogger("fnp.lib.wlapi")
 
 
 class APICallException(Exception):
-    pass
+
+    def __init__(self, cause=None):
+        super(Exception, self).__init__()
+        self.cause = cause
+
+    def __unicode__(self):
+        return u"%s, cause: %s" % (type(self).__name__, repr(self.cause))
+
+    def __str__(self):
+        return self.__unicode__().encode('utf-8')
 
 
 def api_call(path, format="json"):
@@ -29,7 +38,7 @@ def api_call(path, format="json"):
             # prepare request
             rq = urllib2.Request(self.base_url + path + ".json")
 
-            # will send POST when there is data, GET otherwise
+            # will send POST when there is data, GET otherwise            
             if data is not None:
                 rq.add_data(json.dumps(data))
                 rq.add_header("Content-Type", "application/json")
@@ -51,23 +60,34 @@ def api_call(path, format="json"):
 
 class WLAPI(object):
 
-    def __init__(self, config_dict):
+    def __init__(self, **config_dict):
         self.base_url = config_dict['URL']
         self.auth_realm = config_dict['AUTH_REALM']
         self.auth_user = config_dict['AUTH_USER']
 
-        auth_handler = urllib2.HTTPDigestAuthHandler()
-        auth_handler.add_password(
+        digest_handler = urllib2.HTTPDigestAuthHandler()
+        digest_handler.add_password(
+                    realm=self.auth_realm, uri=self.base_url,
+                    user=self.auth_user, passwd=config_dict['AUTH_PASSWD'])
+
+        basic_handler = urllib2.HTTPBasicAuthHandler()
+        basic_handler.add_password(
                     realm=self.auth_realm, uri=self.base_url,
                     user=self.auth_user, passwd=config_dict['AUTH_PASSWD'])
 
-        self.opener = urllib2.build_opener(auth_handler)
+        self.opener = urllib2.build_opener(digest_handler, basic_handler)
 
     def _http_error(self, error):
-        return self._error()
+        message = error.read()
+        logger.debug("HTTP ERROR: %s", message)
+        return self._error(message)
 
     def _error(self, error):
-        raise APICallException(cause=error)
+        raise APICallException(error)
+
+    @api_call("books")
+    def list_books(self):
+        yield
 
     @api_call("books")
     def publish_book(self, document):
diff --git a/platforma/compress_settings.py b/platforma/compress_settings.py
new file mode 100644 (file)
index 0000000..a287001
--- /dev/null
@@ -0,0 +1,72 @@
+# CSS and JS files to compress
+COMPRESS_CSS = {
+    'detail': {
+         'source_filenames': (
+            'css/master.css',
+            'css/gallery.css',
+            'css/history.css',
+            'css/summary.css',
+            'css/html.css',
+            'css/jquery.autocomplete.css',
+            'css/dialogs.css',
+        ),
+        'output_filename': 'compressed/detail_styles_?.css',
+    },
+    'listing': {
+        'source_filenames': (
+            'css/filelist.css',
+        ),
+        'output_filename': 'compressed/listing_styles_?.css',
+     }
+}
+
+COMPRESS_JS = {
+    # everything except codemirror
+    'detail': {
+        'source_filenames': (
+                # libraries
+                'js/jquery-1.4.2.min.js',
+                'js/jquery.autocomplete.js',
+                'js/jquery.blockui.js',
+                'js/jquery.elastic.js',
+                'js/button_scripts.js',
+                'js/slugify.js',
+
+                # wiki scripts
+                'js/wiki/wikiapi.js',
+                'js/wiki/xslt.js',
+
+                # base UI
+                'js/wiki/base.js',
+
+                # dialogs
+                'js/wiki/dialog_save.js',
+
+                # views
+                'js/wiki/view_history.js',
+                'js/wiki/view_summary.js',
+                'js/wiki/view_editor_source.js',
+                'js/wiki/view_editor_wysiwyg.js',
+                'js/wiki/view_gallery.js',
+                'js/wiki/view_column_diff.js',
+
+                # bootstrap
+                'js/wiki/loader.js',
+        ),
+        'output_filename': 'compressed/detail_scripts_?.js',
+     },
+    'listing': {
+        'source_filenames': (
+                'js/jquery-1.4.2.min.js',
+                'js/slugify.js',
+        ),
+        'output_filename': 'compressed/listing_scripts_?.js',
+     }
+}
+
+COMPRESS = True
+COMPRESS_CSS_FILTERS = None
+COMPRESS_JS_FILTERS = None
+COMPRESS_AUTO = False
+COMPRESS_VERSION = True
+COMPRESS_VERSIONING = 'compress.versioning.hash.MD5Versioning'
index 4ba9bb7..db6c98b 100644 (file)
@@ -4,4 +4,5 @@
 def settings(request):
     from django.conf import settings
     return {'MEDIA_URL': settings.MEDIA_URL,
-            'STATIC_URL': settings.STATIC_URL}
+            'STATIC_URL': settings.STATIC_URL,
+            'REDMINE_URL': settings.REDMINE_URL}
index e7eab7a..4ec680f 100644 (file)
@@ -153,6 +153,14 @@ FILEBROWSER_DEFAULT_ORDER = "path_relative"
 # REPOSITORY_PATH = '/Users/zuber/Projekty/platforma/files/books'
 IMAGE_DIR = 'images'
 
+
+WL_API_CONFIG = {
+    "URL": "http://localhost:7000/api/",
+    "AUTH_REALM": "wlapi",
+    "AUTH_USER": "platforma",
+    "AUTH_PASSWD": "platforma",
+}
+
 # Import localsettings file, which may override settings defined here
 try:
     from localsettings import *
index 9f8def0..8fe3e73 100644 (file)
@@ -9,13 +9,17 @@
 #summary-view .book-cover {
        float: left;
        margin: 1em;
+       min-height: 300px;
+       min-width: 240px;
+       
        max-height: 300px;      
+       max-width: 240px;
 }
 
-#summary-view form p {
+#summary-view p {
        margin: 0.5em; 
 }
 
-#summary-view form label {
+#summary-view label {
        font-weight: bold; 
 }  
index f7f2a28..b2fe0b0 100644 (file)
@@ -71,7 +71,7 @@
             var $stub = $('#history-view .row-stub');
             changes_list.html('');
 
-                       var tags = $('select#id_tag option');
+                       var tags = $('select#id_addtag_tag option');
 
             $.each(data, function(){
                 $.wiki.renderStub({
index f86d975..736f4e3 100644 (file)
@@ -2,7 +2,25 @@
 
        function SummaryPerspective(options) {
                var old_callback = options.callback;
+               var self = this;
+
         options.callback = function(){
+                       $('#publish_button').click(function() {
+                               $.blockUI({message: "Oczekiwanie na odpowiedź serwera..."});
+                               self.doc.publish({
+                                       success: function(doc, data) {
+                                               $.blockUI({message: "Udało się", timeout: 2000});
+                                       },
+                                       failure: function(doc, message) {
+                                               $.blockUI({
+                                                       message: message,
+                                                       timeout: 5000
+                                               });
+                                       }
+                                       
+                               });
+                       });
+                       
                        old_callback.call(this);
                };
 
index ac4c5b1..a288397 100644 (file)
@@ -1,9 +1,11 @@
 (function($) {
-
        $.wikiapi = {};
-       var noop = function() {};
-       var noops = {success: noop, failure: noop};
-
+       var noop = function() {
+       };
+       var noops = {
+               success: noop,
+               failure: noop
+       };
        /*
         * Return absolute reverse path of given named view.
         * (at least he have it hard-coded in one place)
         * TODO: think of a way, not to hard-code it here ;)
         *
         */
-        function reverse() {
+       function reverse() {
                var vname = arguments[0];
-
-               if(vname == "ajax_document_text") {
+               var base_path = "/documents";
+               
+               if (vname == "ajax_document_text") {
                        var path = "/" + arguments[1] + "/text";
-                       if (arguments[2] !== undefined)
+                       
+               if (arguments[2] !== undefined) 
                                path += "/" + arguments[2];
-                       return path;
+                       
+                       return base_path + path;
                }
-
+               
                if (vname == "ajax_document_history") {
-                       return "/" + arguments[1] + "/history";
+                       
+                       return base_path + "/" + arguments[1] + "/history";
                }
-
+               
                if (vname == "ajax_document_gallery") {
-                       return "/gallery/" + arguments[1];
+                       
+                       return base_path + "/gallery/" + arguments[1];
                }
-
-               if(vname == "ajax_document_diff")
-                       return "/" + arguments[1] + "/diff";
-
-               if(vname == "ajax_document_addtag")
-                       return "/" + arguments[1] + "/tags";
-
-               console.log("Couldn't reverse match:", vname);
+               
+               if (vname == "ajax_document_diff")      
+                       return base_path + "/" + arguments[1] + "/diff";
+               
+               if (vname == "ajax_document_addtag")
+                       return base_path + "/" + arguments[1] + "/tags";
+                       
+               if (vname == "ajax_publish")
+                       return base_path + "/" + arguments[1] + "/publish";
+                       
+               console.log("Couldn't reverse match:", vname);          
                return "/404.html";
        };
-
+       
        /*
         * Document Abstraction
         */
        function WikiDocument(element_id) {
-               var meta = $('#'+element_id);
-
+               var meta = $('#' + element_id);
                this.id = meta.attr('data-document-name');
                this.revision = $("*[data-key='revision']", meta).text();
                this.galleryLink = $("*[data-key='gallery']", meta).text();
                this.galleryImages = [];
                this.text = null;
-
                this.has_local_changes = false;
                this._lock = -1;
                this._context_lock = -1;
                this._lock_count = 0;
        };
-
-
+       
        WikiDocument.prototype.triggerDocumentChanged = function() {
                $(document).trigger('wlapi_document_changed', this);
        };
-
        /*
         * Fetch text of this document.
         */
        WikiDocument.prototype.fetch = function(params) {
                params = $.extend({}, noops, params);
                var self = this;
-
                $.ajax({
                        method: "GET",
                        url: reverse("ajax_document_text", self.id),
                        dataType: 'json',
-                       success: function(data)
-                       {
+                       success: function(data) {
                                var changed = false;
-
-                               if (self.text === null || self.revision !== data.revision) {
+                               
+if (self.text === null || self.revision !== data.revision) {
                                        self.text = data.text;
                                        self.revision = data.revision;
                                        self.gallery = data.gallery;
                                        changed = true;
                                        self.triggerDocumentChanged();
                                };
-
+                               
                                self.has_local_changes = false;
                                params['success'](self, changed);
                        },
@@ -93,7 +97,6 @@
                        }
                });
        };
-
        /*
         * Fetch history of this document.
         *
                /* this doesn't modify anything, so no locks */
                params = $.extend({}, noops, params);
                var self = this;
-
                $.ajax({
                        method: "GET",
                        url: reverse("ajax_document_history", self.id),
                        dataType: 'json',
-                       data: {"from": params['from'], "upto": params['upto']},
+                       data: {
+                               "from": params['from'],
+                               "upto": params['upto']
+                       },
                        success: function(data) {
                                params['success'](self, data);
                        },
                        }
                });
        };
-
        WikiDocument.prototype.fetchDiff = function(params) {
                /* this doesn't modify anything, so no locks */
                var self = this;
-
                params = $.extend({
                        'from': self.revision,
                        'to': self.revision
                }, noops, params);
-
                $.ajax({
                        method: "GET",
                        url: reverse("ajax_document_diff", self.id),
                        dataType: 'html',
-                       data: {"from": params['from'], "to": params['to']},
+                       data: {
+                               "from": params['from'],
+                               "to": params['to']
+                       },
                        success: function(data) {
                                params['success'](self, data);
                        },
                        }
                });
        };
-
        /*
         * Fetch gallery
         */
        WikiDocument.prototype.refreshGallery = function(params) {
                params = $.extend({}, noops, params);
                var self = this;
-
                $.ajax({
                        method: "GET",
                        url: reverse("ajax_document_gallery", self.galleryLink),
                        }
                });
        };
-
        /*
         * Set document's text
         */
                this.text = text;
                this.has_local_changes = true;
        };
-
        /*
         * Set document's gallery link
         */
                this.galleryLink = gallery;
                this.has_local_changes = true;
        };
-
        /*
         * Save text back to the server
         */
-       WikiDocument.prototype.save = function(params){
+       WikiDocument.prototype.save = function(params) {
                params = $.extend({}, noops, params);
                var self = this;
-
+               
                if (!self.has_local_changes) {
-                       console.log("Abort: no changes.");
+                       console.log("Abort: no changes.");                      
                        return params['success'](self, false, "Nie ma zmian do zapisania.");
                };
-
+               
                // Serialize form to dictionary
                var data = {};
                $.each(params['form'].serializeArray(), function() {
                        data[this.name] = this.value;
                });
-
                var metaComment = '<!--';
                metaComment += '\n\tgallery:' + self.galleryLink;
                metaComment += '\n-->\n'
-
-               data.text = metaComment + self.text;
-               data.comment = data.comment;
-
+               
+               data['textsave-text'] = metaComment + self.text;
+               
                $.ajax({
                        url: reverse("ajax_document_text", self.id),
                        type: "POST",
                        dataType: "json",
                        data: data,
-                       success: function(data){
+                       success: function(data) {
                                var changed = false;
+                               
                                if (data.text) {
                                        self.text = data.text;
                                        self.revision = data.revision;
                                        self.gallery = data.gallery;
                                        changed = true;
                                        self.triggerDocumentChanged();
-                               }
-                               params['success'](self, changed,
-                                       ((changed && "Udało się zapisać :)") || "Twoja wersja i serwera jest identyczna") );
+                               };
+                               
+                               params['success'](self, changed, ((changed && "Udało się zapisać :)") || "Twoja wersja i serwera jest identyczna"));
                        },
                        error: function(xhr) {
                                try {
                                        params['failure'](self, $.parseJSON(xhr.responseText));
-                               }
-                               catch(e) {
-                                       params['failure'](self, {"__message": "<p>Nie udało się zapisać - błąd serwera.</p>"});
+                               } 
+                               catch (e) {
+                                       params['failure'](self, {
+                                               "__message": "<p>Nie udało się zapisać - błąd serwera.</p>"
+                                       });
                                };
+                               
                        }
                });
        }; /* end of save() */
-
+       WikiDocument.prototype.publish = function(params) {
+               params = $.extend({}, noops, params);
+               var self = this;
+               $.ajax({
+                       url: reverse("ajax_publish", self.id),
+                       type: "POST",
+                       dataType: "json",
+                       success: function(data) {
+                               params.success(self, data);
+                       },
+                       error: function(xhr) {
+                               if (xhr.status == 403 || xhr.status == 401) {
+                                       params.failure(self, "Nie masz uprawnień lub nie jesteś zalogowany.");
+                               }
+                               else {
+                                       try {
+                                               params.failure(self, xhr.responseText);
+                                       } 
+                                       catch (e) {
+                                               params.failure(self, "Nie udało się - błąd serwera.");                                              
+                                       };                                      
+                               };
+                               
+                       }
+               });
+       };
        WikiDocument.prototype.setTag = function(params) {
                params = $.extend({}, noops, params);
                var self = this;
-
                var data = {
                        "id": self.id,
                };
-
                /* unpack form */
                $.each(params.form.serializeArray(), function() {
                        data[this.name] = this.value;
                });
-
                $.ajax({
                        url: reverse("ajax_document_addtag", self.id),
                        type: "POST",
                        dataType: "json",
                        data: data,
-                       success: function(data){
+                       success: function(data) {
                                params.success(self, data.message);
                        },
-                       error: function(xhr) {
+                       error: function(xhr) {                          
                                if (xhr.status == 403 || xhr.status == 401) {
                                        params.failure(self, {
                                                "__all__": ["Nie masz uprawnień lub nie jesteś zalogowany."]
                                else {
                                        try {
                                                params.failure(self, $.parseJSON(xhr.responseText));
-                                       }
+                                       } 
                                        catch (e) {
                                                params.failure(self, {
                                                        "__all__": ["Nie udało się - błąd serwera."]
                                                });
                                        };
+                                       
                                };
+                               
                        }
                });
        };
-
        $.wikiapi.WikiDocument = WikiDocument;
-
 })(jQuery);
index 2e50e4b..3c2b6c8 100644 (file)
@@ -23,5 +23,7 @@ urlpatterns = patterns('',
         {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
     url(r'^%s(?P<path>.+)$' % settings.STATIC_URL[1:], 'django.views.static.serve',
         {'document_root': settings.STATIC_ROOT, 'show_indexes': True}),
-    url(r'^', include('wiki.urls')),
+
+    url(r'^$', 'redirect_to', {'url': '/documents/'}),
+    url(r'^documents/', include('wiki.urls')),
 )