#880: hide copyrighted stuff,
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Mon, 24 Oct 2011 11:32:12 +0000 (13:32 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Mon, 24 Oct 2011 11:32:12 +0000 (13:32 +0200)
#1777: nicer book create form,
USE_CELERY option,
some cleaning

16 files changed:
apps/catalogue/forms.py
apps/catalogue/locale/pl/LC_MESSAGES/django.mo
apps/catalogue/locale/pl/LC_MESSAGES/django.po
apps/catalogue/migrations/0006_auto__add_field_book_public.py [new file with mode: 0644]
apps/catalogue/models/book.py
apps/catalogue/models/chunk.py
apps/catalogue/tasks.py
apps/catalogue/templates/catalogue/document_create_missing.html
apps/catalogue/templatetags/book_list.py
apps/catalogue/views.py
apps/catalogue/xml_tools.py
apps/wiki/views.py
redakcja/localsettings.sample
redakcja/settings/test.py
redakcja/static/css/filelist.css
redakcja/static/js/wiki/wikiapi.js

index 8cd00c2..ad1e4d6 100644 (file)
@@ -20,8 +20,13 @@ class DocumentCreateForm(forms.ModelForm):
 
     class Meta:
         model = Book
-        exclude = ['gallery', 'parent', 'parent_number']
-        prepopulated_fields = {'slug': ['title']}
+        exclude = ['parent', 'parent_number']
+
+    def __init__(self, *args, **kwargs):
+        super(DocumentCreateForm, self).__init__(*args, **kwargs)
+        self.fields['slug'].widget.attrs={'class': 'autoslug'}
+        self.fields['gallery'].widget.attrs={'class': 'autoslug'}
+        self.fields['title'].widget.attrs={'class': 'autoslug-source'}
 
     def clean(self):
         super(DocumentCreateForm, self).clean()
@@ -31,10 +36,10 @@ class DocumentCreateForm(forms.ModelForm):
             try:
                 self.cleaned_data['text'] = file.read().decode('utf-8')
             except UnicodeDecodeError:
-                raise forms.ValidationError("Text file must be UTF-8 encoded.")
+                raise forms.ValidationError(_("Text file must be UTF-8 encoded."))
 
         if not self.cleaned_data["text"]:
-            raise forms.ValidationError("You must either enter text or upload a file")
+            self._errors["file"] = self.error_class([_("You must either enter text or upload a file")])
 
         return self.cleaned_data
 
index 96e4b32..b05c6cf 100644 (file)
Binary files a/apps/catalogue/locale/pl/LC_MESSAGES/django.mo and b/apps/catalogue/locale/pl/LC_MESSAGES/django.mo differ
index 22708f4..293fd44 100644 (file)
@@ -7,8 +7,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Platforma Redakcyjna\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2011-10-21 15:53+0200\n"
-"PO-Revision-Date: 2011-10-21 15:53+0100\n"
+"POT-Creation-Date: 2011-10-24 13:25+0200\n"
+"PO-Revision-Date: 2011-10-24 13:28+0100\n"
 "Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
 "Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org.pl>\n"
 "Language: pl\n"
@@ -17,109 +17,122 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
 
-#: forms.py:46
+#: forms.py:39
+#, fuzzy
+msgid "Text file must be UTF-8 encoded."
+msgstr "Plik powinien mieć kodowanie UTF-8."
+
+#: forms.py:42
+msgid "You must either enter text or upload a file"
+msgstr "Proszę wpisać tekst albo wybrać plik do załadowania"
+
+#: forms.py:51
 msgid "ZIP file"
 msgstr "Plik ZIP"
 
-#: forms.py:47
+#: forms.py:52
 msgid "Directories are documents in chunks"
 msgstr "Katalogi zawierają dokumenty w częściach"
 
-#: forms.py:71
+#: forms.py:76
 msgid "Assigned to"
 msgstr "Przypisane do"
 
-#: forms.py:91
-#: forms.py:105
+#: forms.py:96
+#: forms.py:110
 msgid "Chunk with this slug already exists"
 msgstr "Część z tym slugiem już istnieje"
 
-#: forms.py:114
+#: forms.py:119
 msgid "Append to"
 msgstr "Dołącz do"
 
-#: views.py:148
+#: views.py:149
 #, python-format
 msgid "Slug already used for %s"
 msgstr "Slug taki sam jak dla pliku %s"
 
-#: views.py:150
+#: views.py:151
 msgid "Slug already used in repository."
 msgstr "Dokument o tym slugu już istnieje w repozytorium."
 
-#: views.py:156
+#: views.py:157
 msgid "File should be UTF-8 encoded."
 msgstr "Plik powinien mieć kodowanie UTF-8."
 
 #: models/book.py:25
-#: models/chunk.py:24
+#: models/chunk.py:23
 msgid "title"
 msgstr "tytuł"
 
 #: models/book.py:26
-#: models/chunk.py:25
+#: models/chunk.py:24
 msgid "slug"
 msgstr "slug"
 
 #: models/book.py:27
+msgid "public"
+msgstr "publiczna"
+
+#: models/book.py:28
 msgid "scan gallery name"
 msgstr "nazwa galerii skanów"
 
-#: models/book.py:30
+#: models/book.py:31
 msgid "parent"
 msgstr "rodzic"
 
-#: models/book.py:31
+#: models/book.py:32
 msgid "parent number"
 msgstr "numeracja rodzica"
 
-#: models/book.py:48
-#: models/chunk.py:22
+#: models/book.py:46
+#: models/chunk.py:21
 #: models/publish_log.py:17
 msgid "book"
 msgstr "książka"
 
-#: models/book.py:49
+#: models/book.py:47
 msgid "books"
 msgstr "książki"
 
-#: models/book.py:197
+#: models/book.py:198
 msgid "No chunks in the book."
 msgstr "Książka nie ma części."
 
-#: models/book.py:201
+#: models/book.py:202
 msgid "Not all chunks have publishable revisions."
 msgstr "Niektóre części nie są gotowe do publikacji."
 
-#: models/book.py:207
+#: models/book.py:208
 msgid "Invalid XML"
 msgstr "Nieprawidłowy XML"
 
-#: models/book.py:209
+#: models/book.py:210
 msgid "No Dublin Core found."
 msgstr "Brak sekcji Dublin Core."
 
-#: models/book.py:211
+#: models/book.py:212
 msgid "Invalid Dublin Core"
 msgstr "Nieprawidłowy Dublin Core"
 
-#: models/book.py:214
+#: models/book.py:215
 msgid "rdf:about is not"
 msgstr "rdf:about jest różny od"
 
-#: models/chunk.py:23
+#: models/chunk.py:22
 msgid "number"
 msgstr "numer"
 
-#: models/chunk.py:26
+#: models/chunk.py:25
 msgid "gallery start"
 msgstr "początek galerii"
 
-#: models/chunk.py:41
+#: models/chunk.py:40
 msgid "chunk"
 msgstr "część"
 
-#: models/chunk.py:42
+#: models/chunk.py:41
 msgid "chunks"
 msgstr "części"
 
@@ -234,9 +247,13 @@ msgstr "Ustawienia części"
 msgid "Book"
 msgstr "Książka"
 
-#: templates/catalogue/document_create_missing.html:9
-msgid "Create document"
-msgstr "Utwórz dokument"
+#: templates/catalogue/document_create_missing.html:5
+msgid "Create a new book"
+msgstr "Utwórz nową książkę"
+
+#: templates/catalogue/document_create_missing.html:11
+msgid "Create book"
+msgstr "Utwórz książkę"
 
 #: templates/catalogue/document_upload.html:8
 msgid "Bulk documents upload"
diff --git a/apps/catalogue/migrations/0006_auto__add_field_book_public.py b/apps/catalogue/migrations/0006_auto__add_field_book_public.py
new file mode 100644 (file)
index 0000000..fd1cea5
--- /dev/null
@@ -0,0 +1,126 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'Book.public'
+        db.add_column('catalogue_book', 'public', self.gf('django.db.models.fields.BooleanField')(default=True, db_index=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'Book.public'
+        db.delete_column('catalogue_book', 'public')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'catalogue.book': {
+            'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'},
+            '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
+        },
+        'catalogue.bookpublishrecord': {
+            'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'catalogue.chunk': {
+            'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
+            '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
+            'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}),
+            'gallery_start': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'number': ('django.db.models.fields.IntegerField', [], {}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
+            'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'catalogue.chunkchange': {
+            'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+            'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
+            'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
+            'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
+        },
+        'catalogue.chunkpublishrecord': {
+            'Meta': {'object_name': 'ChunkPublishRecord'},
+            'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
+            'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'catalogue.chunktag': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {}),
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
index 5125532..94a9833 100755 (executable)
@@ -24,6 +24,7 @@ class Book(models.Model):
 
     title = models.CharField(_('title'), max_length=255, db_index=True)
     slug = models.SlugField(_('slug'), max_length=128, unique=True, db_index=True)
+    public = models.BooleanField(_('public'), default=True, db_index=True)
     gallery = models.CharField(_('scan gallery name'), max_length=255, blank=True)
 
     #wl_slug = models.CharField(_('title'), max_length=255, null=True, db_index=True, editable=False)
@@ -36,9 +37,6 @@ class Book(models.Model):
     _new_publishable = models.NullBooleanField(editable=False)
     _published = models.NullBooleanField(editable=False)
 
-    # Managers
-    objects = models.Manager()
-
     class NoTextError(BaseException):
         pass
 
@@ -78,6 +76,9 @@ class Book(models.Model):
     # Creating & manipulating
     # =======================
 
+    def accessible(self, request):
+        return self.public or request.user.is_authenticated()
+
     @classmethod
     def create(cls, creator, text, *args, **kwargs):
         b = cls.objects.create(*args, **kwargs)
index 9b1dcba..770ddbf 100755 (executable)
@@ -11,7 +11,6 @@ from django.utils.translation import ugettext_lazy as _
 from catalogue.helpers import cached_in_field
 from catalogue.managers import VisibleManager
 from catalogue.tasks import refresh_instance
-from catalogue.xml_tools import GradedText
 from dvcs import models as dvcs_models
 
 
@@ -124,6 +123,3 @@ class Chunk(dvcs_models.Document):
         self.changed
         self.hidden
         self.short_html
-
-    def graded(self, master=None):
-        return GradedText(self.materialize(), master)
index 3e23037..53b867b 100644 (file)
@@ -1,20 +1,42 @@
 from celery.task import task
+from django.utils import translation
+from django.conf import settings
 
 
 @task
-def refresh_by_pk(cls, pk):
-    cls._default_manager.get(pk=pk).refresh()
-
+def _refresh_by_pk(cls, pk, language=None):
+    prev_language = translation.get_language()
+    language and translation.activate(language)
+    try:
+        cls._default_manager.get(pk=pk).refresh()
+    finally:
+        translation.activate(prev_language)
 
-def refresh_instance(instance):
-    refresh_by_pk.delay(type(instance), instance.pk)
+if settings.USE_CELERY:
+    def refresh_instance(instance):
+        _refresh_by_pk.delay(type(instance), instance.pk, translation.get_language())
+else:
+    def refresh_instance(instance):
+        instance.refresh()
 
 
 @task
-def publishable_error(book):
+def _publishable_error(book, language=None):
+    prev_language = translation.get_language()
+    language and translation.activate(language)
     try:
-        book.assert_publishable()
+        return book.assert_publishable()
     except AssertionError, e:
         return e
     else:
        return None
+    finally:
+        translation.activate(prev_language)
+
+if settings.USE_CELERY:
+    def publishable_error(book):
+        task = _publishable_error.delay(book, translation.get_language())
+        return task.wait()
+else:
+    def publishable_error(book):
+        return _publishable_error(book)
index dcb6175..47c99f9 100644 (file)
@@ -1,14 +1,14 @@
 {% extends "catalogue/base.html" %}
 {% load i18n %}
 
-{% block leftcolumn %}
-       <form enctype="multipart/form-data" method="POST" action="">
-    {% csrf_token %}
-       {{ form.as_p }}
-
-       <p><button type="submit">{% trans "Create document" %}</button></p>
-       </form>
-{% endblock leftcolumn %}
+{% block content %}
+    <h1>{% trans "Create a new book" %}</h1>
 
-{% block rightcolumn %}
-{% endblock rightcolumn %}
+    <form enctype="multipart/form-data" method="POST">
+    {% csrf_token %}
+    <table class='editable'>
+        {{ form.as_table}}
+        <tr><td></td><td><button type="submit">{% trans "Create book" %}</button></td></tr>
+    </table>
+    </form>
+{% endblock content %}
index aee3321..f7e7047 100755 (executable)
@@ -103,6 +103,9 @@ def document_list_filter(request, **kwargs):
 
     chunks = chunks.order_by('book__title', 'book', 'number')
 
+    if not request.user.is_authenticated():
+        chunks = chunks.filter(book__public=True)
+
     state = arg_or_GET('status')
     if state in _states_dict:
         chunks = chunks.filter(_states_dict[state])
index 6fb5f60..3459fda 100644 (file)
@@ -11,7 +11,7 @@ from django.contrib.auth.decorators import login_required, permission_required
 from django.core.urlresolvers import reverse
 from django.db.models import Count, Q
 from django import http
-from django.http import Http404
+from django.http import Http404, HttpResponseForbidden
 from django.shortcuts import get_object_or_404, render
 from django.utils.encoding import iri_to_uri
 from django.utils.http import urlquote_plus
@@ -28,7 +28,6 @@ from catalogue import helpers
 from catalogue.helpers import active_tab
 from catalogue.models import Book, Chunk, BookPublishRecord, ChunkPublishRecord
 from catalogue.tasks import publishable_error
-from catalogue import xml_tools
 
 #
 # Quick hack around caching problems, TODO: use ETags
@@ -101,13 +100,15 @@ def create_missing(request, slug=None):
                 creator=creator,
                 slug=form.cleaned_data['slug'],
                 title=form.cleaned_data['title'],
+                gallery=form.cleaned_data['gallery'],
             )
 
-            return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
+            return http.HttpResponseRedirect(reverse("catalogue_book", args=[book.slug]))
     else:
         form = forms.DocumentCreateForm(initial={
                 "slug": slug,
                 "title": slug.replace('-', ' ').title(),
+                "gallery": slug,
         })
 
     return direct_to_template(request, "catalogue/document_create_missing.html", extra_context={
@@ -185,7 +186,10 @@ def upload(request):
 
 @never_cache
 def book_xml(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
+    book = get_object_or_404(Book, slug=slug)
+    if not book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+    xml = book.materialize()
 
     response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
     response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
@@ -194,7 +198,10 @@ def book_xml(request, slug):
 
 @never_cache
 def book_txt(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
+    book = get_object_or_404(Book, slug=slug)
+    if not book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+    xml = book.materialize()
     output = StringIO()
     # errors?
     librarian.text.transform(StringIO(xml), output)
@@ -206,7 +213,10 @@ def book_txt(request, slug):
 
 @never_cache
 def book_html(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
+    book = get_object_or_404(Book, slug=slug)
+    if not book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+    xml = book.materialize()
     output = StringIO()
     # errors?
     librarian.html.transform(StringIO(xml), output, parse_dublincore=False,
@@ -215,18 +225,21 @@ def book_html(request, slug):
     response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
     return response
 
-
 @never_cache
 def revision(request, slug, chunk=None):
     try:
         doc = Chunk.get(slug, chunk)
     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
         raise Http404
+    if not doc.book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
     return http.HttpResponse(str(doc.revision()))
 
 
 def book(request, slug):
     book = get_object_or_404(Book, slug=slug)
+    if not book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
 
     if request.user.has_perm('catalogue.change_book'):
         if request.method == "POST":
@@ -241,8 +254,7 @@ def book(request, slug):
         form = forms.ReadonlyBookForm(instance=book)
         editable = False
 
-    task = publishable_error.delay(book)
-    publish_error = task.wait()
+    publish_error = publishable_error(book)
     publishable = publish_error is None
 
     return direct_to_template(request, "catalogue/book_detail.html", extra_context={
@@ -260,6 +272,8 @@ def chunk_add(request, slug, chunk):
         doc = Chunk.get(slug, chunk)
     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
         raise Http404
+    if not doc.book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
 
     if request.method == "POST":
         form = forms.ChunkAddForm(request.POST, instance=doc)
@@ -291,6 +305,9 @@ def chunk_edit(request, slug, chunk):
         doc = Chunk.get(slug, chunk)
     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
         raise Http404
+    if not doc.book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
     if request.method == "POST":
         form = forms.ChunkForm(request.POST, instance=doc)
         if form.is_valid():
@@ -318,6 +335,9 @@ def chunk_edit(request, slug, chunk):
 @permission_required('catalogue.change_book')
 def book_append(request, slug):
     book = get_object_or_404(Book, slug=slug)
+    if not book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
     if request.method == "POST":
         form = forms.BookAppendForm(book, request.POST)
         if form.is_valid():
@@ -338,6 +358,9 @@ def book_append(request, slug):
 @login_required
 def publish(request, slug):
     book = get_object_or_404(Book, slug=slug)
+    if not book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
     try:
         book.publish(request.user)
     except NotAuthorizedError:
index d6a9333..242714b 100644 (file)
@@ -1,6 +1,5 @@
 # -*- coding: utf-8 -*-
 from copy import deepcopy
-from functools import wraps
 import re
 
 from lxml import etree
@@ -14,110 +13,6 @@ class ParseError(BaseException):
     pass
 
 
-def obj_memoized(f):
-    """
-        A decorator that caches return value of object methods.
-        The cache is kept with the object, in a _obj_memoized property.
-    """
-    @wraps(f)
-    def wrapper(self, *args, **kwargs):
-        if not hasattr(self, '_obj_memoized'):
-            self._obj_memoized = {}
-        key = (f.__name__,) + args + tuple(sorted(kwargs.iteritems()))
-        try:
-            return self._obj_memoized[key]
-        except TypeError:
-            return f(self, *args, **kwargs)
-        except KeyError:
-            self._obj_memoized[key] = f(self, *args, **kwargs)
-            return self._obj_memoized[key]
-    return wrapper
-
-
-class GradedText(object):
-    _edoc = None
-
-    ROOT = 'utwor'
-    RDF = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}RDF'
-
-    def __init__(self, text):
-        self._text = text
-
-    @obj_memoized
-    def is_xml(self):
-        """
-            Determines if it's a well-formed XML.
-
-            >>> GradedText("<a/>").is_xml()
-            True
-            >>> GradedText("<a>").is_xml()
-            False
-        """
-        try:
-            self._edoc = etree.fromstring(self._text)
-        except etree.XMLSyntaxError:
-            return False
-        return True
-
-    @obj_memoized
-    def is_wl(self):
-        """
-            Determines if it's an XML with a <utwor> and a master tag.
-
-            >>> GradedText("<utwor><powiesc></powiesc></utwor>").is_wl()
-            True
-            >>> GradedText("<a></a>").is_wl()
-            False
-        """
-        if self.is_xml():
-            e = self._edoc
-            # FIXME: there could be comments
-            ret = e.tag == self.ROOT and (
-                len(e) == 1 and e[0].tag in MASTERS or
-                len(e) == 2 and e[0].tag == self.RDF 
-                    and e[1].tag in MASTERS)
-            if ret:
-                self._master = e[-1].tag
-            del self._edoc
-            return ret
-        else:
-            return False
-
-    @obj_memoized
-    def is_broken_wl(self):
-        """
-            Determines if it at least looks like broken WL file
-            and not just some untagged text.
-
-            >>> GradedText("<utwor><</utwor>").is_broken_wl()
-            True
-            >>> GradedText("some text").is_broken_wl()
-            False
-        """
-        if self.is_wl():
-            return True
-        text = self._text.strip()
-        return text.startswith('<utwor>') and text.endswith('</utwor>')
-
-    def master(self):
-        """
-            Gets the master tag.
-
-            >>> GradedText("<utwor><powiesc></powiesc></utwor>").master()
-            'powiesc'
-        """
-        assert self.is_wl()
-        return self._master
-
-    @obj_memoized
-    def has_trim_begin(self):
-        return RE_TRIM_BEGIN.search(self._text)
-
-    @obj_memoized
-    def has_trim_end(self):
-        return RE_TRIM_END.search(self._text)
-
-
 def _trim(text, trim_begin=True, trim_end=True):
     """ 
         Cut off everything before RE_TRIM_BEGIN and after RE_TRIM_END, so
@@ -153,26 +48,6 @@ def compile_text(parts):
     return "".join(texts)
 
 
-def change_master(text, master):
-    """
-        Changes the master tag in a WL document.
-    """
-    e = etree.fromstring(text)
-    e[-1].tag = master
-    return unicode(etree.tostring(e, encoding="utf-8"), 'utf-8')
-
-
-def basic_structure(text, master):
-    e = etree.fromstring('''<utwor>
-<master>
-<!--%s--><!--%s-->
-</master>
-</utwor>''' % (TRIM_BEGIN, TRIM_END))
-    e[0].tag = master
-    e[0][0].tail = "\n"*3 + text + "\n"*3
-    return unicode(etree.tostring(e, encoding="utf-8"), 'utf-8')
-
-
 def add_trim_begin(text):
     trim_tag = etree.Comment(TRIM_BEGIN)
     e = etree.fromstring(text)
index b137a54..4d9fac9 100644 (file)
@@ -5,7 +5,7 @@ import logging
 from django.conf import settings
 from django.core.urlresolvers import reverse
 from django import http
-from django.http import Http404
+from django.http import Http404, HttpResponseForbidden
 from django.middleware.gzip import GZipMiddleware
 from django.utils.decorators import decorator_from_middleware
 from django.utils.encoding import smart_unicode
@@ -46,6 +46,8 @@ def editor(request, slug, chunk=None, template_name='wiki/document_details.html'
                 return http.HttpResponseRedirect(reverse("catalogue_create_missing", args=[slug]))
         else:
             raise Http404
+    if not chunk.book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
 
     access_time = datetime.now()
     last_books = request.session.get("wiki_last_books", {})
@@ -77,6 +79,8 @@ def editor_readonly(request, slug, chunk=None, template_name='wiki/document_deta
         revision = request.GET['revision']
     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist, KeyError):
         raise Http404
+    if not chunk.book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
 
     access_time = datetime.now()
     last_books = request.session.get("wiki_last_books", {})
@@ -102,6 +106,8 @@ def editor_readonly(request, slug, chunk=None, template_name='wiki/document_deta
 @decorator_from_middleware(GZipMiddleware)
 def text(request, chunk_id):
     doc = get_object_or_404(Chunk, pk=chunk_id)
+    if not doc.book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
 
     if request.method == 'POST':
         form = forms.DocumentTextSaveForm(request.POST, user=request.user, prefix="textsave")
@@ -160,6 +166,8 @@ def revert(request, chunk_id):
     form = forms.DocumentTextRevertForm(request.POST, prefix="textrevert")
     if form.is_valid():
         doc = get_object_or_404(Chunk, pk=chunk_id)
+        if not doc.book.accessible(request):
+            return HttpResponseForbidden("Not authorized.")
 
         revision = form.cleaned_data['revision']
 
@@ -205,6 +213,10 @@ def gallery(request, directory):
 
         images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)]
         images.sort()
+
+        if not request.user.is_authenticated():
+            return HttpResponseForbidden("Not authorized.")
+
         return JSONResponse(images)
     except (IndexError, OSError):
         logger.exception("Unable to fetch gallery")
@@ -223,6 +235,9 @@ def diff(request, chunk_id):
         revB = None
 
     doc = get_object_or_404(Chunk, pk=chunk_id)
+    if not doc.book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
+
     # allow diff from the beginning
     if revA:
         docA = doc.at_revision(revA).materialize()
@@ -237,6 +252,8 @@ def diff(request, chunk_id):
 @never_cache
 def revision(request, chunk_id):
     doc = get_object_or_404(Chunk, pk=chunk_id)
+    if not doc.book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
     return http.HttpResponse(str(doc.revision()))
 
 
@@ -244,6 +261,8 @@ def revision(request, chunk_id):
 def history(request, chunk_id):
     # TODO: pagination
     doc = get_object_or_404(Chunk, pk=chunk_id)
+    if not doc.book.accessible(request):
+        return HttpResponseForbidden("Not authorized.")
 
     changes = []
     for change in doc.history().order_by('-created_at'):
@@ -264,6 +283,8 @@ def pubmark(request, chunk_id):
     form = forms.DocumentPubmarkForm(request.POST, prefix="pubmark")
     if form.is_valid():
         doc = get_object_or_404(Chunk, pk=chunk_id)
+        if not doc.book.accessible(request):
+            return HttpResponseForbidden("Not authorized.")
 
         revision = form.cleaned_data['revision']
         publishable = form.cleaned_data['publishable']
index c46e192..b2f2b5c 100644 (file)
@@ -30,3 +30,5 @@ COMPRESS = False
 
 APICLIENT_WL_CONSUMER_KEY = None
 APICLIENT_WL_CONSUMER_SECRET = None
+
+USE_CELERY = True
index 4762257..5fbd59f 100644 (file)
@@ -20,6 +20,7 @@ DATABASES = {
 import tempfile
 
 CATALOGUE_REPO_PATH = tempfile.mkdtemp(prefix='redakcja-repo')
+USE_CELERY = False
 
 INSTALLED_APPS += ('django_nose', 'dvcs.tests')
 
index 2319894..4aead73 100644 (file)
@@ -63,8 +63,8 @@ td {
 .editable td {
     padding: 1px;
 }
-.editable input, .editable select {
-    width: 30em;
+.editable input, .editable select, .editable textarea {
+    width: 400px;
 }
 
 #login-box {
index 4949f6b..1234c98 100644 (file)
                                self.galleryImages = data;
                                params['success'](self, data);
                        },
-                       error: function() {
+                       error: function(xhr) {
+                switch (xhr.status) {
+                    case 403:
+                        var msg = 'Galerie dostępne tylko dla zalogowanych użytkowników.';
+                        break;
+                    case 404:
+                        var msg = "Nie znaleziono galerii o nazwie: '" + self.galleryLink + "'.";
+                    default:
+                        var msg = "Nie udało się wczytać galerii o nazwie: '" + self.galleryLink + "'.";
+                }
                                self.galleryImages = [];
-                               params['failure'](self, "<p>Nie udało się wczytać galerii pod nazwą: '" + self.galleryLink + "'.</p>");
+                               params['failure'](self, "<p>" + msg + "</p>");
                        }
                });
        };