#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
 
     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()
 
     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:
             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"]:
 
         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
 
 
         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"
 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"
 "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"
 
 "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"
 
 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"
 
 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"
 
 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"
 
 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"
 
 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"
 
 #, 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."
 
 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
 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
 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 "slug"
 msgstr "slug"
 
 #: models/book.py:27
+msgid "public"
+msgstr "publiczna"
+
+#: models/book.py:28
 msgid "scan gallery name"
 msgstr "nazwa galerii skanów"
 
 msgid "scan gallery name"
 msgstr "nazwa galerii skanów"
 
-#: models/book.py:30
+#: models/book.py:31
 msgid "parent"
 msgstr "rodzic"
 
 msgid "parent"
 msgstr "rodzic"
 
-#: models/book.py:31
+#: models/book.py:32
 msgid "parent number"
 msgstr "numeracja rodzica"
 
 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/publish_log.py:17
 msgid "book"
 msgstr "książka"
 
-#: models/book.py:49
+#: models/book.py:47
 msgid "books"
 msgstr "książki"
 
 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."
 
 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."
 
 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"
 
 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."
 
 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"
 
 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"
 
 msgid "rdf:about is not"
 msgstr "rdf:about jest różny od"
 
-#: models/chunk.py:23
+#: models/chunk.py:22
 msgid "number"
 msgstr "numer"
 
 msgid "number"
 msgstr "numer"
 
-#: models/chunk.py:26
+#: models/chunk.py:25
 msgid "gallery start"
 msgstr "początek galerii"
 
 msgid "gallery start"
 msgstr "początek galerii"
 
-#: models/chunk.py:41
+#: models/chunk.py:40
 msgid "chunk"
 msgstr "część"
 
 msgid "chunk"
 msgstr "część"
 
-#: models/chunk.py:42
+#: models/chunk.py:41
 msgid "chunks"
 msgstr "części"
 
 msgid "chunks"
 msgstr "części"
 
@@ -234,9 +247,13 @@ msgstr "Ustawienia części"
 msgid "Book"
 msgstr "Książka"
 
 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"
 
 #: 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)
 
     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)
     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)
 
     _new_publishable = models.NullBooleanField(editable=False)
     _published = models.NullBooleanField(editable=False)
 
-    # Managers
-    objects = models.Manager()
-
     class NoTextError(BaseException):
         pass
 
     class NoTextError(BaseException):
         pass
 
@@ -78,6 +76,9 @@ class Book(models.Model):
     # Creating & manipulating
     # =======================
 
     # 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)
     @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.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
 
 
 from dvcs import models as dvcs_models
 
 
@@ -124,6 +123,3 @@ class Chunk(dvcs_models.Document):
         self.changed
         self.hidden
         self.short_html
         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 celery.task import task
+from django.utils import translation
+from django.conf import settings
 
 
 @task
 
 
 @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
 
 
 @task
-def publishable_error(book):
+def _publishable_error(book, language=None):
+    prev_language = translation.get_language()
+    language and translation.activate(language)
     try:
     try:
-        book.assert_publishable()
+        return book.assert_publishable()
     except AssertionError, e:
         return e
     else:
        return None
     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 %}
 
 {% 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')
 
 
     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])
     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.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
 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.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
 
 #
 # 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'],
                 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(),
     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={
         })
 
     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):
 
 @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
 
     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):
 
 @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)
     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):
 
 @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,
     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
 
     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
 @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)
     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":
 
     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
 
         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={
     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
         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)
 
     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
         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():
     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)
 @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():
     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)
 @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:
     try:
         book.publish(request.user)
     except NotAuthorizedError:
index d6a9333..242714b 100644 (file)
@@ -1,6 +1,5 @@
 # -*- coding: utf-8 -*-
 from copy import deepcopy
 # -*- coding: utf-8 -*-
 from copy import deepcopy
-from functools import wraps
 import re
 
 from lxml import etree
 import re
 
 from lxml import etree
@@ -14,110 +13,6 @@ class ParseError(BaseException):
     pass
 
 
     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
 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)
 
 
     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)
 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.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
 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
                 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", {})
 
     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
         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", {})
 
     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)
 @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")
 
     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)
     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']
 
 
         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()
 
         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")
         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)
         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()
     # 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)
 @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()))
 
 
     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)
 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'):
 
     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)
     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']
 
         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
 
 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')
 import tempfile
 
 CATALOGUE_REPO_PATH = tempfile.mkdtemp(prefix='redakcja-repo')
+USE_CELERY = False
 
 INSTALLED_APPS += ('django_nose', 'dvcs.tests')
 
 
 INSTALLED_APPS += ('django_nose', 'dvcs.tests')
 
index 2319894..4aead73 100644 (file)
@@ -63,8 +63,8 @@ td {
 .editable td {
     padding: 1px;
 }
 .editable td {
     padding: 1px;
 }
-.editable input, .editable select {
-    width: 30em;
+.editable input, .editable select, .editable textarea {
+    width: 400px;
 }
 
 #login-box {
 }
 
 #login-box {
index 4949f6b..1234c98 100644 (file)
                                self.galleryImages = data;
                                params['success'](self, data);
                        },
                                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 = [];
                                self.galleryImages = [];
-                               params['failure'](self, "<p>Nie udało się wczytać galerii pod nazwą: '" + self.galleryLink + "'.</p>");
+                               params['failure'](self, "<p>" + msg + "</p>");
                        }
                });
        };
                        }
                });
        };