refactor catalogue to separate app,
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Mon, 4 Jul 2011 16:08:20 +0000 (18:08 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Mon, 4 Jul 2011 16:08:20 +0000 (18:08 +0200)
some minor fixes

68 files changed:
apps/catalogue/__init__.py [new file with mode: 0644]
apps/catalogue/admin.py [new file with mode: 0644]
apps/catalogue/constants.py [new file with mode: 0644]
apps/catalogue/fixtures/stages.json [new file with mode: 0644]
apps/catalogue/forms.py [new file with mode: 0644]
apps/catalogue/helpers.py [new file with mode: 0644]
apps/catalogue/locale/pl/LC_MESSAGES/django.mo [new file with mode: 0644]
apps/catalogue/locale/pl/LC_MESSAGES/django.po [new file with mode: 0644]
apps/catalogue/management/__init__.py [new file with mode: 0755]
apps/catalogue/management/commands/__init__.py [new file with mode: 0755]
apps/catalogue/management/commands/assign_from_redmine.py [new file with mode: 0755]
apps/catalogue/migrations/0001_initial.py [new file with mode: 0644]
apps/catalogue/migrations/__init__.py [new file with mode: 0644]
apps/catalogue/models.py [new file with mode: 0644]
apps/catalogue/templates/catalogue/base.html [new file with mode: 0644]
apps/catalogue/templates/catalogue/book_append_to.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/book_detail.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/book_edit.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/chunk_add.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/chunk_edit.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/document_create_missing.html [new file with mode: 0644]
apps/catalogue/templates/catalogue/document_list.html [new file with mode: 0644]
apps/catalogue/templates/catalogue/document_upload.html [new file with mode: 0644]
apps/catalogue/templates/catalogue/main_tabs.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/user_list.html [new file with mode: 0755]
apps/catalogue/templates/catalogue/wall.html [new file with mode: 0755]
apps/catalogue/templatetags/__init__.py [new file with mode: 0644]
apps/catalogue/templatetags/catalogue.py [new file with mode: 0644]
apps/catalogue/tests.py [new file with mode: 0644]
apps/catalogue/urls.py [new file with mode: 0644]
apps/catalogue/views.py [new file with mode: 0644]
apps/catalogue/xml_tools.py [new file with mode: 0755]
apps/wiki/admin.py
apps/wiki/constants.py [deleted file]
apps/wiki/fixtures/stages.json [deleted file]
apps/wiki/forms.py
apps/wiki/helpers.py
apps/wiki/management/__init__.py [deleted file]
apps/wiki/management/commands/__init__.py [deleted file]
apps/wiki/management/commands/assign_from_redmine.py [deleted file]
apps/wiki/migrations/0003_add_dvcs.py [deleted file]
apps/wiki/models.py
apps/wiki/templates/wiki/base.html [deleted file]
apps/wiki/templates/wiki/book_append_to.html [deleted file]
apps/wiki/templates/wiki/book_detail.html [deleted file]
apps/wiki/templates/wiki/book_edit.html [deleted file]
apps/wiki/templates/wiki/chunk_add.html [deleted file]
apps/wiki/templates/wiki/chunk_edit.html [deleted file]
apps/wiki/templates/wiki/document_create_missing.html [deleted file]
apps/wiki/templates/wiki/document_details_base.html
apps/wiki/templates/wiki/document_list.html [deleted file]
apps/wiki/templates/wiki/document_upload.html [deleted file]
apps/wiki/templates/wiki/main_tabs.html [deleted file]
apps/wiki/templates/wiki/tag_dialog.html [deleted file]
apps/wiki/templates/wiki/user_list.html [deleted file]
apps/wiki/templates/wiki/wall.html [deleted file]
apps/wiki/templatetags/__init__.py [deleted file]
apps/wiki/templatetags/wiki.py [deleted file]
apps/wiki/urls.py
apps/wiki/views.py
apps/wiki/xml_tools.py [deleted file]
redakcja/settings/common.py
redakcja/settings/compress.py
redakcja/static/js/wiki/dialog_addtag.js [deleted file]
redakcja/static/js/wiki/view_history.js
redakcja/static/js/wiki/wikiapi.js
redakcja/static/js/wiki/xslt.js
redakcja/urls.py

diff --git a/apps/catalogue/__init__.py b/apps/catalogue/__init__.py
new file mode 100644 (file)
index 0000000..c53f0e7
--- /dev/null
@@ -0,0 +1 @@
+  # pragma: no cover
diff --git a/apps/catalogue/admin.py b/apps/catalogue/admin.py
new file mode 100644 (file)
index 0000000..70e20d2
--- /dev/null
@@ -0,0 +1,12 @@
+from django.contrib import admin
+
+from catalogue import models
+
+class BookAdmin(admin.ModelAdmin):
+    prepopulated_fields = {'slug': ['title']}
+
+
+admin.site.register(models.Book, BookAdmin)
+admin.site.register(models.Chunk)
+
+admin.site.register(models.Chunk.tag_model)
diff --git a/apps/catalogue/constants.py b/apps/catalogue/constants.py
new file mode 100644 (file)
index 0000000..d75d6b4
--- /dev/null
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+
+TRIM_BEGIN = " TRIM_BEGIN "
+TRIM_END = " TRIM_END "
+
+MASTERS = ['powiesc',
+           'opowiadanie',
+           'liryka_l',
+           'liryka_lp',
+           'dramat_wierszowany_l',
+           'dramat_wierszowany_lp',
+           'dramat_wspolczesny',
+           ]
diff --git a/apps/catalogue/fixtures/stages.json b/apps/catalogue/fixtures/stages.json
new file mode 100644 (file)
index 0000000..5a46ec0
--- /dev/null
@@ -0,0 +1,83 @@
+[
+    {
+        "pk": 1, 
+        "model": "catalogue.chunktag", 
+        "fields": {
+            "ordering": 1, 
+            "name": "Autokorekta", 
+            "slug": "first_correction"
+        }
+    }, 
+    {
+        "pk": 2, 
+        "model": "catalogue.chunktag", 
+        "fields": {
+            "ordering": 2, 
+            "name": "Tagowanie", 
+            "slug": "tagging"
+        }
+    }, 
+    {
+        "pk": 3, 
+        "model": "catalogue.chunktag", 
+        "fields": {
+            "ordering": 3, 
+            "name": "Korekta", 
+            "slug": "proofreading"
+        }
+    }, 
+    {
+        "pk": 4, 
+        "model": "catalogue.chunktag", 
+        "fields": {
+            "ordering": 4, 
+            "name": "Sprawdzenie przypis\u00f3w \u017ar\u00f3d\u0142a", 
+            "slug": "annotation-proofreading"
+        }
+    }, 
+    {
+        "pk": 5, 
+        "model": "catalogue.chunktag", 
+        "fields": {
+            "ordering": 5, 
+            "name": "Uwsp\u00f3\u0142cze\u015bnienie", 
+            "slug": "modernisation"
+        }
+    }, 
+    {
+        "pk": 6, 
+        "model": "catalogue.chunktag", 
+        "fields": {
+            "ordering": 6, 
+            "name": "Przypisy", 
+            "slug": "annotations"
+        }
+    }, 
+    {
+        "pk": 7, 
+        "model": "catalogue.chunktag", 
+        "fields": {
+            "ordering": 7, 
+            "name": "Motywy", 
+            "slug": "themes"
+        }
+    }, 
+    {
+        "pk": 8, 
+        "model": "catalogue.chunktag", 
+        "fields": {
+            "ordering": 8, 
+            "name": "Ostateczna redakcja literacka", 
+            "slug": "editor-proofreading"
+        }
+    }, 
+    {
+        "pk": 9, 
+        "model": "catalogue.chunktag", 
+        "fields": {
+            "ordering": 9, 
+            "name": "Ostateczna redakcja techniczna", 
+            "slug": "technical-editor-proofreading"
+        }
+    }
+]
diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py
new file mode 100644 (file)
index 0000000..33ccbe6
--- /dev/null
@@ -0,0 +1,124 @@
+# -*- 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 django.contrib.auth.models import User
+from django.db.models import Count
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from catalogue.constants import MASTERS
+from catalogue.models import Book, Chunk
+
+class DocumentCreateForm(forms.ModelForm):
+    """
+        Form used for creating new documents.
+    """
+    file = forms.FileField(required=False)
+    text = forms.CharField(required=False, widget=forms.Textarea)
+
+    class Meta:
+        model = Book
+        exclude = ['gallery', 'parent', 'parent_number']
+        prepopulated_fields = {'slug': ['title']}
+
+    def clean(self):
+        super(DocumentCreateForm, self).clean()
+        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.")
+
+        if not self.cleaned_data["text"]:
+            raise forms.ValidationError("You must either enter text or upload a file")
+
+        return self.cleaned_data
+
+
+class DocumentsUploadForm(forms.Form):
+    """
+        Form used for uploading new documents.
+    """
+    file = forms.FileField(required=True, label=_('ZIP file'))
+
+    def clean(self):
+        file = self.cleaned_data['file']
+
+        import zipfile
+        try:
+            z = self.cleaned_data['zip'] = zipfile.ZipFile(file)
+        except zipfile.BadZipfile:
+            raise forms.ValidationError("Should be a ZIP file.")
+        if z.testzip():
+            raise forms.ValidationError("ZIP file corrupt.")
+
+        return self.cleaned_data
+
+
+class ChunkForm(forms.ModelForm):
+    """
+        Form used for editing a chunk.
+    """
+    user = forms.ModelChoiceField(queryset=
+        User.objects.annotate(count=Count('chunk')).
+        order_by('-count', 'last_name', 'first_name'))
+
+
+    class Meta:
+        model = Chunk
+        exclude = ['number']
+
+    def clean_slug(self):
+        slug = self.cleaned_data['slug']
+        try:
+            chunk = Chunk.objects.get(book=self.instance.book, slug=slug)
+        except Chunk.DoesNotExist:
+            return slug
+        if chunk == self.instance:
+            return slug
+        raise forms.ValidationError(_('Chunk with this slug already exists'))
+
+
+class ChunkAddForm(ChunkForm):
+    """
+        Form used for adding a chunk to a document.
+    """
+
+    def clean_slug(self):
+        slug = self.cleaned_data['slug']
+        try:
+            user = Chunk.objects.get(book=self.instance.book, slug=slug)
+        except Chunk.DoesNotExist:
+            return slug
+        raise forms.ValidationError(_('Chunk with this slug already exists'))
+
+
+class BookAppendForm(forms.Form):
+    """
+        Form for appending a book to another book.
+        It means moving all chunks from book A to book B and deleting A.
+    """
+
+    append_to = forms.ModelChoiceField(queryset=Book.objects.all(),
+        label=_("Append to"))
+
+
+class BookForm(forms.ModelForm):
+    """
+        Form used for editing a Book.
+    """
+
+    class Meta:
+        model = Book
+
+
+class ChooseMasterForm(forms.Form):
+    """
+        Form used for fixing the chunks in a book.
+    """
+
+    master = forms.ChoiceField(choices=((m, m) for m in MASTERS))
diff --git a/apps/catalogue/helpers.py b/apps/catalogue/helpers.py
new file mode 100644 (file)
index 0000000..c9dc0bd
--- /dev/null
@@ -0,0 +1,65 @@
+from functools import wraps
+
+from django.db.models import Count
+
+
+def active_tab(tab):
+    """
+        View decorator, which puts tab info on a request.
+    """
+    def wrapper(f):
+        @wraps(f)
+        def wrapped(request, *args, **kwargs):
+            request.catalogue_active_tab = tab
+            return f(request, *args, **kwargs)
+        return wrapped
+    return wrapper
+
+
+class ChunksList(object):
+    def __init__(self, chunk_qs):
+        self.chunk_qs = chunk_qs.annotate(
+            book_length=Count('book__chunk')).select_related(
+            'book', 'stage__name',
+            'user')
+
+        self.book_qs = chunk_qs.values('book_id')
+
+    def __getitem__(self, key):
+        if isinstance(key, slice):
+            return self.get_slice(key)
+        elif isinstance(key, int):
+            return self.get_slice(slice(key, key+1))[0]
+        else:
+            raise TypeError('Unsupported list index. Must be a slice or an int.')
+
+    def __len__(self):
+        return self.book_qs.count()
+
+    def get_slice(self, slice_):
+        book_ids = [x['book_id'] for x in self.book_qs[slice_]]
+        chunk_qs = self.chunk_qs.filter(book__in=book_ids)
+
+        chunks_list = []
+        book = None
+        for chunk in chunk_qs:
+            if chunk.book != book:
+                book = chunk.book
+                chunks_list.append(ChoiceChunks(book, [chunk], chunk.book_length))
+            else:
+                chunks_list[-1].chunks.append(chunk)
+        return chunks_list
+
+
+class ChoiceChunks(object):
+    """
+        Associates the given chunks iterable for a book.
+    """
+
+    chunks = None
+
+    def __init__(self, book, chunks, book_length):
+        self.book = book
+        self.chunks = chunks
+        self.book_length = book_length
+
diff --git a/apps/catalogue/locale/pl/LC_MESSAGES/django.mo b/apps/catalogue/locale/pl/LC_MESSAGES/django.mo
new file mode 100644 (file)
index 0000000..f841945
Binary files /dev/null and b/apps/catalogue/locale/pl/LC_MESSAGES/django.mo differ
diff --git a/apps/catalogue/locale/pl/LC_MESSAGES/django.po b/apps/catalogue/locale/pl/LC_MESSAGES/django.po
new file mode 100644 (file)
index 0000000..c760f3a
--- /dev/null
@@ -0,0 +1,508 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Platforma Redakcyjna\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-06-21 13:41+0200\n"
+"PO-Revision-Date: 2011-06-21 13:46+0100\n"
+"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
+"Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org.pl>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:32
+msgid "Publishable"
+msgstr "Gotowe do publikacji"
+
+#: forms.py:68
+msgid "ZIP file"
+msgstr "Plik ZIP"
+
+#: forms.py:99
+#: forms.py:137
+msgid "Author"
+msgstr "Autor"
+
+#: forms.py:100
+#: forms.py:138
+msgid "Your name"
+msgstr "Imię i nazwisko"
+
+#: forms.py:105
+#: forms.py:143
+msgid "Author's email"
+msgstr "E-mail autora"
+
+#: forms.py:106
+#: forms.py:144
+msgid "Your email address, so we can show a gravatar :)"
+msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)"
+
+#: forms.py:112
+#: forms.py:150
+msgid "Your comments"
+msgstr "Twój komentarz"
+
+#: forms.py:113
+msgid "Describe changes you made."
+msgstr "Opisz swoje zmiany"
+
+#: forms.py:119
+msgid "Completed"
+msgstr "Ukończono"
+
+#: forms.py:120
+msgid "If you completed a life cycle stage, select it."
+msgstr "Jeśli został ukończony etap prac, wskaż go."
+
+#: forms.py:151
+msgid "Describe the reason for reverting."
+msgstr "Opisz powód przywrócenia."
+
+#: forms.py:176
+#: forms.py:190
+msgid "Chunk with this slug already exists"
+msgstr "Część z tym slugiem już istnieje"
+
+#: forms.py:202
+msgid "Append to"
+msgstr "Dołącz do"
+
+#: models.py:25
+msgid "title"
+msgstr "tytuł"
+
+#: models.py:26
+msgid "slug"
+msgstr ""
+
+#: models.py:27
+msgid "scan gallery name"
+msgstr "nazwa galerii skanów"
+
+#: models.py:29
+msgid "parent"
+msgstr "rodzic"
+
+#: models.py:30
+msgid "parent number"
+msgstr "numeracja rodzica"
+
+#: models.py:40
+msgid "book"
+msgstr "książka"
+
+#: models.py:41
+msgid "books"
+msgstr "książki"
+
+#: models.py:206
+msgid "name"
+msgstr "nazwa"
+
+#: models.py:210
+msgid "theme"
+msgstr "motyw"
+
+#: models.py:211
+msgid "themes"
+msgstr "motywy"
+
+#: views.py:241
+#, python-format
+msgid "Slug already used for %s"
+msgstr "Slug taki sam jak dla pliku %s"
+
+#: views.py:243
+msgid "Slug already used in repository."
+msgstr "Dokument o tym slugu już istnieje w repozytorium."
+
+#: views.py:249
+msgid "File should be UTF-8 encoded."
+msgstr "Plik powinien mieć kodowanie UTF-8."
+
+#: views.py:655
+msgid "Tag added"
+msgstr "Dodano tag"
+
+#: views.py:677
+msgid "Revision marked"
+msgstr "Wersja oznaczona"
+
+#: views.py:679
+msgid "Nothing changed"
+msgstr "Nic nie uległo zmianie"
+
+#: templates/wiki/base.html:9
+msgid "Platforma Redakcyjna"
+msgstr ""
+
+#: templates/wiki/book_append_to.html:8
+msgid "Append book"
+msgstr "Dołącz książkę"
+
+#: templates/wiki/book_detail.html:6
+#: templates/wiki/book_detail.html.py:46
+msgid "edit"
+msgstr "edytuj"
+
+#: templates/wiki/book_detail.html:16
+msgid "add basic document structure"
+msgstr "dodaj podstawową strukturę dokumentu"
+
+#: templates/wiki/book_detail.html:20
+msgid "change master tag to"
+msgstr "zmień tak master na"
+
+#: templates/wiki/book_detail.html:24
+msgid "add begin trimming tag"
+msgstr "dodaj początkowy ogranicznik"
+
+#: templates/wiki/book_detail.html:28
+msgid "add end trimming tag"
+msgstr "dodaj końcowy ogranicznik"
+
+#: templates/wiki/book_detail.html:34
+msgid "unstructured text"
+msgstr "tekst bez struktury"
+
+#: templates/wiki/book_detail.html:38
+msgid "unknown XML"
+msgstr "nieznany XML"
+
+#: templates/wiki/book_detail.html:42
+msgid "broken document"
+msgstr "uszkodzony dokument"
+
+#: templates/wiki/book_detail.html:60
+msgid "Apply fixes"
+msgstr "Wykonaj zmiany"
+
+#: templates/wiki/book_detail.html:66
+msgid "Append to other book"
+msgstr "Dołącz do innej książki"
+
+#: templates/wiki/book_detail.html:68
+msgid "Last published"
+msgstr "Ostatnio opublikowano"
+
+#: templates/wiki/book_detail.html:72
+msgid "Full XML"
+msgstr "Pełny XML"
+
+#: templates/wiki/book_detail.html:73
+msgid "HTML version"
+msgstr "Wersja HTML"
+
+#: templates/wiki/book_detail.html:74
+msgid "TXT version"
+msgstr "Wersja TXT"
+
+#: templates/wiki/book_detail.html:76
+msgid "EPUB version"
+msgstr "Wersja EPUB"
+
+#: templates/wiki/book_detail.html:77
+msgid "PDF version"
+msgstr "Wersja PDF"
+
+#: templates/wiki/book_detail.html:90
+#: templates/wiki/tabs/summary_view.html:30
+msgid "Publish"
+msgstr "Opublikuj"
+
+#: templates/wiki/book_detail.html:94
+msgid "This book cannot be published yet"
+msgstr "Ta książka nie może jeszcze zostać opublikowana"
+
+#: templates/wiki/book_edit.html:8
+#: templates/wiki/chunk_edit.html:8
+#: templates/wiki/document_details_base.html:35
+#: templates/wiki/pubmark_dialog.html:15
+#: templates/wiki/tag_dialog.html:15
+msgid "Save"
+msgstr "Zapisz"
+
+#: templates/wiki/chunk_add.html:8
+msgid "Add chunk"
+msgstr "Dodaj część"
+
+#: templates/wiki/diff_table.html:5
+msgid "Old version"
+msgstr "Stara wersja"
+
+#: templates/wiki/diff_table.html:6
+msgid "New version"
+msgstr "Nowa wersja"
+
+#: templates/wiki/document_create_missing.html:8
+msgid "Create document"
+msgstr "Utwórz dokument"
+
+#: templates/wiki/document_details.html:32
+msgid "Click to open/close gallery"
+msgstr "Kliknij, aby (ro)zwinąć galerię"
+
+#: templates/wiki/document_details_base.html:31
+msgid "Help"
+msgstr "Pomoc"
+
+#: templates/wiki/document_details_base.html:33
+msgid "Version"
+msgstr "Wersja"
+
+#: templates/wiki/document_details_base.html:33
+msgid "Unknown"
+msgstr "nieznana"
+
+#: templates/wiki/document_details_base.html:36
+msgid "Save attempt in progress"
+msgstr "Trwa zapisywanie"
+
+#: templates/wiki/document_details_base.html:37
+msgid "There is a newer version of this document!"
+msgstr "Istnieje nowsza wersja tego dokumentu!"
+
+#: templates/wiki/document_list.html:31
+msgid "Clear filter"
+msgstr "Wyczyść filtr"
+
+#: templates/wiki/document_list.html:46
+msgid "No books found."
+msgstr "Nie znaleziono książek."
+
+#: templates/wiki/document_list.html:89
+msgid "Your last edited documents"
+msgstr "Twoje ostatnie edycje"
+
+#: templates/wiki/document_upload.html:8
+msgid "Bulk documents upload"
+msgstr "Hurtowe dodawanie dokumentów"
+
+#: templates/wiki/document_upload.html:11
+msgid "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with <code>.xml</code> will be ignored."
+msgstr "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie kończące się na <code>.xml</code> zostaną zignorowane."
+
+#: templates/wiki/document_upload.html:16
+#: templatetags/wiki.py:36
+msgid "Upload"
+msgstr "Załaduj"
+
+#: templates/wiki/document_upload.html:23
+msgid "There have been some errors. No files have been added to the repository."
+msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium."
+
+#: templates/wiki/document_upload.html:24
+msgid "Offending files"
+msgstr "Błędne pliki"
+
+#: templates/wiki/document_upload.html:32
+msgid "Correct files"
+msgstr "Poprawne pliki"
+
+#: templates/wiki/document_upload.html:43
+msgid "Files have been successfully uploaded to the repository."
+msgstr "Pliki zostały dodane do repozytorium."
+
+#: templates/wiki/document_upload.html:44
+msgid "Uploaded files"
+msgstr "Dodane pliki"
+
+#: templates/wiki/document_upload.html:54
+msgid "Skipped files"
+msgstr "Pominięte pliki"
+
+#: templates/wiki/document_upload.html:55
+msgid "Files skipped due to no <code>.xml</code> extension"
+msgstr "Pliki pominięte z powodu braku rozszerzenia <code>.xml</code>."
+
+#: templates/wiki/pubmark_dialog.html:16
+#: templates/wiki/revert_dialog.html:39
+#: templates/wiki/tag_dialog.html:16
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: templates/wiki/revert_dialog.html:38
+msgid "Revert"
+msgstr "Przywróć"
+
+#: templates/wiki/user_list.html:7
+#: templatetags/wiki.py:33
+msgid "Users"
+msgstr "Użytkownicy"
+
+#: templates/wiki/tabs/annotations_view.html:9
+msgid "all"
+msgstr "wszystkie"
+
+#: templates/wiki/tabs/annotations_view_item.html:3
+msgid "Annotations"
+msgstr "Przypisy"
+
+#: templates/wiki/tabs/gallery_view.html:7
+msgid "Previous"
+msgstr "Poprzednie"
+
+#: templates/wiki/tabs/gallery_view.html:13
+msgid "Next"
+msgstr "Następne"
+
+#: templates/wiki/tabs/gallery_view.html:15
+msgid "Zoom in"
+msgstr "Powiększ"
+
+#: templates/wiki/tabs/gallery_view.html:16
+msgid "Zoom out"
+msgstr "Zmniejsz"
+
+#: templates/wiki/tabs/gallery_view_item.html:3
+msgid "Gallery"
+msgstr "Galeria"
+
+#: templates/wiki/tabs/history_view.html:5
+msgid "Compare versions"
+msgstr "Porównaj wersje"
+
+#: templates/wiki/tabs/history_view.html:7
+msgid "Mark for publishing"
+msgstr "Oznacz do publikacji"
+
+#: templates/wiki/tabs/history_view.html:9
+msgid "Revert document"
+msgstr "Przywróć wersję"
+
+#: templates/wiki/tabs/history_view.html:12
+msgid "View version"
+msgstr "Zobacz wersję"
+
+#: templates/wiki/tabs/history_view_item.html:3
+msgid "History"
+msgstr "Historia"
+
+#: templates/wiki/tabs/search_view.html:3
+#: templates/wiki/tabs/search_view.html:5
+msgid "Search"
+msgstr "Szukaj"
+
+#: templates/wiki/tabs/search_view.html:8
+msgid "Replace with"
+msgstr "Zamień na"
+
+#: templates/wiki/tabs/search_view.html:10
+msgid "Replace"
+msgstr "Zamień"
+
+#: templates/wiki/tabs/search_view.html:13
+msgid "Options"
+msgstr "Opcje"
+
+#: templates/wiki/tabs/search_view.html:15
+msgid "Case sensitive"
+msgstr "Rozróżniaj wielkość liter"
+
+#: templates/wiki/tabs/search_view.html:17
+msgid "From cursor"
+msgstr "Zacznij od kursora"
+
+#: templates/wiki/tabs/search_view_item.html:3
+msgid "Search and replace"
+msgstr "Znajdź i zamień"
+
+#: templates/wiki/tabs/source_editor_item.html:5
+msgid "Source code"
+msgstr "Kod źródłowy"
+
+#: templates/wiki/tabs/summary_view.html:9
+msgid "Title"
+msgstr "Tytuł"
+
+#: templates/wiki/tabs/summary_view.html:14
+msgid "Document ID"
+msgstr "ID dokumentu"
+
+#: templates/wiki/tabs/summary_view.html:18
+msgid "Current version"
+msgstr "Aktualna wersja"
+
+#: templates/wiki/tabs/summary_view.html:21
+msgid "Last edited by"
+msgstr "Ostatnio edytowane przez"
+
+#: templates/wiki/tabs/summary_view.html:25
+msgid "Link to gallery"
+msgstr "Link do galerii"
+
+#: templates/wiki/tabs/summary_view_item.html:3
+msgid "Summary"
+msgstr "Podsumowanie"
+
+#: templates/wiki/tabs/wysiwyg_editor.html:9
+msgid "Insert theme"
+msgstr "Wstaw motyw"
+
+#: templates/wiki/tabs/wysiwyg_editor.html:12
+msgid "Insert annotation"
+msgstr "Wstaw przypis"
+
+#: templates/wiki/tabs/wysiwyg_editor_item.html:3
+msgid "Visual editor"
+msgstr "Edytor wizualny"
+
+#: templatetags/wiki.py:30
+msgid "Assigned to me"
+msgstr "Przypisane do mnie"
+
+#: templatetags/wiki.py:32
+msgid "Unassigned"
+msgstr "Nie przypisane"
+
+#: templatetags/wiki.py:34
+msgid "All"
+msgstr "Wszystkie"
+
+#: templatetags/wiki.py:35
+msgid "Add"
+msgstr "Dodaj"
+
+#: templatetags/wiki.py:39
+msgid "Admin"
+msgstr "Administracja"
+
+#~ msgid "First correction"
+#~ msgstr "Autokorekta"
+
+#~ msgid "Tagging"
+#~ msgstr "Tagowanie"
+
+#~ msgid "Initial Proofreading"
+#~ msgstr "Korekta"
+
+#~ msgid "Annotation Proofreading"
+#~ msgstr "Sprawdzenie przypisów źródła"
+
+#~ msgid "Modernisation"
+#~ msgstr "Uwspółcześnienie"
+
+#~ msgid "Themes"
+#~ msgstr "Motywy"
+
+#~ msgid "Editor's Proofreading"
+#~ msgstr "Ostateczna redakcja literacka"
+
+#~ msgid "Technical Editor's Proofreading"
+#~ msgstr "Ostateczna redakcja techniczna"
+
+#~ msgid "Finished stage: %s"
+#~ msgstr "Ukończony etap: %s"
+
+#~ msgid "Refresh"
+#~ msgstr "Odśwież"
+
+#~ msgid "Insert special character"
+#~ msgstr "Wstaw znak specjalny"
diff --git a/apps/catalogue/management/__init__.py b/apps/catalogue/management/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/apps/catalogue/management/commands/__init__.py b/apps/catalogue/management/commands/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/apps/catalogue/management/commands/assign_from_redmine.py b/apps/catalogue/management/commands/assign_from_redmine.py
new file mode 100755 (executable)
index 0000000..9f7b12d
--- /dev/null
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+
+import csv
+from optparse import make_option
+import re
+import sys
+import urllib
+import urllib2
+
+from django.contrib.auth.models import User
+from django.core.management.base import BaseCommand
+from django.core.management.color import color_style
+from django.db import transaction
+
+from slughifi import slughifi
+from catalogue.models import Chunk
+
+
+REDMINE_CSV = 'http://redmine.nowoczesnapolska.org.pl/projects/wl-publikacje/issues.csv'
+REDAKCJA_URL = 'http://redakcja.wolnelektury.pl/documents/'
+
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('-r', '--redakcja', dest='redakcja', metavar='URL',
+            help='Base URL of Redakcja documents',
+            default=REDAKCJA_URL),
+        make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
+            help='Less output'),
+        make_option('-f', '--force', action='store_true', dest='force', default=False,
+            help='Force assignment overwrite'),
+    )
+    help = 'Imports ticket assignments from Redmine.'
+    args = '[redmine-csv-url]'
+
+    def handle(self, *redmine_csv, **options):
+
+        self.style = color_style()
+
+        redakcja = options.get('redakcja')
+        verbose = options.get('verbose')
+        force = options.get('force')
+
+        if not redmine_csv:
+            if verbose:
+                print "Using default Redmine CSV URL:", REDMINE_CSV
+            redmine_csv = REDMINE_CSV
+
+        # Start transaction management.
+        transaction.commit_unless_managed()
+        transaction.enter_transaction_management()
+        transaction.managed(True)
+
+        redakcja_link = re.compile(re.escape(redakcja) + r'([-_.:?&%/a-zA-Z0-9]*)')
+
+        all_tickets = 0
+        all_chunks = 0
+        done_tickets = 0
+        done_chunks = 0
+        empty_users = 0
+        unknown_users = {}
+        unknown_books = []
+        forced = []
+
+        if verbose:
+            print 'Downloading CSV file'
+        for r in csv.reader(urllib2.urlopen(redmine_csv)):
+            if r[0] == '#':
+                continue
+            all_tickets += 1
+
+            username = r[6]
+            if not username:
+                if verbose:
+                    print "Empty user, skipping"
+                empty_users += 1
+                continue
+
+            first_name, last_name = unicode(username, 'utf-8').rsplit(u' ', 1)
+            try:
+                user = User.objects.get(first_name=first_name, last_name=last_name)
+            except User.DoesNotExist:
+                print self.style.ERROR('Unknown user: ' + username)
+                unknown_users.setdefault(username, 0)
+                unknown_users[username] += 1
+                continue
+
+            ticket_done = False
+            for fname in redakcja_link.findall(r[-1]):
+                fname = unicode(urllib.unquote(fname), 'utf-8', 'ignore')
+                if fname.endswith('.xml'):
+                    fname = fname[:-4]
+                fname = fname.replace(' ', '_')
+                fname = slughifi(fname)
+
+                chunks = Chunk.objects.filter(book__slug=fname)
+                if not chunks:
+                    print self.style.ERROR('Unknown book: ' + fname)
+                    unknown_books.append(fname)
+                    continue
+                all_chunks += chunks.count()
+
+                for chunk in chunks:
+                    if chunk.user:
+                        if chunk.user == user:
+                            continue
+                        else:
+                            forced.append((chunk, chunk.user, user))
+                            if force:
+                                print self.style.WARNING(
+                                    '%s assigned to %s, forcing change to %s.' %
+                                    (chunk.pretty_name(), chunk.user, user))
+                            else:
+                                print self.style.WARNING(
+                                    '%s assigned to %s not to %s, skipping.' %
+                                    (chunk.pretty_name(), chunk.user, user))
+                                continue
+                    chunk.user = user
+                    chunk.save()
+                    ticket_done = True
+                    done_chunks += 1
+
+            if ticket_done:
+                done_tickets += 1
+
+
+        # Print results
+        print
+        print "Results:"
+        print "Assignments imported from %d/%d tickets to %d/%d relevalt chunks." % (
+                done_tickets, all_tickets, done_chunks, all_chunks)
+        if empty_users:
+            print "%d tickets were unassigned." % empty_users
+        if forced:
+            print "%d assignments conficts (%s):" % (
+                len(forced), "changed" if force else "left")
+            for chunk, orig, user in forced:
+                print "  %s: \t%s \t->  %s" % (
+                    chunk.pretty_name(), orig.username, user.username)
+        if unknown_books:
+            print "%d unknown books:" % len(unknown_books)
+            for fname in unknown_books:
+                print "  %s" % fname
+        if unknown_users:
+            print "%d unknown users:" % len(unknown_users)
+            for name in unknown_users:
+                print "  %s (%d tickets)" % (name, unknown_users[name])
+        print
+
+
+        transaction.commit()
+        transaction.leave_transaction_management()
+
diff --git a/apps/catalogue/migrations/0001_initial.py b/apps/catalogue/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..4ebde47
--- /dev/null
@@ -0,0 +1,365 @@
+# encoding: utf-8
+import datetime
+import os.path
+import cPickle
+import re
+import urllib
+
+from django.conf import settings
+from django.db import models
+from mercurial import mdiff, hg, ui
+from south.db import db
+from south.v2 import SchemaMigration
+
+from slughifi import slughifi
+
+META_REGEX = re.compile(r'\s*<!--\s(.*?)-->', re.DOTALL | re.MULTILINE)
+STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE)
+AUTHOR_RE = re.compile(r'\s*(.*?)\s*<(.*)>\s*')
+
+
+def urlunquote(url):
+    """Unqotes URL
+
+    # >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84')
+    # u'Za\u017c\xf3\u0142\u0107_g\u0119\u015bl\u0105 ja\u017a\u0144'
+    """
+    return unicode(urllib.unquote(url), 'utf-8', 'ignore')
+
+
+def split_name(name):
+    parts = name.split('__')
+    return parts
+
+
+def file_to_title(fname):
+    """ Returns a title-like version of a filename. """
+    parts = (p.replace('_', ' ').title() for p in fname.split('__'))
+    return ' / '.join(parts)
+
+
+def make_patch(src, dst):
+    if isinstance(src, unicode):
+        src = src.encode('utf-8')
+    if isinstance(dst, unicode):
+        dst = dst.encode('utf-8')
+    return cPickle.dumps(mdiff.textdiff(src, dst))
+
+
+def plain_text(text):
+    return re.sub(META_REGEX, '', text, 1)
+
+
+def gallery(slug, text):
+    result = {}
+
+    m = re.match(META_REGEX, text)
+    if m:
+        for line in m.group(1).split('\n'):
+            try:
+                k, v = line.split(':', 1)
+                result[k.strip()] = v.strip()
+            except ValueError:
+                continue
+
+    gallery = result.get('gallery', slughifi(slug))
+
+    if gallery.startswith('/'):
+        gallery = os.path.basename(gallery)
+
+    return gallery
+
+
+def migrate_file_from_hg(orm, fname, entry):
+    fname = urlunquote(fname)
+    print fname
+    if fname.endswith('.xml'):
+        fname = fname[:-4]
+    title = file_to_title(fname)
+    fname = slughifi(fname)
+    # create all the needed objects
+    # what if it already exists?
+    book = orm.Book.objects.create(
+        title=title,
+        slug=fname)
+    chunk = orm.Chunk.objects.create(
+        book=book,
+        number=1,
+        slug='1')
+    head = orm.ChunkChange.objects.create(
+        tree=chunk,
+        revision=-1,
+        patch=make_patch('', ''),
+        created_at=datetime.datetime.fromtimestamp(entry.filectx(0).date()[0]),
+        description=''
+        )
+    chunk.head = head
+    try:
+        chunk.stage = orm.ChunkTag.objects.order_by('ordering')[0]
+    except IndexError:
+        chunk.stage = None
+    old_data = ''
+
+    maxrev = entry.filerev()
+    gallery_link = None
+    
+    for rev in xrange(maxrev + 1):
+        fctx = entry.filectx(rev)
+        data = fctx.data()
+        gallery_link = gallery(fname, data)
+        data = plain_text(data)
+
+        # get tags from description
+        description = fctx.description().decode("utf-8", 'replace')
+        tags = STAGE_TAGS_RE.findall(description)
+        tags = [orm.ChunkTag.objects.get(slug=slug.strip()) for slug in tags]
+
+        if tags:
+            max_ordering = max(tags, key=lambda x: x.ordering).ordering
+            try:
+                chunk.stage = orm.ChunkTag.objects.filter(ordering__gt=max_ordering).order_by('ordering')[0]
+            except IndexError:
+                chunk.stage = None
+
+        description = STAGE_TAGS_RE.sub('', description)
+
+        author = author_name = author_email = None
+        author_desc = fctx.user().decode("utf-8", 'replace')
+        m = AUTHOR_RE.match(author_desc)
+        if m:
+            try:
+                author = orm['auth.User'].objects.get(username=m.group(1), email=m.group(2))
+            except orm['auth.User'].DoesNotExist:
+                author_name = m.group(1)
+                author_email = m.group(2)
+        else:
+            author_name = author_desc
+
+        head = orm.ChunkChange.objects.create(
+            tree=chunk,
+            revision=rev + 1,
+            patch=make_patch(old_data, data),
+            created_at=datetime.datetime.fromtimestamp(fctx.date()[0]),
+            description=description,
+            author=author,
+            author_name=author_name,
+            author_email=author_email,
+            parent=chunk.head
+            )
+        head.tags = tags
+        chunk.head = head
+        old_data = data
+
+    chunk.save()
+    if gallery_link:
+        book.gallery = gallery_link
+        book.save()
+
+
+def migrate_from_hg(orm):
+    try:
+        hg_path = settings.WIKI_REPOSITORY_PATH
+    except:
+        pass
+
+    print 'migrate from', hg_path
+    repo = hg.repository(ui.ui(), hg_path)
+    tip = repo['tip']
+    for fname in tip:
+        if fname.startswith('.'):
+            continue
+        migrate_file_from_hg(orm, fname, tip[fname])
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding model 'Book'
+        db.create_table('catalogue_book', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=128, db_index=True)),
+            ('gallery', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['catalogue.Book'])),
+            ('parent_number', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)),
+            ('last_published', self.gf('django.db.models.fields.DateTimeField')(null=True, db_index=True)),
+        ))
+        db.send_create_signal('catalogue', ['Book'])
+
+        # Adding model 'Chunk'
+        db.create_table('catalogue_chunk', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_documents', null=True, to=orm['auth.User'])),
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
+            ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Book'])),
+            ('number', self.gf('django.db.models.fields.IntegerField')()),
+            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)),
+            ('comment', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
+            ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ChunkTag'], null=True, blank=True)),
+            ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['catalogue.ChunkChange'], null=True, blank=True)),
+        ))
+        db.send_create_signal('catalogue', ['Chunk'])
+
+        # Adding unique constraint on 'Chunk', fields ['book', 'number']
+        db.create_unique('catalogue_chunk', ['book_id', 'number'])
+
+        # Adding unique constraint on 'Chunk', fields ['book', 'slug']
+        db.create_unique('catalogue_chunk', ['book_id', 'slug'])
+
+        # Adding model 'ChunkTag'
+        db.create_table('catalogue_chunktag', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=64)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=64, unique=True, null=True, blank=True)),
+            ('ordering', self.gf('django.db.models.fields.IntegerField')()),
+        ))
+        db.send_create_signal('catalogue', ['ChunkTag'])
+
+        # Adding model 'ChunkChange'
+        db.create_table('catalogue_chunkchange', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
+            ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
+            ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
+            ('patch', self.gf('django.db.models.fields.TextField')(blank=True)),
+            ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['catalogue.ChunkChange'])),
+            ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['catalogue.ChunkChange'])),
+            ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
+            ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)),
+            ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)),
+            ('tree', self.gf('django.db.models.fields.related.ForeignKey')(related_name='change_set', to=orm['catalogue.Chunk'])),
+        ))
+        db.send_create_signal('catalogue', ['ChunkChange'])
+
+        # Adding unique constraint on 'ChunkChange', fields ['tree', 'revision']
+        db.create_unique('catalogue_chunkchange', ['tree_id', 'revision'])
+
+        # Adding M2M table for field tags on 'ChunkChange'
+        db.create_table('catalogue_chunkchange_tags', (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('chunkchange', models.ForeignKey(orm['catalogue.chunkchange'], null=False)),
+            ('chunktag', models.ForeignKey(orm['catalogue.chunktag'], null=False))
+        ))
+        db.create_unique('catalogue_chunkchange_tags', ['chunkchange_id', 'chunktag_id'])
+
+        if not db.dry_run:
+            from django.core.management import call_command
+            call_command("loaddata", "stages.json")
+
+            migrate_from_hg(orm)
+
+
+    def backwards(self, orm):
+        
+        # Removing unique constraint on 'ChunkChange', fields ['tree', 'revision']
+        db.delete_unique('catalogue_chunkchange', ['tree_id', 'revision'])
+
+        # Removing unique constraint on 'Chunk', fields ['book', 'slug']
+        db.delete_unique('catalogue_chunk', ['book_id', 'slug'])
+
+        # Removing unique constraint on 'Chunk', fields ['book', 'number']
+        db.delete_unique('catalogue_chunk', ['book_id', 'number'])
+
+        # Deleting model 'Book'
+        db.delete_table('catalogue_book')
+
+        # Deleting model 'Chunk'
+        db.delete_table('catalogue_chunk')
+
+        # Deleting model 'ChunkTag'
+        db.delete_table('catalogue_chunktag')
+
+        # Deleting model 'ChunkChange'
+        db.delete_table('catalogue_chunkchange')
+
+        # Removing M2M table for field tags on 'ChunkChange'
+        db.delete_table('catalogue_chunkchange_tags')
+
+
+    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': "['parent_number', 'title']", 'object_name': 'Book'},
+            'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_published': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': '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'}),
+            '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.chunk': {
+            'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
+            'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}),
+            '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'}),
+            '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'}),
+            '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']"}),
+            'patch': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            '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.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']
diff --git a/apps/catalogue/migrations/__init__.py b/apps/catalogue/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py
new file mode 100644 (file)
index 0000000..ff3d434
--- /dev/null
@@ -0,0 +1,178 @@
+# -*- 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 django.core.urlresolvers import reverse
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from dvcs import models as dvcs_models
+from catalogue.xml_tools import compile_text
+
+import logging
+logger = logging.getLogger("fnp.catalogue")
+
+
+class Book(models.Model):
+    """ A document edited on the wiki """
+
+    title = models.CharField(_('title'), max_length=255, db_index=True)
+    slug = models.SlugField(_('slug'), max_length=128, unique=True, db_index=True)
+    gallery = models.CharField(_('scan gallery name'), max_length=255, blank=True)
+
+    parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children")
+    parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True)
+    last_published = models.DateTimeField(null=True, editable=False, db_index=True)
+
+    class NoTextError(BaseException):
+        pass
+
+    class Meta:
+        ordering = ['parent_number', 'title']
+        verbose_name = _('book')
+        verbose_name_plural = _('books')
+
+    def __unicode__(self):
+        return self.title
+
+    def get_absolute_url(self):
+        return reverse("catalogue_book", args=[self.slug])
+
+    @classmethod
+    def create(cls, creator=None, text=u'', *args, **kwargs):
+        """
+            >>> Book.create(slug='x', text='abc').materialize()
+            'abc'
+        """
+        instance = cls(*args, **kwargs)
+        instance.save()
+        instance[0].commit(author=creator, text=text)
+        return instance
+
+    def __iter__(self):
+        return iter(self.chunk_set.all())
+
+    def __getitem__(self, chunk):
+        return self.chunk_set.all()[chunk]
+
+    def materialize(self, publishable=True):
+        """ 
+            Get full text of the document compiled from chunks.
+            Takes the current versions of all texts
+            or versions most recently tagged for publishing.
+        """
+        if publishable:
+            changes = [chunk.publishable() for chunk in self]
+        else:
+            changes = [chunk.head for chunk in self]
+        if None in changes:
+            raise self.NoTextError('Some chunks have no available text.')
+        return compile_text(change.materialize() for change in changes)
+
+    def publishable(self):
+        if not len(self):
+            return False
+        for chunk in self:
+            if not chunk.publishable():
+                return False
+        return True
+
+    def make_chunk_slug(self, proposed):
+        """ 
+            Finds a chunk slug not yet used in the book.
+        """
+        slugs = set(c.slug for c in self)
+        i = 1
+        new_slug = proposed
+        while new_slug in slugs:
+            new_slug = "%s-%d" % (proposed, i)
+            i += 1
+        return new_slug
+
+    def append(self, other):
+        number = self[len(self) - 1].number + 1
+        single = len(other) == 1
+        for chunk in other:
+            # move chunk to new book
+            chunk.book = self
+            chunk.number = number
+
+            # try some title guessing
+            if other.title.startswith(self.title):
+                other_title_part = other.title[len(self.title):].lstrip(' /')
+            else:
+                other_title_part = other.title
+
+            if single:
+                # special treatment for appending one-parters:
+                # just use the guessed title and original book slug
+                chunk.comment = other_title_part
+                if other.slug.startswith(self.slug):
+                    chunk_slug = other.slug[len(self.slug):].lstrip('-_')
+                else:
+                    chunk_slug = other.slug
+                chunk.slug = self.make_chunk_slug(chunk_slug)
+            else:
+                chunk.comment = "%s, %s" % (other_title_part, chunk.comment)
+                chunk.slug = self.make_chunk_slug(chunk.slug)
+            chunk.save()
+            number += 1
+        other.delete()
+
+    @staticmethod
+    def listener_create(sender, instance, created, **kwargs):
+        if created:
+            instance.chunk_set.create(number=1, slug='1')
+
+models.signals.post_save.connect(Book.listener_create, sender=Book)
+
+
+class Chunk(dvcs_models.Document):
+    """ An editable chunk of text. Every Book text is divided into chunks. """
+
+    book = models.ForeignKey(Book, editable=False)
+    number = models.IntegerField()
+    slug = models.SlugField()
+    comment = models.CharField(max_length=255, blank=True)
+
+    class Meta:
+        unique_together = [['book', 'number'], ['book', 'slug']]
+        ordering = ['number']
+
+    def __unicode__(self):
+        return "%d-%d: %s" % (self.book_id, self.number, self.comment)
+
+    def get_absolute_url(self):
+        return reverse("wiki_editor", args=[self.book.slug, self.slug])
+
+    @classmethod
+    def get(cls, slug, chunk=None):
+        if chunk is None:
+            return cls.objects.get(book__slug=slug, number=1)
+        else:
+            return cls.objects.get(book__slug=slug, slug=chunk)
+
+    def pretty_name(self, book_length=None):
+        title = self.book.title
+        if self.comment:
+            title += ", %s" % self.comment
+        if book_length > 1:
+            title += " (%d/%d)" % (self.number, book_length)
+        return title
+
+    def split(self, slug, comment='', creator=None):
+        """ Create an empty chunk after this one """
+        self.book.chunk_set.filter(number__gt=self.number).update(
+                number=models.F('number')+1)
+        new_chunk = self.book.chunk_set.create(number=self.number+1,
+                creator=creator, slug=slug, comment=comment)
+        return new_chunk
+
+    @staticmethod
+    def listener_saved(sender, instance, created, **kwargs):
+        if instance.book:
+            # save book so that its _list_html is reset
+            instance.book.save()
+
+models.signals.post_save.connect(Chunk.listener_saved, sender=Chunk)
diff --git a/apps/catalogue/templates/catalogue/base.html b/apps/catalogue/templates/catalogue/base.html
new file mode 100644 (file)
index 0000000..4ba8a0e
--- /dev/null
@@ -0,0 +1,49 @@
+{% load compressed i18n %}
+{% load catalogue %}
+<!DOCTYPE html>
+<html>
+<head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+    {% compressed_css 'listing' %}
+    <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}style.css" />
+    <title>{% block title %}{% trans "Platforma Redakcyjna" %}{% endblock title %}</title>
+</head>
+<body>
+
+<div id="tabs-nav">
+
+    <a href="{% url catalogue_document_list %}">
+        <img id="logo" src="{{ STATIC_URL }}img/wl-orange.png" />
+    </a>
+
+    <div id="tabs-nav-left">
+        {% main_tabs %}
+    </div>
+
+    <span id="login-box">
+        {% include "registration/head_login.html" %}
+    </span>
+
+    <div class='clr' ></div>
+</div>
+
+<div id="content">
+
+{% block content %}
+<div id="catalogue_layout_left_column">
+       {% block leftcolumn %}
+       {% endblock leftcolumn %}
+</div>
+<div id="catalogue_layout_right_column">
+       {% block rightcolumn %}
+       {% endblock rightcolumn %}
+</div>
+{% endblock content %}
+
+</div>
+
+{% compressed_js 'listing' %}
+{% block extrabody %}
+{% endblock %}
+</body>
+</html>
diff --git a/apps/catalogue/templates/catalogue/book_append_to.html b/apps/catalogue/templates/catalogue/book_append_to.html
new file mode 100755 (executable)
index 0000000..76a5962
--- /dev/null
@@ -0,0 +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 "Append book" %}</button></p>
+       </form>
+{% endblock leftcolumn %}
+
+{% block rightcolumn %}
+{% endblock rightcolumn %}
diff --git a/apps/catalogue/templates/catalogue/book_detail.html b/apps/catalogue/templates/catalogue/book_detail.html
new file mode 100755 (executable)
index 0000000..1c0178c
--- /dev/null
@@ -0,0 +1,104 @@
+{% extends "catalogue/base.html" %}
+{% load comments i18n %}
+
+{% block leftcolumn %}
+
+<a href="{% url catalogue_book_edit book.slug %}">{% trans "edit" %}</a>
+    <h1>{{ book.title }}</h1>
+
+<table>
+    {% for c in chunks %}
+        <tr class="chunk-{{ c.grade }}">
+        <td><a target="_blank" href="{{ c.chunk.get_absolute_url }}">{{ c.chunk.comment }}</a></td>
+        <td>{% for fix in c.fix %}
+
+                {% ifequal fix "wl" %}<span class="fix"
+                    title="{% trans "add basic document structure" %}"
+                    >&lt;/&gt;</span>{% endifequal %}
+
+                {% ifequal fix "bad-master" %}<span class="fix"
+                    title='{% trans "change master tag to" %} "{{ first_master }}"'
+                    >master</span>{% endifequal %}
+
+                {% ifequal fix "trim-begin" %}<span class="fix"
+                    title="{% trans "add begin trimming tag" %}"
+                    >&#x2701;</span>{% endifequal %}
+
+                {% ifequal fix "trim-end" %}<span class="fix"
+                    title="{% trans "add end trimming tag" %}"
+                    >&#x2703;</span>{% endifequal %}
+
+            {% endfor %}
+
+            {% ifequal c.grade "plain" %}
+                <span class="fix-info">{% trans "unstructured text" %}</span>
+            {% endifequal %}
+
+            {% ifequal c.grade "xml" %}
+                <span class="fix-info">{% trans "unknown XML" %}</span>
+            {% endifequal %}
+
+            {% ifequal c.grade "wl-broken" %}
+                <span class="fix-info">{% trans "broken document" %}</span>
+            {% endifequal %}
+
+        </td>
+        <td><a href="{% url catalogue_chunk_edit book.slug c.chunk.slug%}">[{% trans "edit" %}]</a></td>
+        <td>{% if c.chunk.publishable %}P{% endif %}</td>
+        <td>{% if c.chunk.user.is_authenticated %}
+                <a href="{% url catalogue_user c.chunk.user.username %}">{{ c.chunk.user }}</a>
+            {% endif %}</td>
+        <td><a href="{% url catalogue_chunk_add book.slug c.chunk.slug %}">[+]</a></td>
+        </tr>
+    {% endfor %}
+    {% if need_fixing %}
+        <tr><td></td><td>
+            <form method="POST" action="">
+                {% csrf_token %}
+                {% if choose_master %}
+                    {{ form.master }}
+                {% endif %}
+                <button type="submit">{% trans "Apply fixes" %}</button>
+            </form>
+        </td></tr>
+    {% endif %}
+</table>
+
+<p><a href="{% url catalogue_book_append book.slug %}">{% trans "Append to other book" %}</a></p>
+
+<p>{% trans "Last published" %}: {{ book.last_published }}</p>
+
+{% if book.publishable %}
+    <p>
+    <a href="{% url catalogue_book_xml book.slug %}">{% trans "Full XML" %}</a><br/>
+    <a target="_blank" href="{% url catalogue_book_html book.slug %}">{% trans "HTML version" %}</a><br/>
+    <a href="{% url catalogue_book_txt book.slug %}">{% trans "TXT version" %}</a><br/>
+    {% comment %}
+    <a href="{% url catalogue_book_epub book.slug %}">{% trans "EPUB version" %}</a><br/>
+    <a href="{% url catalogue_book_pdf book.slug %}">{% trans "PDF version" %}</a><br/>
+    {% endcomment %}
+    </p>
+
+    <!--
+    Angel photos:
+    Angels in Ely Cathedral (http://www.flickr.com/photos/21804434@N02/4483220595/) /
+    mira66 (http://www.flickr.com/photos/21804434@N02/) /
+    CC BY 2.0 (http://creativecommons.org/licenses/by/2.0/)
+    -->
+    <form method="POST" action="{% url catalogue_publish book.slug %}">{% csrf_token %}
+        <img src="{{ STATIC_URL }}img/angel-left.png" style="vertical-align: middle" />
+        <button id="publish-button" type="submit">
+            <span>{% trans "Publish" %}</span></button>
+        <img src="{{ STATIC_URL }}img/angel-right.png" style="vertical-align: middle" />
+        </form></p>
+{% else %}
+    {% trans "This book cannot be published yet" %}
+{% endif %}
+
+{% endblock leftcolumn %}
+
+{% block rightcolumn %}
+{% render_comment_list for book %}
+{% render_comment_form for book %}
+
+{% endblock rightcolumn %}
diff --git a/apps/catalogue/templates/catalogue/book_edit.html b/apps/catalogue/templates/catalogue/book_edit.html
new file mode 100755 (executable)
index 0000000..3fffa96
--- /dev/null
@@ -0,0 +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 "Save" %}</button></p>
+       </form>
+{% endblock leftcolumn %}
+
+{% block rightcolumn %}
+{% endblock rightcolumn %}
diff --git a/apps/catalogue/templates/catalogue/chunk_add.html b/apps/catalogue/templates/catalogue/chunk_add.html
new file mode 100755 (executable)
index 0000000..800a7e4
--- /dev/null
@@ -0,0 +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 "Add chunk" %}</button></p>
+       </form>
+{% endblock leftcolumn %}
+
+{% block rightcolumn %}
+{% endblock rightcolumn %}
diff --git a/apps/catalogue/templates/catalogue/chunk_edit.html b/apps/catalogue/templates/catalogue/chunk_edit.html
new file mode 100755 (executable)
index 0000000..3fffa96
--- /dev/null
@@ -0,0 +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 "Save" %}</button></p>
+       </form>
+{% endblock leftcolumn %}
+
+{% block rightcolumn %}
+{% endblock rightcolumn %}
diff --git a/apps/catalogue/templates/catalogue/document_create_missing.html b/apps/catalogue/templates/catalogue/document_create_missing.html
new file mode 100644 (file)
index 0000000..dcb6175
--- /dev/null
@@ -0,0 +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 rightcolumn %}
+{% endblock rightcolumn %}
diff --git a/apps/catalogue/templates/catalogue/document_list.html b/apps/catalogue/templates/catalogue/document_list.html
new file mode 100644 (file)
index 0000000..9a40dfc
--- /dev/null
@@ -0,0 +1,102 @@
+{% extends "catalogue/base.html" %}
+
+{% load i18n %}
+{% load pagination_tags %}
+{% load catalogue %}
+
+{% block extrabody %}
+{{ block.super }}
+<script type="text/javascript" charset="utf-8">
+$(function() {
+       function search(event) {
+        event.preventDefault();
+        var expr = new RegExp(slugify($('#file-list-filter').val()), 'i');
+        $('#file-list tbody tr').hide().filter(function(index) {
+            return expr.test(slugify( $('a', this).attr('data-id') ));
+        }).show();
+    }
+
+    $('#file-list-find-button').click(search).hide();
+       $('#file-list-filter').bind('keyup change DOMAttrModified', search);
+});
+</script>
+{% endblock %}
+
+{% block leftcolumn %}
+       <form method="get" action="#">
+    <table>
+       <thead>
+               <tr><th>Filtr:</th>
+                       <th><input autocomplete="off" name="filter" id="file-list-filter" type="text" size="40" /></th>
+                       <th><input type="reset" value="{% trans "Clear filter" %}" id="file-list-reset-button"/></th>
+                       </tr>
+               </thead>
+               <tbody>
+               </tbody>
+    </table>
+       </form>
+
+
+
+       <form method="get" action="#">
+    <table id="file-list">
+               <tbody>
+        {% autopaginate books 100 %}
+        {% if not books %}
+            <tr><td>{% trans "No books found." %}</td></tr>
+        {% endif %}
+       {% for item in books %}
+            {% with item.book as book %}
+
+            {% ifequal item.book_length 1 %}
+                {% with item.chunks.0 as chunk %}
+                <tr>
+                    <td><a target="_blank" href="{% url catalogue_book book.slug %}">[B]</a></td>
+                    <td><a href="{% url catalogue_chunk_edit book.slug chunk.slug %}">[c]</a></td>
+                    <td><a target="_blank"
+                                href="{% url wiki_editor book.slug %}">
+                                {{ book.title }}</a></td>
+                    <td>({{ chunk.stage }})</td>
+                    <td>{% if chunk.user %}<a href="{% url catalogue_user chunk.user.username %}">{{ chunk.user.first_name }} {{ chunk.user.last_name }}</a>{% endif %}</td>
+                </tr>
+                {% endwith %}
+            {% else %}
+                <tr>
+                    <td><a target="_blank" href="{% url catalogue_book book.slug %}">[B]</a></td>
+                    <td></td>
+                    <td>{{ book.title }}</td>
+                </tr>
+                {% for chunk in item.chunks %}
+                    <tr>
+                        <td></td>
+                        <td><a href="{% url catalogue_chunk_edit book.slug chunk.slug %}">[c]</a></td>
+                        <td><a target="_blank" href="{{ chunk.get_absolute_url }}">
+                                <span class='chunkno'>{{ chunk.number }}.</span>
+                                {{ chunk.comment }}</a></td>
+                        <td>({{ chunk.stage }})</td>
+                        <td>{% if chunk.user %}<a href="{% url catalogue_user chunk.user.username %}">{{ chunk.user.first_name }} {{ chunk.user.last_name }}</a>{% endif %}</td>
+                    </td></tr>
+                {% endfor %}
+            {% endifequal %}
+            {% endwith %}
+       {% endfor %}
+        <tr><td colspan="3">{% paginate %}</td></tr>
+               </tbody>
+    </table>
+       </form>
+{% endblock leftcolumn %}
+
+{% block rightcolumn %}
+       <div id="last-edited-list">
+               <h2>{% trans "Your last edited documents" %}</h2>
+           <ol>
+                       {% for slugs, item in last_books %}
+                       <li><a href="{% url wiki_editor slugs.0 slugs.1 %}"
+                               target="_blank">{{ item.title }}</a><br/><span class="date">({{ item.time|date:"H:i:s, d/m/Y" }})</span></li>
+                       {% endfor %}
+               </ol>
+       </div>
+
+    <h2>{% trans "Recent activity" %}</h2>
+    {% wall %}
+{% endblock rightcolumn %}
diff --git a/apps/catalogue/templates/catalogue/document_upload.html b/apps/catalogue/templates/catalogue/document_upload.html
new file mode 100644 (file)
index 0000000..87e93e0
--- /dev/null
@@ -0,0 +1,69 @@
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+
+{% block leftcolumn %}
+
+
+<h2>{% trans "Bulk documents upload" %}</h2>
+
+<p>
+{% trans "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with <code>.xml</code> will be ignored." %}
+</p>
+
+<form enctype="multipart/form-data" method="POST" action="">
+{% csrf_token %}
+{{ form.as_p }}
+<p><button type="submit">{% trans "Upload" %}</button></p>
+</form>
+
+<hr/>
+
+{% if error_list %}
+
+    <p class='error'>{% trans "There have been some errors. No files have been added to the repository." %}
+    <h3>{% trans "Offending files" %}</h3>
+    <ul id='error-list'>
+        {% for filename, title, error in error_list %}
+            <li>{{ title }} (<code>{{ filename }}</code>): {{ error }}</li>
+        {% endfor %}
+    </ul>
+
+    {% if ok_list %}
+    <h3>{% trans "Correct files" %}</h3>
+        <ul>
+            {% for filename, slug, title in ok_list %}
+                <li>{{ title }} (<code>{{ filename }}</code>)</li>
+            {% endfor %}
+        </ul>
+    {% endif %}
+
+{% else %}
+
+    {% if ok_list %}
+        <p class='success'>{% trans "Files have been successfully uploaded to the repository." %}</p>
+        <h3>{% trans "Uploaded files" %}</h3>
+        <ul id='ok-list'>
+        {% for filename, slug, title in ok_list %}
+            <li><a href='{% url wiki_editor slug %}'>{{ title }}</a> (<code>{{ filename }})</a></li>
+        {% endfor %}
+        </ul>
+    {% endif %}
+{% endif %}
+
+{% if skipped_list %}
+    <h3>{% trans "Skipped files" %}</h3>
+    <p>{% trans "Files skipped due to no <code>.xml</code> extension" %}</p>
+    <ul id='skipped-list'>
+        {% for filename in skipped_list %}
+            <li>{{ filename }}</li>
+        {% endfor %}
+    </ul>
+{% endif %}
+
+
+{% endblock leftcolumn %}
+
+
+{% block rightcolumn %}
+{% endblock rightcolumn %}
diff --git a/apps/catalogue/templates/catalogue/main_tabs.html b/apps/catalogue/templates/catalogue/main_tabs.html
new file mode 100755 (executable)
index 0000000..82321cc
--- /dev/null
@@ -0,0 +1,3 @@
+{% for tab in tabs %}
+    <a {% ifequal active_tab tab.slug %}class="active" {% endifequal %}href="{{ tab.url }}">{{ tab.caption }}</a>
+{% endfor %}
diff --git a/apps/catalogue/templates/catalogue/user_list.html b/apps/catalogue/templates/catalogue/user_list.html
new file mode 100755 (executable)
index 0000000..9e1e83e
--- /dev/null
@@ -0,0 +1,18 @@
+{% extends "catalogue/base.html" %}
+
+{% load i18n %}
+
+{% block leftcolumn %}
+
+<h1>{% trans "Users" %}</h1>
+
+<ul>
+{% for user in users %}
+    <li><a href="{% url catalogue_user user.username %}">
+        <span class="chunkno">{{ forloop.counter }}.</span>
+        {{ user.first_name }} {{ user.last_name }}</a>
+        ({{ user.count }})</li>
+{% endfor %}
+</ul>
+
+{% endblock leftcolumn %}
diff --git a/apps/catalogue/templates/catalogue/wall.html b/apps/catalogue/templates/catalogue/wall.html
new file mode 100755 (executable)
index 0000000..0e4597e
--- /dev/null
@@ -0,0 +1,32 @@
+{% load i18n %}
+{% load gravatar %}
+
+<ul class='wall' style='padding-left: 0; list-style: none;'>
+{% for item in wall %}
+    <li style='clear: left; border-top: 1px dotted gray;  padding-bottom:1em; margin-bottom: 1em;'>
+        <div style='float: left;margin-right: 1em;'>
+            {% if item.get_email %}
+                {% gravatar_img_for_email item.get_email 32 %}
+                <br/>
+            {% endif %}
+
+            <!--img src='{{ STATIC_URL }}img/wall/{{ item.tag}}.png' alt='{% trans item.tag %}' /-->
+        </div>
+
+        {{ item.timestamp }}
+        <br/>
+        {% if item.user %}
+            <a href="{% url catalogue_user item.user.username %}">
+            {{ item.user.first_name }} {{ item.user.last_name }}</a>
+            &lt;{{ item.user.email }}>
+        {% else %}
+            {{ item.user_name }}
+            {% if item.get_email %}
+                &lt;{{ item.get_email }}>
+            {% endif %}
+        {% endif %}
+        <br/><a target="_blank" href='{{ item.url }}'>{{ item.title }}</a>
+        <br/>{{ item.summary }}
+        </li>
+{% endfor %}
+</ul>
diff --git a/apps/catalogue/templatetags/__init__.py b/apps/catalogue/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/catalogue/templatetags/catalogue.py b/apps/catalogue/templatetags/catalogue.py
new file mode 100644 (file)
index 0000000..cdb1940
--- /dev/null
@@ -0,0 +1,141 @@
+from __future__ import absolute_import
+
+from django.db.models import Count
+from django.core.urlresolvers import reverse
+from django.contrib.comments.models import Comment
+from django.template.defaultfilters import stringfilter
+from django import template
+from django.utils.translation import ugettext as _
+
+from catalogue.models import Book, Chunk
+
+register = template.Library()
+
+
+class Tab(object):
+    slug = None
+    caption = None
+    url = None
+
+    def __init__(self, slug, caption, url):
+        self.slug = slug
+        self.caption = caption
+        self.url = url
+
+
+@register.inclusion_tag("catalogue/main_tabs.html", takes_context=True)
+def main_tabs(context):
+    active = getattr(context['request'], 'catalogue_active_tab', None)
+
+    tabs = []
+    user = context['user']
+    if user.is_authenticated():
+        tabs.append(Tab('my', _('My page'), reverse("catalogue_user")))
+
+    tabs.append(Tab('unassigned', _('Unassigned'), reverse("catalogue_unassigned")))
+    tabs.append(Tab('users', _('Users'), reverse("catalogue_users")))
+    tabs.append(Tab('create', _('Add'), reverse("catalogue_create_missing")))
+    tabs.append(Tab('upload', _('Upload'), reverse("catalogue_upload")))
+
+    if user.is_staff:
+        tabs.append(Tab('admin', _('Admin'), reverse("admin:index")))
+
+    return {"tabs": tabs, "active_tab": active}
+
+
+class WallItem(object):
+    title = ''
+    summary = ''
+    url = ''
+    timestamp = ''
+    user = None
+    email = ''
+
+    def __init__(self, tag):
+        self.tag = tag
+
+    def get_email(self):
+        if self.user:
+            return self.user.email
+        else:
+            return self.email
+
+
+def changes_wall(max_len):
+    qs = Chunk.change_model.objects.filter(revision__gt=-1).order_by('-created_at')
+    qs = qs.defer('patch')
+    qs = qs.select_related('author', 'tree', 'tree__book__title')
+    qs = qs[:max_len]
+    for item in qs:
+        tag = 'stage' if item.tags.count() else 'change'
+        chunk = item.tree
+        w  = WallItem(tag)
+        w.title = chunk.pretty_name()
+        w.summary = item.description
+        w.url = reverse('wiki_editor', 
+                args=[chunk.book.slug, chunk.slug]) + '?diff=%d' % item.revision
+        w.timestamp = item.created_at
+        w.user = item.author
+        w.email = item.author_email
+        yield w
+
+
+def published_wall(max_len):
+    qs = Book.objects.exclude(last_published=None).order_by('-last_published')
+    qs = qs[:max_len]
+    for item in qs:
+        w  = WallItem('publish')
+        w.title = item.title
+        w.summary = item.title
+        w.url = chunk.book.get_absolute_url()
+        w.timestamp = item.last_published
+        w.user = item.last_published_by
+        yield w
+
+
+def comments_wall(max_len):
+    qs = Comment.objects.filter(is_public=True).select_related().order_by('-submit_date')
+    qs = qs[:max_len]
+    for item in qs:
+        w  = WallItem('comment')
+        w.title = item.content_object
+        w.summary = item.comment
+        w.url = item.content_object.get_absolute_url()
+        w.timestamp = item.submit_date
+        w.user = item.user
+        w.email = item.user_email
+        yield w
+
+
+def big_wall(max_len, *args):
+    """
+        Takes some WallItem iterators and zips them into one big wall.
+        Input iterators must already be sorted by timestamp.
+    """
+    subwalls = []
+    for w in args:
+        try:
+            subwalls.append([next(w), w])
+        except StopIteration:
+            pass
+
+    while max_len and subwalls:
+        i, next_item = max(enumerate(subwalls), key=lambda x: x[1][0].timestamp)
+        yield next_item[0]
+        max_len -= 1
+        try:
+            next_item[0] = next(next_item[1])
+        except StopIteration:
+            del subwalls[i]
+
+
+@register.inclusion_tag("catalogue/wall.html", takes_context=True)
+def wall(context, max_len=10):
+    return {
+        "request": context['request'],
+        "STATIC_URL": context['STATIC_URL'],
+        "wall": big_wall(max_len,
+            changes_wall(max_len),
+            published_wall(max_len),
+            comments_wall(max_len),
+        )}
diff --git a/apps/catalogue/tests.py b/apps/catalogue/tests.py
new file mode 100644 (file)
index 0000000..6577737
--- /dev/null
@@ -0,0 +1,19 @@
+from nose.tools import *
+import wiki.models as models
+import shutil
+import tempfile
+
+
+class TestStorageBase:
+    def setUp(self):
+        self.dirpath = tempfile.mkdtemp(prefix='nosetest_')
+
+    def tearDown(self):
+        shutil.rmtree(self.dirpath)
+
+
+class TestDocumentStorage(TestStorageBase):
+
+    def test_storage_empty(self):
+        storage = models.DocumentStorage(self.dirpath)
+        eq_(storage.all(), [])
diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py
new file mode 100644 (file)
index 0000000..93c02b1
--- /dev/null
@@ -0,0 +1,41 @@
+# -*- coding: utf-8
+from django.conf.urls.defaults import *
+from django.views.generic.simple import redirect_to
+
+
+urlpatterns = patterns('catalogue.views',
+    url(r'^$', redirect_to, {'url': 'catalogue/'}),
+
+    url(r'^catalogue/$', 'document_list', name='catalogue_document_list'),
+    url(r'^unassigned/$', 'unassigned', name='catalogue_unassigned'),
+    url(r'^user/$', 'my', name='catalogue_user'),
+    url(r'^user/(?P<username>[^/]+)/$', 'user', name='catalogue_user'),
+    url(r'^users/$', 'users', name='catalogue_users'),
+
+    url(r'^upload/$',
+        'upload', name='catalogue_upload'),
+
+    url(r'^create/(?P<slug>[^/]*)/',
+        'create_missing', name='catalogue_create_missing'),
+    url(r'^create/',
+        'create_missing', name='catalogue_create_missing'),
+
+    url(r'^book/(?P<slug>[^/]+)/publish$', 'publish', name="catalogue_publish"),
+    #url(r'^(?P<name>[^/]+)/publish/(?P<version>\d+)$', 'publish', name="catalogue_publish"),
+
+    url(r'^book/(?P<slug>[^/]+)/$', 'book', name="catalogue_book"),
+    url(r'^book/(?P<slug>[^/]+)/xml$', 'book_xml', name="catalogue_book_xml"),
+    url(r'^book/(?P<slug>[^/]+)/txt$', 'book_txt', name="catalogue_book_txt"),
+    url(r'^book/(?P<slug>[^/]+)/html$', 'book_html', name="catalogue_book_html"),
+    #url(r'^book/(?P<slug>[^/]+)/epub$', 'book_epub', name="catalogue_book_epub"),
+    #url(r'^book/(?P<slug>[^/]+)/pdf$', 'book_pdf', name="catalogue_book_pdf"),
+    url(r'^chunk_add/(?P<slug>[^/]+)/(?P<chunk>[^/]+)/$',
+        'chunk_add', name="catalogue_chunk_add"),
+    url(r'^chunk_edit/(?P<slug>[^/]+)/(?P<chunk>[^/]+)/$',
+        'chunk_edit', name="catalogue_chunk_edit"),
+    url(r'^book_append/(?P<slug>[^/]+)/$',
+        'book_append', name="catalogue_book_append"),
+    url(r'^book_edit/(?P<slug>[^/]+)/$',
+        'book_edit', name="catalogue_book_edit"),
+
+)
diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py
new file mode 100644 (file)
index 0000000..a44c7e8
--- /dev/null
@@ -0,0 +1,418 @@
+from datetime import datetime
+import logging
+import os
+from StringIO import StringIO
+
+from django.contrib import auth
+from django.contrib.auth.models import User
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+from django.db.models import Count
+from django import http
+from django.http import Http404
+from django.shortcuts import get_object_or_404
+from django.utils.http import urlquote_plus
+from django.utils.translation import ugettext_lazy as _
+from django.views.decorators.http import require_POST
+from django.views.generic.simple import direct_to_template
+
+import librarian.html
+import librarian.text
+
+from apiclient import api_call
+from catalogue import forms
+from catalogue import helpers
+from catalogue.helpers import active_tab
+from catalogue.models import Book, Chunk
+from catalogue import xml_tools
+
+#
+# Quick hack around caching problems, TODO: use ETags
+#
+from django.views.decorators.cache import never_cache
+
+logger = logging.getLogger("fnp.catalogue")
+
+
+@active_tab('all')
+@never_cache
+def document_list(request):
+    chunks_list = helpers.ChunksList(Chunk.objects.order_by(
+        'book__title', 'book', 'number'))
+
+    return direct_to_template(request, 'catalogue/document_list.html', extra_context={
+        'books': chunks_list,
+        #'books': [helpers.BookChunks(b) for b in Book.objects.all().select_related()],
+        'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
+                        key=lambda x: x[1]['time'], reverse=True),
+    })
+
+
+@active_tab('unassigned')
+@never_cache
+def unassigned(request):
+    chunks_list = helpers.ChunksList(Chunk.objects.filter(
+        user=None).order_by('book__title', 'book__id', 'number'))
+
+    return direct_to_template(request, 'catalogue/document_list.html', extra_context={
+        'books': chunks_list,
+        'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
+                        key=lambda x: x[1]['time'], reverse=True),
+    })
+
+
+@never_cache
+def user(request, username=None):
+    if username is None:
+        if request.user.is_authenticated():
+            user = request.user
+        else:
+            raise Http404
+    else:
+        user = get_object_or_404(User, username=username)
+
+    chunks_list = helpers.ChunksList(Chunk.objects.filter(
+        user=user).order_by('book__title', 'book', 'number'))
+
+    return direct_to_template(request, 'catalogue/document_list.html', extra_context={
+        'books': chunks_list,
+        'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
+                        key=lambda x: x[1]['time'], reverse=True),
+    })
+my = login_required(active_tab('my')(user))
+
+
+@active_tab('users')
+def users(request):
+    return direct_to_template(request, 'catalogue/user_list.html', extra_context={
+        'users': User.objects.all().annotate(count=Count('chunk')).order_by(
+            '-count', 'last_name', 'first_name'),
+    })
+
+
+@never_cache
+def logout_then_redirect(request):
+    auth.logout(request)
+    return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
+
+
+@active_tab('create')
+def create_missing(request, slug=None):
+    if slug is None:
+        slug = ''
+    slug = slug.replace(' ', '-')
+
+    if request.method == "POST":
+        form = forms.DocumentCreateForm(request.POST, request.FILES)
+        if form.is_valid():
+            
+            if request.user.is_authenticated():
+                creator = request.user
+            else:
+                creator = None
+            book = Book.create(creator=creator,
+                slug=form.cleaned_data['slug'],
+                title=form.cleaned_data['title'],
+                text=form.cleaned_data['text'],
+            )
+
+            return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
+    else:
+        form = forms.DocumentCreateForm(initial={
+                "slug": slug,
+                "title": slug.replace('-', ' ').title(),
+        })
+
+    return direct_to_template(request, "catalogue/document_create_missing.html", extra_context={
+        "slug": slug,
+        "form": form,
+    })
+
+
+@active_tab('upload')
+def upload(request):
+    if request.method == "POST":
+        form = forms.DocumentsUploadForm(request.POST, request.FILES)
+        if form.is_valid():
+            import slughifi
+
+            if request.user.is_authenticated():
+                creator = request.user
+            else:
+                creator = None
+
+            zip = form.cleaned_data['zip']
+            skipped_list = []
+            ok_list = []
+            error_list = []
+            slugs = {}
+            existing = [book.slug for book in Book.objects.all()]
+            for filename in zip.namelist():
+                if filename[-1] == '/':
+                    continue
+                title = os.path.basename(filename)[:-4]
+                slug = slughifi(title)
+                if not (slug and filename.endswith('.xml')):
+                    skipped_list.append(filename)
+                elif slug in slugs:
+                    error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
+                elif slug in existing:
+                    error_list.append((filename, slug, _('Slug already used in repository.')))
+                else:
+                    try:
+                        zip.read(filename).decode('utf-8') # test read
+                        ok_list.append((filename, slug, title))
+                    except UnicodeDecodeError:
+                        error_list.append((filename, title, _('File should be UTF-8 encoded.')))
+                    slugs[slug] = filename
+
+            if not error_list:
+                for filename, slug, title in ok_list:
+                    Book.create(creator=creator,
+                        slug=slug,
+                        title=title,
+                        text=zip.read(filename).decode('utf-8'),
+                    )
+
+            return direct_to_template(request, "catalogue/document_upload.html", extra_context={
+                "form": form,
+                "ok_list": ok_list,
+                "skipped_list": skipped_list,
+                "error_list": error_list,
+            })
+    else:
+        form = forms.DocumentsUploadForm()
+
+    return direct_to_template(request, "catalogue/document_upload.html", extra_context={
+        "form": form,
+    })
+
+
+@never_cache
+def book_xml(request, slug):
+    xml = get_object_or_404(Book, slug=slug).materialize()
+
+    response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
+    response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
+    return response
+
+
+@never_cache
+def book_txt(request, slug):
+    xml = get_object_or_404(Book, slug=slug).materialize()
+    output = StringIO()
+    # errors?
+    librarian.text.transform(StringIO(xml), output)
+    text = output.getvalue()
+    response = http.HttpResponse(text, content_type='text/plain', mimetype='text/plain')
+    response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug
+    return response
+
+
+@never_cache
+def book_html(request, slug):
+    xml = get_object_or_404(Book, slug=slug).materialize()
+    output = StringIO()
+    # errors?
+    librarian.html.transform(StringIO(xml), output, parse_dublincore=False,
+                             flags=['full-page'])
+    html = output.getvalue()
+    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
+    return http.HttpResponse(str(doc.revision()))
+
+
+def book(request, slug):
+    book = get_object_or_404(Book, slug=slug)
+
+    # TODO: most of this should go somewhere else
+
+    # do we need some automation?
+    first_master = None
+    chunks = []
+    need_fixing = False
+    choose_master = False
+
+    length = book.chunk_set.count()
+    for i, chunk in enumerate(book):
+        chunk_dict = {
+            "chunk": chunk,
+            "fix": [],
+            "grade": ""
+            }
+        graded = xml_tools.GradedText(chunk.materialize())
+        if graded.is_wl():
+            master = graded.master()
+            if first_master is None:
+                first_master = master
+            elif master != first_master:
+                chunk_dict['fix'].append('bad-master')
+
+            if i > 0 and not graded.has_trim_begin():
+                chunk_dict['fix'].append('trim-begin')
+            if i < length - 1 and not graded.has_trim_end():
+                chunk_dict['fix'].append('trim-end')
+
+            if chunk_dict['fix']:
+                chunk_dict['grade'] = 'wl-fix'
+            else:
+                chunk_dict['grade'] = 'wl'
+
+        elif graded.is_broken_wl():
+            chunk_dict['grade'] = 'wl-broken'
+        elif graded.is_xml():
+            chunk_dict['grade'] = 'xml'
+        else:
+            chunk_dict['grade'] = 'plain'
+            chunk_dict['fix'].append('wl')
+            choose_master = True
+
+        if chunk_dict['fix']:
+            need_fixing = True
+        chunks.append(chunk_dict)
+
+    if first_master or not need_fixing:
+        choose_master = False
+
+    if request.method == "POST":
+        form = forms.ChooseMasterForm(request.POST)
+        if not choose_master or form.is_valid():
+            if choose_master:
+                first_master = form.cleaned_data['master']
+
+            # do the actual fixing
+            for c in chunks:
+                if not c['fix']:
+                    continue
+
+                text = c['chunk'].materialize()
+                for fix in c['fix']:
+                    if fix == 'bad-master':
+                        text = xml_tools.change_master(text, first_master)
+                    elif fix == 'trim-begin':
+                        text = xml_tools.add_trim_begin(text)
+                    elif fix == 'trim-end':
+                        text = xml_tools.add_trim_end(text)
+                    elif fix == 'wl':
+                        text = xml_tools.basic_structure(text, first_master)
+                author = request.user if request.user.is_authenticated() else None
+                description = "auto-fix: " + ", ".join(c['fix'])
+                c['chunk'].commit(text=text, author=author, 
+                    description=description)
+
+            return http.HttpResponseRedirect(book.get_absolute_url())
+    elif choose_master:
+        form = forms.ChooseMasterForm()
+    else:
+        form = None
+
+    return direct_to_template(request, "catalogue/book_detail.html", extra_context={
+        "book": book,
+        "chunks": chunks,
+        "need_fixing": need_fixing,
+        "choose_master": choose_master,
+        "first_master": first_master,
+        "form": form,
+    })
+
+
+def chunk_add(request, slug, chunk):
+    try:
+        doc = Chunk.get(slug, chunk)
+    except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
+        raise Http404
+
+    if request.method == "POST":
+        form = forms.ChunkAddForm(request.POST, instance=doc)
+        if form.is_valid():
+            if request.user.is_authenticated():
+                creator = request.user
+            else:
+                creator = None
+            doc.split(creator=creator,
+                slug=form.cleaned_data['slug'],
+                comment=form.cleaned_data['comment'],
+            )
+
+            return http.HttpResponseRedirect(doc.book.get_absolute_url())
+    else:
+        form = forms.ChunkAddForm(initial={
+                "slug": str(doc.number + 1),
+                "comment": "cz. %d" % (doc.number + 1, ),
+        })
+
+    return direct_to_template(request, "catalogue/chunk_add.html", extra_context={
+        "chunk": doc,
+        "form": form,
+    })
+
+
+def chunk_edit(request, slug, chunk):
+    try:
+        doc = Chunk.get(slug, chunk)
+    except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
+        raise Http404
+    if request.method == "POST":
+        form = forms.ChunkForm(request.POST, instance=doc)
+        if form.is_valid():
+            form.save()
+            return http.HttpResponseRedirect(doc.book.get_absolute_url())
+    else:
+        form = forms.ChunkForm(instance=doc)
+    return direct_to_template(request, "catalogue/chunk_edit.html", extra_context={
+        "chunk": doc,
+        "form": form,
+    })
+
+
+def book_append(request, slug):
+    book = get_object_or_404(Book, slug=slug)
+    if request.method == "POST":
+        form = forms.BookAppendForm(request.POST)
+        if form.is_valid():
+            append_to = form.cleaned_data['append_to']
+            append_to.append(book)
+            return http.HttpResponseRedirect(append_to.get_absolute_url())
+    else:
+        form = forms.BookAppendForm()
+    return direct_to_template(request, "catalogue/book_append_to.html", extra_context={
+        "book": book,
+        "form": form,
+    })
+
+
+def book_edit(request, slug):
+    book = get_object_or_404(Book, slug=slug)
+    if request.method == "POST":
+        form = forms.BookForm(request.POST, instance=book)
+        if form.is_valid():
+            form.save()
+            return http.HttpResponseRedirect(book.get_absolute_url())
+    else:
+        form = forms.BookForm(instance=book)
+    return direct_to_template(request, "catalogue/book_edit.html", extra_context={
+        "book": book,
+        "form": form,
+    })
+
+
+@require_POST
+@login_required
+def publish(request, slug):
+    book = get_object_or_404(Book, slug=slug)
+    try:
+        ret = api_call(request.user, "books", {"book_xml": book.materialize()})
+    except BaseException, e:
+        return http.HttpResponse(e)
+    else:
+        book.last_published = datetime.now()
+        book.save()
+        return http.HttpResponseRedirect(book.get_absolute_url())
diff --git a/apps/catalogue/xml_tools.py b/apps/catalogue/xml_tools.py
new file mode 100755 (executable)
index 0000000..928e57b
--- /dev/null
@@ -0,0 +1,204 @@
+from functools import wraps
+import re
+
+from lxml import etree
+from catalogue.constants import TRIM_BEGIN, TRIM_END, MASTERS
+
+RE_TRIM_BEGIN = re.compile("^<!--%s-->$" % TRIM_BEGIN, re.M)
+RE_TRIM_END = re.compile("^<!--%s-->$" % TRIM_END, re.M)
+
+
+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
+        that eg. one big XML file can be compiled from many small XML files.
+    """
+    if trim_begin:
+        text = RE_TRIM_BEGIN.split(text, maxsplit=1)[-1]
+    if trim_end:
+        text = RE_TRIM_END.split(text, maxsplit=1)[0]
+    return text
+
+
+def compile_text(parts):
+    """ 
+        Compiles full text from an iterable of parts,
+        trimming where applicable.
+    """
+    texts = []
+    trim_begin = False
+    text = ''
+    for next_text in parts:
+        if not next_text:
+            continue
+        # trim the end, because there's more non-empty text
+        # don't trim beginning, if `text' is the first non-empty part
+        texts.append(_trim(text, trim_begin=trim_begin))
+        trim_begin = True
+        text = next_text
+    # don't trim the end, because there's no more text coming after `text'
+    # only trim beginning if it's not still the first non-empty
+    texts.append(_trim(text, trim_begin=trim_begin, trim_end=False))
+    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 etree.tostring(e, encoding="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 etree.tostring(e, encoding="utf-8")
+
+
+def add_trim_begin(text):
+    trim_tag = etree.Comment(TRIM_BEGIN)
+    e = etree.fromstring(text)
+    for master in e[::-1]:
+        if master.tag in MASTERS:
+            break
+    if master.tag not in MASTERS:
+        raise ParseError('No master tag found!')
+
+    master.insert(0, trim_tag)
+    trim_tag.tail = '\n\n\n' + (master.text or '')
+    master.text = '\n'
+    return etree.tostring(e, encoding="utf-8")
+
+
+def add_trim_end(text):
+    trim_tag = etree.Comment(TRIM_END)
+    e = etree.fromstring(text)
+    for master in e[::-1]:
+        if master.tag in MASTERS:
+            break
+    if master.tag not in MASTERS:
+        raise ParseError('No master tag found!')
+
+    master.append(trim_tag)
+    trim_tag.tail = '\n'
+    prev = trim_tag.getprevious()
+    if prev is not None:
+        prev.tail = (prev.tail or '') + '\n\n\n'
+    else:
+        master.text = (master.text or '') + '\n\n\n'
+    return etree.tostring(e, encoding="utf-8")
index b742f70..1a61b66 100644 (file)
@@ -2,12 +2,4 @@ from django.contrib import admin
 
 from wiki import models
 
-class BookAdmin(admin.ModelAdmin):
-    prepopulated_fields = {'slug': ['title']}
-
-
-admin.site.register(models.Book, BookAdmin)
-admin.site.register(models.Chunk)
 admin.site.register(models.Theme)
-
-admin.site.register(models.Chunk.tag_model)
diff --git a/apps/wiki/constants.py b/apps/wiki/constants.py
deleted file mode 100644 (file)
index d75d6b4..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-# -*- coding: utf-8 -*-
-
-TRIM_BEGIN = " TRIM_BEGIN "
-TRIM_END = " TRIM_END "
-
-MASTERS = ['powiesc',
-           'opowiadanie',
-           'liryka_l',
-           'liryka_lp',
-           'dramat_wierszowany_l',
-           'dramat_wierszowany_lp',
-           'dramat_wspolczesny',
-           ]
diff --git a/apps/wiki/fixtures/stages.json b/apps/wiki/fixtures/stages.json
deleted file mode 100644 (file)
index 992ebc7..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-[
-    {
-        "pk": 1, 
-        "model": "wiki.chunktag", 
-        "fields": {
-            "ordering": 1, 
-            "name": "Autokorekta", 
-            "slug": "first_correction"
-        }
-    }, 
-    {
-        "pk": 2, 
-        "model": "wiki.chunktag", 
-        "fields": {
-            "ordering": 2, 
-            "name": "Tagowanie", 
-            "slug": "tagging"
-        }
-    }, 
-    {
-        "pk": 3, 
-        "model": "wiki.chunktag", 
-        "fields": {
-            "ordering": 3, 
-            "name": "Korekta", 
-            "slug": "proofreading"
-        }
-    }, 
-    {
-        "pk": 4, 
-        "model": "wiki.chunktag", 
-        "fields": {
-            "ordering": 4, 
-            "name": "Sprawdzenie przypis\u00f3w \u017ar\u00f3d\u0142a", 
-            "slug": "annotation-proofreading"
-        }
-    }, 
-    {
-        "pk": 5, 
-        "model": "wiki.chunktag", 
-        "fields": {
-            "ordering": 5, 
-            "name": "Uwsp\u00f3\u0142cze\u015bnienie", 
-            "slug": "modernisation"
-        }
-    }, 
-    {
-        "pk": 6, 
-        "model": "wiki.chunktag", 
-        "fields": {
-            "ordering": 6, 
-            "name": "Przypisy", 
-            "slug": "annotations"
-        }
-    }, 
-    {
-        "pk": 7, 
-        "model": "wiki.chunktag", 
-        "fields": {
-            "ordering": 7, 
-            "name": "Motywy", 
-            "slug": "themes"
-        }
-    }, 
-    {
-        "pk": 8, 
-        "model": "wiki.chunktag", 
-        "fields": {
-            "ordering": 8, 
-            "name": "Ostateczna redakcja literacka", 
-            "slug": "editor-proofreading"
-        }
-    }, 
-    {
-        "pk": 9, 
-        "model": "wiki.chunktag", 
-        "fields": {
-            "ordering": 9, 
-            "name": "Ostateczna redakcja techniczna", 
-            "slug": "technical-editor-proofreading"
-        }
-    }
-]
index 6b10909..a8a57c8 100644 (file)
@@ -3,22 +3,10 @@
 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from django.contrib.auth.models import User
-from django.db.models import Count
 from django import forms
 from django.utils.translation import ugettext_lazy as _
 
-from wiki.constants import MASTERS
-from wiki.models import Book, Chunk
-
-class DocumentTagForm(forms.Form):
-    """
-        Form for tagging revisions.
-    """
-
-    id = forms.CharField(widget=forms.HiddenInput)
-    tag = forms.ModelChoiceField(queryset=Chunk.tag_model.objects.all())
-    revision = forms.IntegerField(widget=forms.HiddenInput)
+from catalogue.models import Chunk
 
 
 class DocumentPubmarkForm(forms.Form):
@@ -32,54 +20,6 @@ class DocumentPubmarkForm(forms.Form):
     revision = forms.IntegerField(widget=forms.HiddenInput)
 
 
-class DocumentCreateForm(forms.ModelForm):
-    """
-        Form used for creating new documents.
-    """
-    file = forms.FileField(required=False)
-    text = forms.CharField(required=False, widget=forms.Textarea)
-
-    class Meta:
-        model = Book
-        exclude = ['gallery', 'parent', 'parent_number']
-        prepopulated_fields = {'slug': ['title']}
-
-    def clean(self):
-        super(DocumentCreateForm, self).clean()
-        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.")
-
-        if not self.cleaned_data["text"]:
-            raise forms.ValidationError("You must either enter text or upload a file")
-
-        return self.cleaned_data
-
-
-class DocumentsUploadForm(forms.Form):
-    """
-        Form used for uploading new documents.
-    """
-    file = forms.FileField(required=True, label=_('ZIP file'))
-
-    def clean(self):
-        file = self.cleaned_data['file']
-
-        import zipfile
-        try:
-            z = self.cleaned_data['zip'] = zipfile.ZipFile(file)
-        except zipfile.BadZipfile:
-            raise forms.ValidationError("Should be a ZIP file.")
-        if z.testzip():
-            raise forms.ValidationError("ZIP file corrupt.")
-
-        return self.cleaned_data
-
-
 class DocumentTextSaveForm(forms.Form):
     """
     Form for saving document's text:
@@ -149,70 +89,3 @@ class DocumentTextRevertForm(forms.Form):
         label=_(u"Your comments"),
         help_text=_(u"Describe the reason for reverting."),
     )
-
-
-class ChunkForm(forms.ModelForm):
-    """
-        Form used for editing a chunk.
-    """
-    user = forms.ModelChoiceField(queryset=
-        User.objects.annotate(count=Count('chunk')).
-        order_by('-count', 'last_name', 'first_name'))
-
-
-    class Meta:
-        model = Chunk
-        exclude = ['number']
-
-    def clean_slug(self):
-        slug = self.cleaned_data['slug']
-        try:
-            chunk = Chunk.objects.get(book=self.instance.book, slug=slug)
-        except Chunk.DoesNotExist:
-            return slug
-        if chunk == self.instance:
-            return slug
-        raise forms.ValidationError(_('Chunk with this slug already exists'))
-
-
-class ChunkAddForm(ChunkForm):
-    """
-        Form used for adding a chunk to a document.
-    """
-
-    def clean_slug(self):
-        slug = self.cleaned_data['slug']
-        try:
-            user = Chunk.objects.get(book=self.instance.book, slug=slug)
-        except Chunk.DoesNotExist:
-            return slug
-        raise forms.ValidationError(_('Chunk with this slug already exists'))
-
-
-
-
-class BookAppendForm(forms.Form):
-    """
-        Form for appending a book to another book.
-        It means moving all chunks from book A to book B and deleting A.
-    """
-
-    append_to = forms.ModelChoiceField(queryset=Book.objects.all(),
-        label=_("Append to"))
-
-
-class BookForm(forms.ModelForm):
-    """
-        Form used for editing a Book.
-    """
-
-    class Meta:
-        model = Book
-
-
-class ChooseMasterForm(forms.Form):
-    """
-        Form used for fixing the chunks in a book.
-    """
-
-    master = forms.ChoiceField(choices=((m, m) for m in MASTERS))
index 3e0267e..dace3d0 100644 (file)
@@ -1,9 +1,9 @@
+from datetime import datetime
+from functools import wraps
+
 from django import http
-from django.db.models import Count
 from django.utils import simplejson as json
 from django.utils.functional import Promise
-from datetime import datetime
-from functools import wraps
 
 
 class ExtendedEncoder(json.JSONEncoder):
@@ -59,136 +59,3 @@ def ajax_require_permission(permission):
             return view(request, *args, **kwargs)
         return authorized_view
     return decorator
-
-import collections
-
-def recursive_groupby(iterable):
-    """
-#    >>> recursive_groupby([1,2,3,4,5])
-#    [1, 2, 3, 4, 5]
-
-    >>> recursive_groupby([[1]])
-    [1]
-
-    >>> recursive_groupby([('a', 1),('a', 2), 3, ('b', 4), 5])
-    ['a', [1, 2], 3, 'b', [4], 5]
-
-    >>> recursive_groupby([('a', 'x', 1),('a', 'x', 2), ('a', 'x', 3)])
-    ['a', ['x', [1, 2, 3]]]
-
-    """
-
-    def _generator(iterator):
-        group = None
-        grouper = None
-
-        for item in iterator:
-            if not isinstance(item, collections.Sequence):
-                if grouper is not None:
-                    yield grouper
-                    if len(group):
-                        yield recursive_groupby(group)
-                    group = None
-                    grouper = None
-                yield item
-                continue
-            elif len(item) == 1:
-                if grouper is not None:
-                    yield grouper
-                    if len(group):
-                        yield recursive_groupby(group)
-                    group = None
-                    grouper = None
-                yield item[0]
-                continue
-            elif not len(item):
-                continue
-
-            if grouper is None:
-                group = [item[1:]]
-                grouper = item[0]
-                continue
-
-            if grouper != item[0]:
-                if grouper is not None:
-                    yield grouper
-                    if len(group):
-                        yield recursive_groupby(group)
-                    group = None
-                    grouper = None
-                group = [item[1:]]
-                grouper = item[0]
-                continue
-
-            group.append(item[1:])
-
-        if grouper is not None:
-            yield grouper
-            if len(group):
-                yield recursive_groupby(group)
-            group = None
-            grouper = None
-
-    return list(_generator(iterable))
-
-
-def active_tab(tab):
-    """
-        View decorator, which puts tab info on a request.
-    """
-    def wrapper(f):
-        @wraps(f)
-        def wrapped(request, *args, **kwargs):
-            request.wiki_active_tab = tab
-            return f(request, *args, **kwargs)
-        return wrapped
-    return wrapper
-
-
-class ChunksList(object):
-    def __init__(self, chunk_qs):
-        self.chunk_qs = chunk_qs.annotate(
-            book_length=Count('book__chunk')).select_related(
-            'book', 'stage__name',
-            'user')
-
-        self.book_qs = chunk_qs.values('book_id')
-
-    def __getitem__(self, key):
-        if isinstance(key, slice):
-            return self.get_slice(key)
-        elif isinstance(key, int):
-            return self.get_slice(slice(key, key+1))[0]
-        else:
-            raise TypeError('Unsupported list index. Must be a slice or an int.')
-
-    def __len__(self):
-        return self.book_qs.count()
-
-    def get_slice(self, slice_):
-        book_ids = [x['book_id'] for x in self.book_qs[slice_]]
-        chunk_qs = self.chunk_qs.filter(book__in=book_ids)
-
-        chunks_list = []
-        book = None
-        for chunk in chunk_qs:
-            if chunk.book != book:
-                book = chunk.book
-                chunks_list.append(ChoiceChunks(book, [chunk], chunk.book_length))
-            else:
-                chunks_list[-1].chunks.append(chunk)
-        return chunks_list
-
-
-class ChoiceChunks(object):
-    """
-        Associates the given chunks iterable for a book.
-    """
-
-    chunks = None
-
-    def __init__(self, book, chunks, book_length):
-        self.book = book
-        self.chunks = chunks
-        self.book_length = book_length
-
diff --git a/apps/wiki/management/__init__.py b/apps/wiki/management/__init__.py
deleted file mode 100755 (executable)
index e69de29..0000000
diff --git a/apps/wiki/management/commands/__init__.py b/apps/wiki/management/commands/__init__.py
deleted file mode 100755 (executable)
index e69de29..0000000
diff --git a/apps/wiki/management/commands/assign_from_redmine.py b/apps/wiki/management/commands/assign_from_redmine.py
deleted file mode 100755 (executable)
index 4517749..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import csv
-from optparse import make_option
-import re
-import sys
-import urllib
-import urllib2
-
-from django.contrib.auth.models import User
-from django.core.management.base import BaseCommand
-from django.core.management.color import color_style
-from django.db import transaction
-
-from slughifi import slughifi
-from wiki.models import Chunk
-
-
-REDMINE_CSV = 'http://redmine.nowoczesnapolska.org.pl/projects/wl-publikacje/issues.csv'
-REDAKCJA_URL = 'http://redakcja.wolnelektury.pl/documents/'
-
-
-class Command(BaseCommand):
-    option_list = BaseCommand.option_list + (
-        make_option('-r', '--redakcja', dest='redakcja', metavar='URL',
-            help='Base URL of Redakcja documents',
-            default=REDAKCJA_URL),
-        make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
-            help='Less output'),
-        make_option('-f', '--force', action='store_true', dest='force', default=False,
-            help='Force assignment overwrite'),
-    )
-    help = 'Imports ticket assignments from Redmine.'
-    args = '[redmine-csv-url]'
-
-    def handle(self, *redmine_csv, **options):
-
-        self.style = color_style()
-
-        redakcja = options.get('redakcja')
-        verbose = options.get('verbose')
-        force = options.get('force')
-
-        if not redmine_csv:
-            if verbose:
-                print "Using default Redmine CSV URL:", REDMINE_CSV
-            redmine_csv = REDMINE_CSV
-
-        # Start transaction management.
-        transaction.commit_unless_managed()
-        transaction.enter_transaction_management()
-        transaction.managed(True)
-
-        redakcja_link = re.compile(re.escape(redakcja) + r'([-_.:?&%/a-zA-Z0-9]*)')
-
-        all_tickets = 0
-        all_chunks = 0
-        done_tickets = 0
-        done_chunks = 0
-        empty_users = 0
-        unknown_users = 0
-        unknown_books = 0
-        forced = 0
-
-        if verbose:
-            print 'Downloading CSV file'
-        for r in csv.reader(urllib2.urlopen(redmine_csv)):
-            if r[0] == '#':
-                continue
-            all_tickets += 1
-
-            username = r[6]
-            if not username:
-                if verbose:
-                    print "Empty user, skipping"
-                empty_users += 1
-                continue
-
-            first_name, last_name = unicode(username, 'utf-8').rsplit(u' ', 1)
-            try:
-                user = User.objects.get(first_name=first_name, last_name=last_name)
-            except User.DoesNotExist:
-                print self.style.ERROR('Unknown user: ' + username)
-                print "'%s' '%s'" % (first_name, last_name)
-                print type(last_name)
-                unknown_users += 1
-                continue
-
-            ticket_done = False
-            for fname in redakcja_link.findall(r[-1]):
-                fname = unicode(urllib.unquote(fname), 'utf-8', 'ignore')
-                if fname.endswith('.xml'):
-                    fname = fname[:-4]
-                fname = fname.replace(' ', '_')
-                fname = slughifi(fname)
-
-                chunks = Chunk.objects.filter(book__slug=fname)
-                if not chunks:
-                    print self.style.ERROR('Unknown book: ' + fname)
-                    unknown_books += 1
-                    continue
-                all_chunks += chunks.count()
-
-                for chunk in chunks:
-                    if chunk.user:
-                        if chunk.user == user:
-                            continue
-                        else:
-                            forced += 1
-                            if force:
-                                print self.style.WARNING(
-                                    '%s assigned to %s, forcing change to %s.' %
-                                    (chunk.pretty_name(), chunk.user, user))
-                            else:
-                                print self.style.WARNING(
-                                    '%s assigned to %s not to %s, skipping.' %
-                                    (chunk.pretty_name(), chunk.user, user))
-                                continue
-                    chunk.user = user
-                    chunk.save()
-                    ticket_done = True
-                    done_chunks += 1
-
-            if ticket_done:
-                done_tickets += 1
-
-
-        # Print results
-        print
-        print "Results:"
-        print "Done %d/%d tickets, assigned %d/%d book chunks." % (
-                done_tickets, all_tickets, done_chunks, all_chunks)
-        print "%d tickets unassigned, for %d chunks assignment differed." % (
-                empty_users, forced)
-        print "Unrecognized: %d books, %d users." % (
-                unknown_books, unknown_users)
-        print
-
-
-        transaction.commit()
-        transaction.leave_transaction_management()
-
diff --git a/apps/wiki/migrations/0003_add_dvcs.py b/apps/wiki/migrations/0003_add_dvcs.py
deleted file mode 100644 (file)
index 87ac321..0000000
+++ /dev/null
@@ -1,369 +0,0 @@
-# encoding: utf-8
-import datetime
-import os.path
-import cPickle
-import re
-import urllib
-
-from django.conf import settings
-from django.db import models
-from mercurial import mdiff, hg, ui
-from south.db import db
-from south.v2 import SchemaMigration
-
-from slughifi import slughifi
-
-META_REGEX = re.compile(r'\s*<!--\s(.*?)-->', re.DOTALL | re.MULTILINE)
-STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE)
-AUTHOR_RE = re.compile(r'\s*(.*?)\s*<(.*)>\s*')
-
-
-def urlunquote(url):
-    """Unqotes URL
-
-    # >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84')
-    # u'Za\u017c\xf3\u0142\u0107_g\u0119\u015bl\u0105 ja\u017a\u0144'
-    """
-    return unicode(urllib.unquote(url), 'utf-8', 'ignore')
-
-
-def split_name(name):
-    parts = name.split('__')
-    return parts
-
-
-def file_to_title(fname):
-    """ Returns a title-like version of a filename. """
-    parts = (p.replace('_', ' ').title() for p in fname.split('__'))
-    return ' / '.join(parts)
-
-
-def make_patch(src, dst):
-    if isinstance(src, unicode):
-        src = src.encode('utf-8')
-    if isinstance(dst, unicode):
-        dst = dst.encode('utf-8')
-    return cPickle.dumps(mdiff.textdiff(src, dst))
-
-
-def plain_text(text):
-    return re.sub(META_REGEX, '', text, 1)
-
-
-def gallery(slug, text):
-    result = {}
-
-    m = re.match(META_REGEX, text)
-    if m:
-        for line in m.group(1).split('\n'):
-            try:
-                k, v = line.split(':', 1)
-                result[k.strip()] = v.strip()
-            except ValueError:
-                continue
-
-    gallery = result.get('gallery', slughifi(slug))
-
-    if gallery.startswith('/'):
-        gallery = os.path.basename(gallery)
-
-    return gallery
-
-
-def migrate_file_from_hg(orm, fname, entry):
-    fname = urlunquote(fname)
-    print fname
-    if fname.endswith('.xml'):
-        fname = fname[:-4]
-    title = file_to_title(fname)
-    fname = slughifi(fname)
-    # create all the needed objects
-    # what if it already exists?
-    book = orm.Book.objects.create(
-        title=title,
-        slug=fname)
-    chunk = orm.Chunk.objects.create(
-        book=book,
-        number=1,
-        slug='1')
-    head = orm['wiki.ChunkChange'].objects.create(
-        tree=chunk,
-        revision=-1,
-        patch=make_patch('', ''),
-        created_at=datetime.datetime.fromtimestamp(entry.filectx(0).date()[0]),
-        description=''
-        )
-    chunk.head = head
-    try:
-        chunk.stage = orm['wiki.ChunkTag'].objects.order_by('ordering')[0]
-    except IndexError:
-        chunk.stage = None
-    old_data = ''
-
-    maxrev = entry.filerev()
-    gallery_link = None
-    
-    for rev in xrange(maxrev + 1):
-        fctx = entry.filectx(rev)
-        data = fctx.data()
-        gallery_link = gallery(fname, data)
-        data = plain_text(data)
-
-        # get tags from description
-        description = fctx.description().decode("utf-8", 'replace')
-        tags = STAGE_TAGS_RE.findall(description)
-        tags = [orm['wiki.ChunkTag'].objects.get(slug=slug.strip()) for slug in tags]
-
-        if tags:
-            max_ordering = max(tags, key=lambda x: x.ordering).ordering
-            try:
-                chunk.stage = orm['wiki.ChunkTag'].objects.filter(ordering__gt=max_ordering).order_by('ordering')[0]
-            except IndexError:
-                chunk.stage = None
-
-        description = STAGE_TAGS_RE.sub('', description)
-
-        author = author_name = author_email = None
-        author_desc = fctx.user().decode("utf-8", 'replace')
-        m = AUTHOR_RE.match(author_desc)
-        if m:
-            try:
-                author = orm['auth.User'].objects.get(username=m.group(1), email=m.group(2))
-            except orm['auth.User'].DoesNotExist:
-                author_name = m.group(1)
-                author_email = m.group(2)
-        else:
-            author_name = author_desc
-
-        head = orm['wiki.ChunkChange'].objects.create(
-            tree=chunk,
-            revision=rev + 1,
-            patch=make_patch(old_data, data),
-            created_at=datetime.datetime.fromtimestamp(fctx.date()[0]),
-            description=description,
-            author=author,
-            author_name=author_name,
-            author_email=author_email,
-            parent=chunk.head
-            )
-        head.tags = tags
-        chunk.head = head
-        old_data = data
-
-    chunk.save()
-    if gallery_link:
-        book.gallery = gallery_link
-        book.save()
-
-
-def migrate_from_hg(orm):
-    try:
-        hg_path = settings.WIKI_REPOSITORY_PATH
-    except:
-        pass
-
-    print 'migrate from', hg_path
-    repo = hg.repository(ui.ui(), hg_path)
-    tip = repo['tip']
-    for fname in tip:
-        if fname.startswith('.'):
-            continue
-        migrate_file_from_hg(orm, fname, tip[fname])
-
-
-class Migration(SchemaMigration):
-
-    def forwards(self, orm):
-        
-        # Adding model 'Book'
-        db.create_table('wiki_book', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('title', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
-            ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=128, db_index=True)),
-            ('gallery', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
-            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['wiki.Book'])),
-            ('parent_number', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)),
-            ('last_published', self.gf('django.db.models.fields.DateTimeField')(null=True, db_index=True)),
-        ))
-        db.send_create_signal('wiki', ['Book'])
-
-        # Adding model 'Chunk'
-        db.create_table('wiki_chunk', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_documents', null=True, to=orm['auth.User'])),
-            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
-            ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.Book'])),
-            ('number', self.gf('django.db.models.fields.IntegerField')()),
-            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)),
-            ('comment', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
-            ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.ChunkTag'], null=True, blank=True)),
-            ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['wiki.ChunkChange'], null=True, blank=True)),
-        ))
-        db.send_create_signal('wiki', ['Chunk'])
-
-        # Adding unique constraint on 'Chunk', fields ['book', 'number']
-        db.create_unique('wiki_chunk', ['book_id', 'number'])
-
-        # Adding unique constraint on 'Chunk', fields ['book', 'slug']
-        db.create_unique('wiki_chunk', ['book_id', 'slug'])
-
-        # Adding model 'ChunkTag'
-        db.create_table('wiki_chunktag', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=64)),
-            ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=64, unique=True, null=True, blank=True)),
-            ('ordering', self.gf('django.db.models.fields.IntegerField')()),
-        ))
-        db.send_create_signal('wiki', ['ChunkTag'])
-
-        # Adding model 'ChunkChange'
-        db.create_table('wiki_chunkchange', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
-            ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
-            ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
-            ('patch', self.gf('django.db.models.fields.TextField')(blank=True)),
-            ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
-            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['wiki.ChunkChange'])),
-            ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['wiki.ChunkChange'])),
-            ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
-            ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)),
-            ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)),
-            ('tree', self.gf('django.db.models.fields.related.ForeignKey')(related_name='change_set', to=orm['wiki.Chunk'])),
-        ))
-        db.send_create_signal('wiki', ['ChunkChange'])
-
-        # Adding unique constraint on 'ChunkChange', fields ['tree', 'revision']
-        db.create_unique('wiki_chunkchange', ['tree_id', 'revision'])
-
-        # Adding M2M table for field tags on 'ChunkChange'
-        db.create_table('wiki_chunkchange_tags', (
-            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
-            ('chunkchange', models.ForeignKey(orm['wiki.chunkchange'], null=False)),
-            ('chunktag', models.ForeignKey(orm['wiki.chunktag'], null=False))
-        ))
-        db.create_unique('wiki_chunkchange_tags', ['chunkchange_id', 'chunktag_id'])
-
-        if not db.dry_run:
-            from django.core.management import call_command
-            call_command("loaddata", "stages.json")
-
-            migrate_from_hg(orm)
-
-    def backwards(self, orm):
-        
-        # Removing unique constraint on 'ChunkChange', fields ['tree', 'revision']
-        db.delete_unique('wiki_chunkchange', ['tree_id', 'revision'])
-
-        # Removing unique constraint on 'Chunk', fields ['book', 'slug']
-        db.delete_unique('wiki_chunk', ['book_id', 'slug'])
-
-        # Removing unique constraint on 'Chunk', fields ['book', 'number']
-        db.delete_unique('wiki_chunk', ['book_id', 'number'])
-
-        # Deleting model 'Book'
-        db.delete_table('wiki_book')
-
-        # Deleting model 'Chunk'
-        db.delete_table('wiki_chunk')
-
-        # Deleting model 'ChunkTag'
-        db.delete_table('wiki_chunktag')
-
-        # Deleting model 'ChunkChange'
-        db.delete_table('wiki_chunkchange')
-
-        # Removing M2M table for field tags on 'ChunkChange'
-        db.delete_table('wiki_chunkchange_tags')
-
-
-    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'})
-        },
-        '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'})
-        },
-        'wiki.book': {
-            'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'},
-            'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'last_published': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.Book']"}),
-            'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': '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'})
-        },
-        'wiki.chunk': {
-            'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
-            'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Book']"}),
-            'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}),
-            'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['wiki.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['wiki.ChunkTag']", 'null': 'True', 'blank': 'True'}),
-            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
-        },
-        'wiki.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'}),
-            '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['wiki.ChunkChange']"}),
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['wiki.ChunkChange']"}),
-            'patch': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            '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['wiki.ChunkTag']"}),
-            'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['wiki.Chunk']"})
-        },
-        'wiki.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'})
-        },
-        'wiki.theme': {
-            'Meta': {'ordering': "('name',)", 'object_name': 'Theme'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'})
-        }
-    }
-
-    complete_apps = ['wiki']
index 20c8b86..c539908 100644 (file)
 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-import itertools
-import re
-
-from django.core.urlresolvers import reverse
 from django.db import models
-from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext_lazy as _
-from django.template.loader import render_to_string
-
-from dvcs import models as dvcs_models
-from wiki.xml_tools import compile_text
 
 import logging
 logger = logging.getLogger("fnp.wiki")
 
 
-class Book(models.Model):
-    """ A document edited on the wiki """
-
-    title = models.CharField(_('title'), max_length=255, db_index=True)
-    slug = models.SlugField(_('slug'), max_length=128, unique=True, db_index=True)
-    gallery = models.CharField(_('scan gallery name'), max_length=255, blank=True)
-
-    parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children")
-    parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True)
-    last_published = models.DateTimeField(null=True, editable=False, db_index=True)
-
-    class NoTextError(BaseException):
-        pass
-
-    class Meta:
-        ordering = ['parent_number', 'title']
-        verbose_name = _('book')
-        verbose_name_plural = _('books')
-
-    def __unicode__(self):
-        return self.title
-
-    def get_absolute_url(self):
-        return reverse("wiki_book", args=[self.slug])
-
-    @classmethod
-    def create(cls, creator=None, text=u'', *args, **kwargs):
-        """
-            >>> Book.create(slug='x', text='abc').materialize()
-            'abc'
-        """
-        instance = cls(*args, **kwargs)
-        instance.save()
-        instance[0].commit(author=creator, text=text)
-        return instance
-
-    def __iter__(self):
-        return iter(self.chunk_set.all())
-
-    def __getitem__(self, chunk):
-        return self.chunk_set.all()[chunk]
-
-    def materialize(self, publishable=True):
-        """ 
-            Get full text of the document compiled from chunks.
-            Takes the current versions of all texts
-            or versions most recently tagged for publishing.
-        """
-        if publishable:
-            changes = [chunk.publishable() for chunk in self]
-        else:
-            changes = [chunk.head for chunk in self]
-        if None in changes:
-            raise self.NoTextError('Some chunks have no available text.')
-        return compile_text(change.materialize() for change in changes)
-
-    def publishable(self):
-        if not len(self):
-            return False
-        for chunk in self:
-            if not chunk.publishable():
-                return False
-        return True
-
-    def make_chunk_slug(self, proposed):
-        """ 
-            Finds a chunk slug not yet used in the book.
-        """
-        slugs = set(c.slug for c in self)
-        i = 1
-        new_slug = proposed
-        while new_slug in slugs:
-            new_slug = "%s-%d" % (proposed, i)
-            i += 1
-        return new_slug
-
-    def append(self, other):
-        number = self[len(self) - 1].number + 1
-        single = len(other) == 1
-        for chunk in other:
-            # move chunk to new book
-            chunk.book = self
-            chunk.number = number
-
-            # try some title guessing
-            if other.title.startswith(self.title):
-                other_title_part = other.title[len(self.title):].lstrip(' /')
-            else:
-                other_title_part = other.title
-
-            if single:
-                # special treatment for appending one-parters:
-                # just use the guessed title and original book slug
-                chunk.comment = other_title_part
-                if other.slug.startswith(self.slug):
-                    chunk_slug = other.slug[len(self.slug):].lstrip('-_')
-                else:
-                    chunk_slug = other.slug
-                chunk.slug = self.make_chunk_slug(chunk_slug)
-            else:
-                chunk.comment = "%s, %s" % (other_title_part, chunk.comment)
-                chunk.slug = self.make_chunk_slug(chunk.slug)
-            chunk.save()
-            number += 1
-        other.delete()
-
-    @staticmethod
-    def listener_create(sender, instance, created, **kwargs):
-        if created:
-            instance.chunk_set.create(number=1, slug='1')
-
-models.signals.post_save.connect(Book.listener_create, sender=Book)
-
-
-class Chunk(dvcs_models.Document):
-    """ An editable chunk of text. Every Book text is divided into chunks. """
-
-    book = models.ForeignKey(Book, editable=False)
-    number = models.IntegerField()
-    slug = models.SlugField()
-    comment = models.CharField(max_length=255, blank=True)
-
-    class Meta:
-        unique_together = [['book', 'number'], ['book', 'slug']]
-        ordering = ['number']
-
-    def __unicode__(self):
-        return "%d-%d: %s" % (self.book_id, self.number, self.comment)
-
-    def get_absolute_url(self):
-        return reverse("wiki_editor", args=[self.book.slug, self.slug])
-
-    @classmethod
-    def get(cls, slug, chunk=None):
-        if chunk is None:
-            return cls.objects.get(book__slug=slug, number=1)
-        else:
-            return cls.objects.get(book__slug=slug, slug=chunk)
-
-    def pretty_name(self, book_length=None):
-        title = self.book.title
-        if self.comment:
-            title += ", %s" % self.comment
-        if book_length > 1:
-            title += " (%d/%d)" % (self.number, book_length)
-        return title
-
-    def split(self, slug, comment='', creator=None):
-        """ Create an empty chunk after this one """
-        self.book.chunk_set.filter(number__gt=self.number).update(
-                number=models.F('number')+1)
-        new_chunk = self.book.chunk_set.create(number=self.number+1,
-                creator=creator, slug=slug, comment=comment)
-        return new_chunk
-
-    def list_html(self):
-        _list_html = render_to_string('wiki/chunk_list_item.html',
-                {'chunk': self})
-        return mark_safe(_list_html)
-
-    @staticmethod
-    def listener_saved(sender, instance, created, **kwargs):
-        if instance.book:
-            # save book so that its _list_html is reset
-            instance.book.save()
-
-models.signals.post_save.connect(Chunk.listener_saved, sender=Chunk)
-
-
 class Theme(models.Model):
     name = models.CharField(_('name'), max_length=50, unique=True)
 
diff --git a/apps/wiki/templates/wiki/base.html b/apps/wiki/templates/wiki/base.html
deleted file mode 100644 (file)
index 83cfb7c..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-{% load compressed i18n %}
-{% load wiki %}
-<!DOCTYPE html>
-<html>
-<head>
-    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
-    {% compressed_css 'listing' %}
-    <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}style.css" />
-    <title>{% block title %}{% trans "Platforma Redakcyjna" %}{% endblock title %}</title>
-</head>
-<body>
-
-<div id="tabs-nav">
-
-    <a href="{% url wiki_document_list %}">
-        <img id="logo" src="{{ STATIC_URL }}img/wl-orange.png" />
-    </a>
-
-    <div id="tabs-nav-left">
-        {% main_tabs %}
-    </div>
-
-    <span id="login-box">
-        {% include "registration/head_login.html" %}
-    </span>
-
-    <div class='clr' ></div>
-</div>
-
-<div id="content">
-
-{% block content %}
-<div id="wiki_layout_left_column">
-       {% block leftcolumn %}
-       {% endblock leftcolumn %}
-</div>
-<div id="wiki_layout_right_column">
-       {% block rightcolumn %}
-       {% endblock rightcolumn %}
-</div>
-{% endblock content %}
-
-</div>
-
-{% compressed_js 'listing' %}
-{% block extrabody %}
-{% endblock %}
-</body>
-</html>
diff --git a/apps/wiki/templates/wiki/book_append_to.html b/apps/wiki/templates/wiki/book_append_to.html
deleted file mode 100755 (executable)
index da6594d..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends "wiki/base.html" %}
-{% load i18n %}
-
-{% block leftcolumn %}
-       <form enctype="multipart/form-data" method="POST" action="">
-    {% csrf_token %}
-       {{ form.as_p }}
-
-       <p><button type="submit">{% trans "Append book" %}</button></p>
-       </form>
-{% endblock leftcolumn %}
-
-{% block rightcolumn %}
-{% endblock rightcolumn %}
diff --git a/apps/wiki/templates/wiki/book_detail.html b/apps/wiki/templates/wiki/book_detail.html
deleted file mode 100755 (executable)
index 5ea0e3d..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-{% extends "wiki/base.html" %}
-{% load comments i18n %}
-
-{% block leftcolumn %}
-
-<a href="{% url wiki_book_edit book.slug %}">{% trans "edit" %}</a>
-    <h1>{{ book.title }}</h1>
-
-<table>
-    {% for c in chunks %}
-        <tr class="chunk-{{ c.grade }}">
-        <td><a target="_blank" href="{{ c.chunk.get_absolute_url }}">{{ c.chunk.comment }}</a></td>
-        <td>{% for fix in c.fix %}
-
-                {% ifequal fix "wl" %}<span class="fix"
-                    title="{% trans "add basic document structure" %}"
-                    >&lt;/&gt;</span>{% endifequal %}
-
-                {% ifequal fix "bad-master" %}<span class="fix"
-                    title='{% trans "change master tag to" %} "{{ first_master }}"'
-                    >master</span>{% endifequal %}
-
-                {% ifequal fix "trim-begin" %}<span class="fix"
-                    title="{% trans "add begin trimming tag" %}"
-                    >&#x2701;</span>{% endifequal %}
-
-                {% ifequal fix "trim-end" %}<span class="fix"
-                    title="{% trans "add end trimming tag" %}"
-                    >&#x2703;</span>{% endifequal %}
-
-            {% endfor %}
-
-            {% ifequal c.grade "plain" %}
-                <span class="fix-info">{% trans "unstructured text" %}</span>
-            {% endifequal %}
-
-            {% ifequal c.grade "xml" %}
-                <span class="fix-info">{% trans "unknown XML" %}</span>
-            {% endifequal %}
-
-            {% ifequal c.grade "wl-broken" %}
-                <span class="fix-info">{% trans "broken document" %}</span>
-            {% endifequal %}
-
-        </td>
-        <td><a href="{% url wiki_chunk_edit book.slug c.chunk.slug%}">[{% trans "edit" %}]</a></td>
-        <td>{% if c.chunk.publishable %}P{% endif %}</td>
-        <td>{% if c.chunk.user.is_authenticated %}
-                <a href="{% url wiki_user c.chunk.user.username %}">{{ c.chunk.user }}</a>
-            {% endif %}</td>
-        <td><a href="{% url wiki_chunk_add book.slug c.chunk.slug %}">[+]</a></td>
-        </tr>
-    {% endfor %}
-    {% if need_fixing %}
-        <tr><td></td><td>
-            <form method="POST" action="">
-                {% csrf_token %}
-                {% if choose_master %}
-                    {{ form.master }}
-                {% endif %}
-                <button type="submit">{% trans "Apply fixes" %}</button>
-            </form>
-        </td></tr>
-    {% endif %}
-</table>
-
-<p><a href="{% url wiki_book_append book.slug %}">{% trans "Append to other book" %}</a></p>
-
-<p>{% trans "Last published" %}: {{ book.last_published }}</p>
-
-{% if book.publishable %}
-    <p>
-    <a href="{% url wiki_book_xml book.slug %}">{% trans "Full XML" %}</a><br/>
-    <a target="_blank" href="{% url wiki_book_html book.slug %}">{% trans "HTML version" %}</a><br/>
-    <a href="{% url wiki_book_txt book.slug %}">{% trans "TXT version" %}</a><br/>
-    {% comment %}
-    <a href="{% url wiki_book_epub book.slug %}">{% trans "EPUB version" %}</a><br/>
-    <a href="{% url wiki_book_pdf book.slug %}">{% trans "PDF version" %}</a><br/>
-    {% endcomment %}
-    </p>
-
-    <!--
-    Angel photos:
-    Angels in Ely Cathedral (http://www.flickr.com/photos/21804434@N02/4483220595/) /
-    mira66 (http://www.flickr.com/photos/21804434@N02/) /
-    CC BY 2.0 (http://creativecommons.org/licenses/by/2.0/)
-    -->
-    <form method="POST" action="{% url wiki_publish book.slug %}">{% csrf_token %}
-        <img src="{{ STATIC_URL }}img/angel-left.png" style="vertical-align: middle" />
-        <button id="publish-button" type="submit">
-            <span>{% trans "Publish" %}</span></button>
-        <img src="{{ STATIC_URL }}img/angel-right.png" style="vertical-align: middle" />
-        </form></p>
-{% else %}
-    {% trans "This book cannot be published yet" %}
-{% endif %}
-
-{% endblock leftcolumn %}
-
-{% block rightcolumn %}
-{% render_comment_list for book %}
-{% render_comment_form for book %}
-
-{% endblock rightcolumn %}
diff --git a/apps/wiki/templates/wiki/book_edit.html b/apps/wiki/templates/wiki/book_edit.html
deleted file mode 100755 (executable)
index 8bc7ea7..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends "wiki/base.html" %}
-{% load i18n %}
-
-{% block leftcolumn %}
-       <form enctype="multipart/form-data" method="POST" action="">
-    {% csrf_token %}
-       {{ form.as_p }}
-
-       <p><button type="submit">{% trans "Save" %}</button></p>
-       </form>
-{% endblock leftcolumn %}
-
-{% block rightcolumn %}
-{% endblock rightcolumn %}
diff --git a/apps/wiki/templates/wiki/chunk_add.html b/apps/wiki/templates/wiki/chunk_add.html
deleted file mode 100755 (executable)
index 836c34d..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends "wiki/base.html" %}
-{% load i18n %}
-
-{% block leftcolumn %}
-       <form enctype="multipart/form-data" method="POST" action="">
-    {% csrf_token %}
-       {{ form.as_p }}
-
-       <p><button type="submit">{% trans "Add chunk" %}</button></p>
-       </form>
-{% endblock leftcolumn %}
-
-{% block rightcolumn %}
-{% endblock rightcolumn %}
diff --git a/apps/wiki/templates/wiki/chunk_edit.html b/apps/wiki/templates/wiki/chunk_edit.html
deleted file mode 100755 (executable)
index 8bc7ea7..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends "wiki/base.html" %}
-{% load i18n %}
-
-{% block leftcolumn %}
-       <form enctype="multipart/form-data" method="POST" action="">
-    {% csrf_token %}
-       {{ form.as_p }}
-
-       <p><button type="submit">{% trans "Save" %}</button></p>
-       </form>
-{% endblock leftcolumn %}
-
-{% block rightcolumn %}
-{% endblock rightcolumn %}
diff --git a/apps/wiki/templates/wiki/document_create_missing.html b/apps/wiki/templates/wiki/document_create_missing.html
deleted file mode 100644 (file)
index 9414f7c..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends "wiki/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 rightcolumn %}
-{% endblock rightcolumn %}
index 5f98b73..76711dd 100644 (file)
@@ -26,7 +26,7 @@
 </div>
 
 <div id="header">
-    <h1><a href="{% url wiki_document_list %}"><img src="{{STATIC_URL}}icons/go-home.png"/><a href="{% url wiki_document_list %}">Strona<br>główna</a></h1>
+    <h1><a href="{% url catalogue_document_list %}"><img src="{{STATIC_URL}}icons/go-home.png"/><a href="{% url catalogue_document_list %}">Strona<br>główna</a></h1>
     <div id="tools">
         <a href="{{ REDMINE_URL }}projects/wl-publikacje/wiki/Pomoc" target="_blank">
         {% trans "Help" %}</a>
diff --git a/apps/wiki/templates/wiki/document_list.html b/apps/wiki/templates/wiki/document_list.html
deleted file mode 100644 (file)
index f68ba3e..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-{% extends "wiki/base.html" %}
-
-{% load i18n %}
-{% load pagination_tags %}
-{% load wiki %}
-
-{% block extrabody %}
-{{ block.super }}
-<script type="text/javascript" charset="utf-8">
-$(function() {
-       function search(event) {
-        event.preventDefault();
-        var expr = new RegExp(slugify($('#file-list-filter').val()), 'i');
-        $('#file-list tbody tr').hide().filter(function(index) {
-            return expr.test(slugify( $('a', this).attr('data-id') ));
-        }).show();
-    }
-
-    $('#file-list-find-button').click(search).hide();
-       $('#file-list-filter').bind('keyup change DOMAttrModified', search);
-});
-</script>
-{% endblock %}
-
-{% block leftcolumn %}
-       <form method="get" action="#">
-    <table>
-       <thead>
-               <tr><th>Filtr:</th>
-                       <th><input autocomplete="off" name="filter" id="file-list-filter" type="text" size="40" /></th>
-                       <th><input type="reset" value="{% trans "Clear filter" %}" id="file-list-reset-button"/></th>
-                       </tr>
-               </thead>
-               <tbody>
-               </tbody>
-    </table>
-       </form>
-
-
-
-       <form method="get" action="#">
-    <table id="file-list">
-               <tbody>
-        {% autopaginate books 100 %}
-        {% if not books %}
-            <tr><td>{% trans "No books found." %}</td></tr>
-        {% endif %}
-       {% for item in books %}
-            {% with item.book as book %}
-
-            {% ifequal item.book_length 1 %}
-                {% with item.chunks.0 as chunk %}
-                <tr>
-                    <td><a target="_blank" href="{% url wiki_book book.slug %}">[B]</a></td>
-                    <td><a href="{% url wiki_chunk_edit book.slug chunk.slug %}">[c]</a></td>
-                    <td><a target="_blank"
-                                href="{% url wiki_editor book.slug %}">
-                                {{ book.title }}</a></td>
-                    <td>({{ chunk.stage }})</td>
-                    <td>{% if chunk.user %}<a href="{% url wiki_user chunk.user.username %}">{{ chunk.user.first_name }} {{ chunk.user.last_name }}</a>{% endif %}</td>
-                </tr>
-                {% endwith %}
-            {% else %}
-                <tr>
-                    <td><a target="_blank" href="{% url wiki_book book.slug %}">[B]</a></td>
-                    <td></td>
-                    <td>{{ book.title }}</td>
-                </tr>
-                {% for chunk in item.chunks %}
-                    <tr>
-                        <td></td>
-                        <td><a href="{% url wiki_chunk_edit book.slug chunk.slug %}">[c]</a></td>
-                        <td><a target="_blank" href="{{ chunk.get_absolute_url }}">
-                                <span class='chunkno'>{{ chunk.number }}.</span>
-                                {{ chunk.comment }}</a></td>
-                        <td>({{ chunk.stage }})</td>
-                        <td>{% if chunk.user %}<a href="{% url wiki_user chunk.user.username %}">{{ chunk.user.first_name }} {{ chunk.user.last_name }}</a>{% endif %}</td>
-                    </td></tr>
-                {% endfor %}
-            {% endifequal %}
-            {% endwith %}
-       {% endfor %}
-        <tr><td colspan="3">{% paginate %}</td></tr>
-               </tbody>
-    </table>
-       </form>
-{% endblock leftcolumn %}
-
-{% block rightcolumn %}
-       <div id="last-edited-list">
-               <h2>{% trans "Your last edited documents" %}</h2>
-           <ol>
-                       {% for slugs, item in last_books %}
-                       <li><a href="{% url wiki_editor slugs.0 slugs.1 %}"
-                               target="_blank">{{ item.title }}</a><br/><span class="date">({{ item.time|date:"H:i:s, d/m/Y" }})</span></li>
-                       {% endfor %}
-               </ol>
-       </div>
-
-    <h2>{% trans "Recent activity" %}</h2>
-    {% wall %}
-{% endblock rightcolumn %}
diff --git a/apps/wiki/templates/wiki/document_upload.html b/apps/wiki/templates/wiki/document_upload.html
deleted file mode 100644 (file)
index f7af2ce..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-{% extends "wiki/base.html" %}
-{% load i18n %}
-
-
-{% block leftcolumn %}
-
-
-<h2>{% trans "Bulk documents upload" %}</h2>
-
-<p>
-{% trans "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with <code>.xml</code> will be ignored." %}
-</p>
-
-<form enctype="multipart/form-data" method="POST" action="">
-{% csrf_token %}
-{{ form.as_p }}
-<p><button type="submit">{% trans "Upload" %}</button></p>
-</form>
-
-<hr/>
-
-{% if error_list %}
-
-    <p class='error'>{% trans "There have been some errors. No files have been added to the repository." %}
-    <h3>{% trans "Offending files" %}</h3>
-    <ul id='error-list'>
-        {% for filename, title, error in error_list %}
-            <li>{{ title }} (<code>{{ filename }}</code>): {{ error }}</li>
-        {% endfor %}
-    </ul>
-
-    {% if ok_list %}
-    <h3>{% trans "Correct files" %}</h3>
-        <ul>
-            {% for filename, slug, title in ok_list %}
-                <li>{{ title }} (<code>{{ filename }}</code>)</li>
-            {% endfor %}
-        </ul>
-    {% endif %}
-
-{% else %}
-
-    {% if ok_list %}
-        <p class='success'>{% trans "Files have been successfully uploaded to the repository." %}</p>
-        <h3>{% trans "Uploaded files" %}</h3>
-        <ul id='ok-list'>
-        {% for filename, slug, title in ok_list %}
-            <li><a href='{% url wiki_editor slug %}'>{{ title }}</a> (<code>{{ filename }})</a></li>
-        {% endfor %}
-        </ul>
-    {% endif %}
-{% endif %}
-
-{% if skipped_list %}
-    <h3>{% trans "Skipped files" %}</h3>
-    <p>{% trans "Files skipped due to no <code>.xml</code> extension" %}</p>
-    <ul id='skipped-list'>
-        {% for filename in skipped_list %}
-            <li>{{ filename }}</li>
-        {% endfor %}
-    </ul>
-{% endif %}
-
-
-{% endblock leftcolumn %}
-
-
-{% block rightcolumn %}
-{% endblock rightcolumn %}
diff --git a/apps/wiki/templates/wiki/main_tabs.html b/apps/wiki/templates/wiki/main_tabs.html
deleted file mode 100755 (executable)
index 82321cc..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-{% for tab in tabs %}
-    <a {% ifequal active_tab tab.slug %}class="active" {% endifequal %}href="{{ tab.url }}">{{ tab.caption }}</a>
-{% endfor %}
diff --git a/apps/wiki/templates/wiki/tag_dialog.html b/apps/wiki/templates/wiki/tag_dialog.html
deleted file mode 100644 (file)
index aa4b242..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-{% load i18n %}
-<div id="add_tag_dialog" class="dialog" data-ui-jsclass="AddTagDialog">
-       <form method="POST" action="#">
-    {% csrf_token %}
-               {% 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>
-               {% endfor %}
-
-               {% for f in forms.add_tag.hidden_fields %}
-                       {{ f }}
-               {% endfor %}
-               <p data-ui-error-for="__all__"> </p>
-
-               <p class="action_area">
-                       <button type="submit" class"ok" data-ui-action="save">{% trans "Save" %}</button>
-                       <button type="button" class="cancel" data-ui-action="cancel">{% trans "Cancel" %}</button>
-               </p>
-       </form>
-</div>
diff --git a/apps/wiki/templates/wiki/user_list.html b/apps/wiki/templates/wiki/user_list.html
deleted file mode 100755 (executable)
index 8996cc0..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-{% extends "wiki/base.html" %}
-
-{% load i18n %}
-
-{% block leftcolumn %}
-
-<h1>{% trans "Users" %}</h1>
-
-<ul>
-{% for user in users %}
-    <li><a href="{% url wiki_user user.username %}">
-        <span class="chunkno">{{ forloop.counter }}.</span>
-        {{ user.first_name }} {{ user.last_name }}</a>
-        ({{ user.count }})</li>
-{% endfor %}
-</ul>
-
-{% endblock leftcolumn %}
diff --git a/apps/wiki/templates/wiki/wall.html b/apps/wiki/templates/wiki/wall.html
deleted file mode 100755 (executable)
index ed4685a..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-{% load i18n %}
-{% load gravatar %}
-
-<ul class='wall' style='padding-left: 0; list-style: none;'>
-{% for item in wall %}
-    <li style='clear: left; border-top: 1px dotted gray;  padding-bottom:1em; margin-bottom: 1em;'>
-        <div style='float: left;margin-right: 1em;'>
-            {% if item.get_email %}
-                {% gravatar_img_for_email item.get_email 32 %}
-                <br/>
-            {% endif %}
-
-            <img src='{{ STATIC_URL }}img/wall/{{ item.tag}}.png' alt='{% trans item.tag %}' />
-        </div>
-
-        {{ item.timestamp }}
-        <br/>
-        {% if item.user %}
-            <a href="{% url wiki_user item.user.username %}">
-            {{ item.user.first_name }} {{ item.user.last_name }}</a>
-            &lt;{{ item.user.email }}>
-        {% else %}
-            {{ item.user_name }}
-            {% if item.get_email %}
-                &lt;{{ item.get_email }}>
-            {% endif %}
-        {% endif %}
-        <br/><a target="_blank" href='{{ item.url }}'>{{ item.title }}</a>
-        <br/>{{ item.summary }}
-        </li>
-{% endfor %}
-</ul>
diff --git a/apps/wiki/templatetags/__init__.py b/apps/wiki/templatetags/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/apps/wiki/templatetags/wiki.py b/apps/wiki/templatetags/wiki.py
deleted file mode 100644 (file)
index 3a1cc17..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-from __future__ import absolute_import
-
-from django.db.models import Count
-from django.core.urlresolvers import reverse
-from django.contrib.comments.models import Comment
-from django.template.defaultfilters import stringfilter
-from django import template
-from django.utils.translation import ugettext as _
-
-from wiki.models import Book, Chunk
-
-register = template.Library()
-
-
-class Tab(object):
-    slug = None
-    caption = None
-    url = None
-
-    def __init__(self, slug, caption, url):
-        self.slug = slug
-        self.caption = caption
-        self.url = url
-
-
-@register.inclusion_tag("wiki/main_tabs.html", takes_context=True)
-def main_tabs(context):
-    active = getattr(context['request'], 'wiki_active_tab', None)
-
-    tabs = []
-    user = context['user']
-    if user.is_authenticated():
-        tabs.append(Tab('my', _('My page'), reverse("wiki_user")))
-
-    tabs.append(Tab('unassigned', _('Unassigned'), reverse("wiki_unassigned")))
-    tabs.append(Tab('users', _('Users'), reverse("wiki_users")))
-    tabs.append(Tab('create', _('Add'), reverse("wiki_create_missing")))
-    tabs.append(Tab('upload', _('Upload'), reverse("wiki_upload")))
-
-    if user.is_staff:
-        tabs.append(Tab('admin', _('Admin'), reverse("admin:index")))
-
-    return {"tabs": tabs, "active_tab": active}
-
-
-class WallItem(object):
-    title = ''
-    summary = ''
-    url = ''
-    timestamp = ''
-    user = None
-    email = ''
-
-    def __init__(self, tag):
-        self.tag = tag
-
-    def get_email(self):
-        if self.user:
-            return self.user.email
-        else:
-            return self.email
-
-
-def changes_wall(max_len):
-    qs = Chunk.change_model.objects.filter(revision__gt=-1).order_by('-created_at')
-    qs = qs.defer('patch')
-    qs = qs.select_related('author', 'tree', 'tree__book__title')
-    qs = qs[:max_len]
-    for item in qs:
-        tag = 'stage' if item.tags.count() else 'change'
-        chunk = item.tree
-        w  = WallItem(tag)
-        w.title = chunk.pretty_name()
-        w.summary = item.description
-        w.url = reverse('wiki_editor', 
-                args=[chunk.book.slug, chunk.slug]) + '?diff=%d' % item.revision
-        w.timestamp = item.created_at
-        w.user = item.author
-        w.email = item.author_email
-        yield w
-
-
-def published_wall(max_len):
-    qs = Book.objects.exclude(last_published=None).order_by('-last_published')
-    qs = qs[:max_len]
-    for item in qs:
-        w  = WallItem('publish')
-        w.title = item.title
-        w.summary = item.title
-        w.url = chunk.book.get_absolute_url()
-        w.timestamp = item.last_published
-        w.user = item.last_published_by
-        yield w
-
-
-def comments_wall(max_len):
-    qs = Comment.objects.filter(is_public=True).select_related().order_by('-submit_date')
-    qs = qs[:max_len]
-    for item in qs:
-        w  = WallItem('comment')
-        w.title = item.content_object
-        w.summary = item.comment
-        w.url = item.content_object.get_absolute_url()
-        w.timestamp = item.submit_date
-        w.user = item.user
-        w.email = item.user_email
-        yield w
-
-
-def big_wall(max_len, *args):
-    """
-        Takes some WallItem iterators and zips them into one big wall.
-        Input iterators must already be sorted by timestamp.
-    """
-    subwalls = []
-    for w in args:
-        try:
-            subwalls.append([next(w), w])
-        except StopIteration:
-            pass
-
-    while max_len and subwalls:
-        i, next_item = max(enumerate(subwalls), key=lambda x: x[1][0].timestamp)
-        yield next_item[0]
-        max_len -= 1
-        try:
-            next_item[0] = next(next_item[1])
-        except StopIteration:
-            del subwalls[i]
-
-
-@register.inclusion_tag("wiki/wall.html", takes_context=True)
-def wall(context, max_len=10):
-    return {
-        "request": context['request'],
-        "STATIC_URL": context['STATIC_URL'],
-        "wall": big_wall(max_len,
-            changes_wall(max_len),
-            published_wall(max_len),
-            comments_wall(max_len),
-        )}
index 097de60..dc866cd 100644 (file)
@@ -1,39 +1,14 @@
 # -*- coding: utf-8
 from django.conf.urls.defaults import *
-from django.views.generic.simple import redirect_to
-from django.views.generic.list_detail import object_detail
-from django.conf import settings
-from wiki.models import Book
 
 
-#PART = ur"""[ ĄĆĘŁŃÓŚŻŹąćęłńóśżź0-9\w_.-]+"""
-
 urlpatterns = patterns('wiki.views',
-    url(r'^$', redirect_to, {'url': 'catalogue/'}),
-
-    url(r'^catalogue/$', 'document_list', name='wiki_document_list'),
-    #url(r'^catalogue/([^/]+)/$', 'document_list'),
-    #url(r'^catalogue/([^/]+)/([^/]+)/$', 'document_list'),
-    #url(r'^catalogue/([^/]+)/([^/]+)/([^/]+)$', 'document_list'),
-    url(r'^unassigned/$', 'unassigned', name='wiki_unassigned'),
-    url(r'^user/$', 'my', name='wiki_user'),
-    url(r'^user/(?P<username>[^/]+)/$', 'user', name='wiki_user'),
-    url(r'^users/$', 'users', name='wiki_users'),
-
     url(r'^edit/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$',
         'editor', name="wiki_editor"),
 
     url(r'^readonly/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$',
         'editor_readonly', name="wiki_editor_readonly"),
 
-    url(r'^upload/$',
-        'upload', name='wiki_upload'),
-
-    url(r'^create/(?P<slug>[^/]*)/',
-        'create_missing', name='wiki_create_missing'),
-    url(r'^create/',
-        'create_missing', name='wiki_create_missing'),
-
     url(r'^gallery/(?P<directory>[^/]+)/$',
         'gallery', name="wiki_gallery"),
 
@@ -49,26 +24,8 @@ urlpatterns = patterns('wiki.views',
     url(r'^revert/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$',
         'revert', name='wiki_revert'),
 
-    url(r'^book/(?P<slug>[^/]+)/publish$', 'publish', name="wiki_publish"),
-    #url(r'^(?P<name>[^/]+)/publish/(?P<version>\d+)$', 'publish', name="wiki_publish"),
-
     url(r'^diff/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$', 'diff', name="wiki_diff"),
-    url(r'^tag/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$', 'add_tag', name="wiki_add_tag"),
     url(r'^pubmark/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$', 'pubmark', name="wiki_pubmark"),
 
-    url(r'^book/(?P<slug>[^/]+)/$', 'book', name="wiki_book"),
-    url(r'^book/(?P<slug>[^/]+)/xml$', 'book_xml', name="wiki_book_xml"),
-    url(r'^book/(?P<slug>[^/]+)/txt$', 'book_txt', name="wiki_book_txt"),
-    url(r'^book/(?P<slug>[^/]+)/html$', 'book_html', name="wiki_book_html"),
-    #url(r'^book/(?P<slug>[^/]+)/epub$', 'book_epub', name="wiki_book_epub"),
-    #url(r'^book/(?P<slug>[^/]+)/pdf$', 'book_pdf', name="wiki_book_pdf"),
-    url(r'^chunk_add/(?P<slug>[^/]+)/(?P<chunk>[^/]+)/$',
-        'chunk_add', name="wiki_chunk_add"),
-    url(r'^chunk_edit/(?P<slug>[^/]+)/(?P<chunk>[^/]+)/$',
-        'chunk_edit', name="wiki_chunk_edit"),
-    url(r'^book_append/(?P<slug>[^/]+)/$',
-        'book_append', name="wiki_book_append"),
-    url(r'^book_edit/(?P<slug>[^/]+)/$',
-        'book_edit', name="wiki_book_edit"),
-
+    url(r'^themes$', 'themes', name="themes"),
 )
index 424de24..75650e0 100644 (file)
+from datetime import datetime
 import os
-from StringIO import StringIO
 import logging
-logger = logging.getLogger("fnp.wiki")
-
-from lxml import etree
 
 from django.conf import settings
-
-from django.contrib import auth
-from django.contrib.auth.models import User
-from django.contrib.auth.decorators import login_required
-from django.db.models import Count
-from django.utils.http import urlquote_plus
-from django.views.generic.simple import direct_to_template
-from django.views.decorators.http import require_POST, require_GET
 from django.core.urlresolvers import reverse
-from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError,
-                ajax_require_permission, recursive_groupby, active_tab)
-from wiki import helpers
 from django import http
-from django.shortcuts import get_object_or_404, redirect
 from django.http import Http404
-
-from wiki.models import Book, Chunk, Theme
-from wiki import forms
-from datetime import datetime
+from django.middleware.gzip import GZipMiddleware
+from django.utils.decorators import decorator_from_middleware
 from django.utils.encoding import smart_unicode
 from django.utils.translation import ugettext_lazy as _
-from django.utils.decorators import decorator_from_middleware
-from django.middleware.gzip import GZipMiddleware
+from django.views.decorators.http import require_POST, require_GET
+from django.views.generic.simple import direct_to_template
 
-import librarian.html
-import librarian.text
-from wiki import xml_tools
-from apiclient import api_call
+from catalogue.models import Book, Chunk
+import nice_diff
+from wiki import forms
+from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError,
+                ajax_require_permission)
+from wiki.models import Theme
 
 #
 # Quick hack around caching problems, TODO: use ETags
 #
 from django.views.decorators.cache import never_cache
 
-import nice_diff
-import operator
+logger = logging.getLogger("fnp.wiki")
 
 MAX_LAST_DOCS = 10
 
 
-@active_tab('all')
-@never_cache
-def document_list(request):
-    chunks_list = helpers.ChunksList(Chunk.objects.order_by(
-        'book__title', 'book', 'number'))
-
-    return direct_to_template(request, 'wiki/document_list.html', extra_context={
-        'books': chunks_list,
-        #'books': [helpers.BookChunks(b) for b in Book.objects.all().select_related()],
-        'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
-                        key=lambda x: x[1]['time'], reverse=True),
-    })
-
-
-@active_tab('unassigned')
-@never_cache
-def unassigned(request):
-    chunks_list = helpers.ChunksList(Chunk.objects.filter(
-        user=None).order_by('book__title', 'book__id', 'number'))
-
-    return direct_to_template(request, 'wiki/document_list.html', extra_context={
-        'books': chunks_list,
-        'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
-                        key=lambda x: x[1]['time'], reverse=True),
-    })
-
-
-@never_cache
-def user(request, username=None):
-    if username is None:
-        if request.user.is_authenticated():
-            user = request.user
-        else:
-            raise Http404
-    else:
-        user = get_object_or_404(User, username=username)
-
-    chunks_list = helpers.ChunksList(Chunk.objects.filter(
-        user=user).order_by('book__title', 'book', 'number'))
-
-    return direct_to_template(request, 'wiki/document_list.html', extra_context={
-        'books': chunks_list,
-        'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
-                        key=lambda x: x[1]['time'], reverse=True),
-    })
-my = login_required(active_tab('my')(user))
-
-
-@active_tab('users')
-def users(request):
-    return direct_to_template(request, 'wiki/user_list.html', extra_context={
-        'users': User.objects.all().annotate(count=Count('chunk')).order_by(
-            '-count', 'last_name', 'first_name'),
-    })
-
-
-@never_cache
-def logout_then_redirect(request):
-    auth.logout(request)
-    return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
-
-
 @never_cache
 def editor(request, slug, chunk=None, template_name='wiki/document_details.html'):
     try:
@@ -120,7 +42,7 @@ def editor(request, slug, chunk=None, template_name='wiki/document_details.html'
             try:
                 book = Book.objects.get(slug=slug)
             except Book.DoesNotExist:
-                return http.HttpResponseRedirect(reverse("wiki_create_missing", args=[slug]))
+                return http.HttpResponseRedirect(reverse("catalogue_create_missing", args=[slug]))
         else:
             raise Http404
 
@@ -141,7 +63,6 @@ def editor(request, slug, chunk=None, template_name='wiki/document_details.html'
         'forms': {
             "text_save": forms.DocumentTextSaveForm(prefix="textsave"),
             "text_revert": forms.DocumentTextRevertForm(prefix="textrevert"),
-            "add_tag": forms.DocumentTagForm(prefix="addtag"),
             "pubmark": forms.DocumentPubmarkForm(prefix="pubmark"),
         },
         'REDMINE_URL': settings.REDMINE_URL,
@@ -176,98 +97,6 @@ def editor_readonly(request, slug, chunk=None, template_name='wiki/document_deta
     })
 
 
-@active_tab('create')
-def create_missing(request, slug=None):
-    if slug is None:
-        slug = ''
-    slug = slug.replace(' ', '-')
-
-    if request.method == "POST":
-        form = forms.DocumentCreateForm(request.POST, request.FILES)
-        if form.is_valid():
-            
-            if request.user.is_authenticated():
-                creator = request.user
-            else:
-                creator = None
-            book = Book.create(creator=creator,
-                slug=form.cleaned_data['slug'],
-                title=form.cleaned_data['title'],
-                text=form.cleaned_data['text'],
-            )
-
-            return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
-    else:
-        form = forms.DocumentCreateForm(initial={
-                "slug": slug,
-                "title": slug.replace('-', ' ').title(),
-        })
-
-    return direct_to_template(request, "wiki/document_create_missing.html", extra_context={
-        "slug": slug,
-        "form": form,
-    })
-
-
-@active_tab('upload')
-def upload(request):
-    if request.method == "POST":
-        form = forms.DocumentsUploadForm(request.POST, request.FILES)
-        if form.is_valid():
-            import slughifi
-
-            if request.user.is_authenticated():
-                creator = request.user
-            else:
-                creator = None
-
-            zip = form.cleaned_data['zip']
-            skipped_list = []
-            ok_list = []
-            error_list = []
-            slugs = {}
-            existing = [book.slug for book in Book.objects.all()]
-            for filename in zip.namelist():
-                if filename[-1] == '/':
-                    continue
-                title = os.path.basename(filename)[:-4]
-                slug = slughifi(title)
-                if not (slug and filename.endswith('.xml')):
-                    skipped_list.append(filename)
-                elif slug in slugs:
-                    error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
-                elif slug in existing:
-                    error_list.append((filename, slug, _('Slug already used in repository.')))
-                else:
-                    try:
-                        zip.read(filename).decode('utf-8') # test read
-                        ok_list.append((filename, slug, title))
-                    except UnicodeDecodeError:
-                        error_list.append((filename, title, _('File should be UTF-8 encoded.')))
-                    slugs[slug] = filename
-
-            if not error_list:
-                for filename, slug, title in ok_list:
-                    Book.create(creator=creator,
-                        slug=slug,
-                        title=title,
-                        text=zip.read(filename).decode('utf-8'),
-                    )
-
-            return direct_to_template(request, "wiki/document_upload.html", extra_context={
-                "form": form,
-                "ok_list": ok_list,
-                "skipped_list": skipped_list,
-                "error_list": error_list,
-            })
-    else:
-        form = forms.DocumentsUploadForm()
-
-    return direct_to_template(request, "wiki/document_upload.html", extra_context={
-        "form": form,
-    })
-
-
 @never_cache
 @decorator_from_middleware(GZipMiddleware)
 def text(request, slug, chunk=None):
@@ -317,39 +146,6 @@ def text(request, slug, chunk=None):
         })
 
 
-@never_cache
-def book_xml(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
-
-    response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
-    response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
-    return response
-
-
-@never_cache
-def book_txt(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
-    output = StringIO()
-    # errors?
-    librarian.text.transform(StringIO(xml), output)
-    text = output.getvalue()
-    response = http.HttpResponse(text, content_type='text/plain', mimetype='text/plain')
-    response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug
-    return response
-
-
-@never_cache
-def book_html(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
-    output = StringIO()
-    # errors?
-    librarian.html.transform(StringIO(xml), output, parse_dublincore=False,
-                             flags=['full-page'])
-    html = output.getvalue()
-    response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
-    return response
-
-
 @never_cache
 @require_POST
 def revert(request, slug, chunk=None):
@@ -462,198 +258,6 @@ def history(request, slug, chunk=None):
     return JSONResponse(changes)
 
 
-def book(request, slug):
-    book = get_object_or_404(Book, slug=slug)
-
-    # TODO: most of this should go somewhere else
-
-    # do we need some automation?
-    first_master = None
-    chunks = []
-    need_fixing = False
-    choose_master = False
-
-    length = len(book)
-    for i, chunk in enumerate(book):
-        chunk_dict = {
-            "chunk": chunk,
-            "fix": [],
-            "grade": ""
-            }
-        graded = xml_tools.GradedText(chunk.materialize())
-        if graded.is_wl():
-            master = graded.master()
-            if first_master is None:
-                first_master = master
-            elif master != first_master:
-                chunk_dict['fix'].append('bad-master')
-
-            if i > 0 and not graded.has_trim_begin():
-                chunk_dict['fix'].append('trim-begin')
-            if i < length - 1 and not graded.has_trim_end():
-                chunk_dict['fix'].append('trim-end')
-
-            if chunk_dict['fix']:
-                chunk_dict['grade'] = 'wl-fix'
-            else:
-                chunk_dict['grade'] = 'wl'
-
-        elif graded.is_broken_wl():
-            chunk_dict['grade'] = 'wl-broken'
-        elif graded.is_xml():
-            chunk_dict['grade'] = 'xml'
-        else:
-            chunk_dict['grade'] = 'plain'
-            chunk_dict['fix'].append('wl')
-            choose_master = True
-
-        if chunk_dict['fix']:
-            need_fixing = True
-        chunks.append(chunk_dict)
-
-    if first_master or not need_fixing:
-        choose_master = False
-
-    if request.method == "POST":
-        form = forms.ChooseMasterForm(request.POST)
-        if not choose_master or form.is_valid():
-            if choose_master:
-                first_master = form.cleaned_data['master']
-
-            # do the actual fixing
-            for c in chunks:
-                if not c['fix']:
-                    continue
-
-                text = c['chunk'].materialize()
-                for fix in c['fix']:
-                    if fix == 'bad-master':
-                        text = xml_tools.change_master(text, first_master)
-                    elif fix == 'trim-begin':
-                        text = xml_tools.add_trim_begin(text)
-                    elif fix == 'trim-end':
-                        text = xml_tools.add_trim_end(text)
-                    elif fix == 'wl':
-                        text = xml_tools.basic_structure(text, first_master)
-                author = request.user if request.user.is_authenticated() else None
-                description = "auto-fix: " + ", ".join(c['fix'])
-                c['chunk'].commit(text=text, author=author, 
-                    description=description)
-
-            return http.HttpResponseRedirect(book.get_absolute_url())
-    elif choose_master:
-        form = forms.ChooseMasterForm()
-    else:
-        form = None
-
-    return direct_to_template(request, "wiki/book_detail.html", extra_context={
-        "book": book,
-        "chunks": chunks,
-        "need_fixing": need_fixing,
-        "choose_master": choose_master,
-        "first_master": first_master,
-        "form": form,
-    })
-
-
-def chunk_add(request, slug, chunk):
-    try:
-        doc = Chunk.get(slug, chunk)
-    except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
-        raise Http404
-
-    if request.method == "POST":
-        form = forms.ChunkAddForm(request.POST, instance=doc)
-        if form.is_valid():
-            if request.user.is_authenticated():
-                creator = request.user
-            else:
-                creator = None
-            doc.split(creator=creator,
-                slug=form.cleaned_data['slug'],
-                comment=form.cleaned_data['comment'],
-            )
-
-            return http.HttpResponseRedirect(doc.book.get_absolute_url())
-    else:
-        form = forms.ChunkAddForm(initial={
-                "slug": str(doc.number + 1),
-                "comment": "cz. %d" % (doc.number + 1, ),
-        })
-
-    return direct_to_template(request, "wiki/chunk_add.html", extra_context={
-        "chunk": doc,
-        "form": form,
-    })
-
-
-def chunk_edit(request, slug, chunk):
-    try:
-        doc = Chunk.get(slug, chunk)
-    except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
-        raise Http404
-    if request.method == "POST":
-        form = forms.ChunkForm(request.POST, instance=doc)
-        if form.is_valid():
-            form.save()
-            return http.HttpResponseRedirect(doc.book.get_absolute_url())
-    else:
-        form = forms.ChunkForm(instance=doc)
-    return direct_to_template(request, "wiki/chunk_edit.html", extra_context={
-        "chunk": doc,
-        "form": form,
-    })
-
-
-def book_append(request, slug):
-    book = get_object_or_404(Book, slug=slug)
-    if request.method == "POST":
-        form = forms.BookAppendForm(request.POST)
-        if form.is_valid():
-            append_to = form.cleaned_data['append_to']
-            append_to.append(book)
-            return http.HttpResponseRedirect(append_to.get_absolute_url())
-    else:
-        form = forms.BookAppendForm()
-    return direct_to_template(request, "wiki/book_append_to.html", extra_context={
-        "book": book,
-        "form": form,
-    })
-
-
-def book_edit(request, slug):
-    book = get_object_or_404(Book, slug=slug)
-    if request.method == "POST":
-        form = forms.BookForm(request.POST, instance=book)
-        if form.is_valid():
-            form.save()
-            return http.HttpResponseRedirect(book.get_absolute_url())
-    else:
-        form = forms.BookForm(instance=book)
-    return direct_to_template(request, "wiki/book_edit.html", extra_context={
-        "book": book,
-        "form": form,
-    })
-
-
-@require_POST
-@ajax_require_permission('wiki.can_change_tags')
-def add_tag(request, slug, chunk=None):
-    form = forms.DocumentTagForm(request.POST, prefix="addtag")
-    if form.is_valid():
-        try:
-            doc = Chunk.get(slug, chunk)
-        except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
-            raise Http404
-
-        tag = form.cleaned_data['tag']
-        revision = form.cleaned_data['revision']
-        doc.at_revision(revision).tags.add(tag)
-        return JSONResponse({"message": _("Tag added")})
-    else:
-        return JSONFormInvalid(form)
-
-
 @require_POST
 @ajax_require_permission('wiki.can_pubmark')
 def pubmark(request, slug, chunk=None):
@@ -667,7 +271,6 @@ def pubmark(request, slug, chunk=None):
         revision = form.cleaned_data['revision']
         publishable = form.cleaned_data['publishable']
         change = doc.at_revision(revision)
-        print publishable, change.publishable
         if publishable != change.publishable:
             change.publishable = publishable
             change.save()
@@ -678,20 +281,6 @@ def pubmark(request, slug, chunk=None):
         return JSONFormInvalid(form)
 
 
-@require_POST
-@login_required
-def publish(request, slug):
-    book = get_object_or_404(Book, slug=slug)
-    try:
-        ret = api_call(request.user, "books", {"book_xml": book.materialize()})
-    except BaseException, e:
-        return http.HttpResponse(e)
-    else:
-        book.last_published = datetime.now()
-        book.save()
-        return http.HttpResponseRedirect(book.get_absolute_url())
-
-
 def themes(request):
     prefix = request.GET.get('q', '')
     return http.HttpResponse('\n'.join([str(t) for t in Theme.objects.filter(name__istartswith=prefix)]))
diff --git a/apps/wiki/xml_tools.py b/apps/wiki/xml_tools.py
deleted file mode 100755 (executable)
index 6dc5089..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-from functools import wraps
-import re
-
-from lxml import etree
-from wiki.constants import TRIM_BEGIN, TRIM_END, MASTERS
-
-RE_TRIM_BEGIN = re.compile("^<!--%s-->$" % TRIM_BEGIN, re.M)
-RE_TRIM_END = re.compile("^<!--%s-->$" % TRIM_END, re.M)
-
-
-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
-        that eg. one big XML file can be compiled from many small XML files.
-    """
-    if trim_begin:
-        text = RE_TRIM_BEGIN.split(text, maxsplit=1)[-1]
-    if trim_end:
-        text = RE_TRIM_END.split(text, maxsplit=1)[0]
-    return text
-
-
-def compile_text(parts):
-    """ 
-        Compiles full text from an iterable of parts,
-        trimming where applicable.
-    """
-    texts = []
-    trim_begin = False
-    text = ''
-    for next_text in parts:
-        if not next_text:
-            continue
-        # trim the end, because there's more non-empty text
-        # don't trim beginning, if `text' is the first non-empty part
-        texts.append(_trim(text, trim_begin=trim_begin))
-        trim_begin = True
-        text = next_text
-    # don't trim the end, because there's no more text coming after `text'
-    # only trim beginning if it's not still the first non-empty
-    texts.append(_trim(text, trim_begin=trim_begin, trim_end=False))
-    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 etree.tostring(e, encoding="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 etree.tostring(e, encoding="utf-8")
-
-
-def add_trim_begin(text):
-    trim_tag = etree.Comment(TRIM_BEGIN)
-    e = etree.fromstring(text)
-    for master in e[::-1]:
-        if master.tag in MASTERS:
-            break
-    if master.tag not in MASTERS:
-        raise ParseError('No master tag found!')
-
-    master.insert(0, trim_tag)
-    trim_tag.tail = '\n\n\n' + (master.text or '')
-    master.text = '\n'
-    return etree.tostring(e, encoding="utf-8")
-
-
-def add_trim_end(text):
-    trim_tag = etree.Comment(TRIM_END)
-    e = etree.fromstring(text)
-    for master in e[::-1]:
-        if master.tag in MASTERS:
-            break
-    if master.tag not in MASTERS:
-        raise ParseError('No master tag found!')
-
-    master.append(trim_tag)
-    trim_tag.tail = '\n'
-    prev = trim_tag.getprevious()
-    if prev is not None:
-        prev.tail = (prev.tail or '') + '\n\n\n'
-    else:
-        master.text = (master.text or '') + '\n\n\n'
-    return etree.tostring(e, encoding="utf-8")
index eb6c9ba..9ff3805 100644 (file)
@@ -115,6 +115,7 @@ INSTALLED_APPS = (
     'pagination',
     'gravatar',
 
+    'catalogue',
     'dvcs',
     'wiki',
     'toolbar',
index 5554aca..bf3e817 100644 (file)
@@ -44,7 +44,6 @@ COMPRESS_JS = {
                 # dialogs
                 'js/wiki/dialog_save.js',
                 'js/wiki/dialog_revert.js',
-                'js/wiki/dialog_addtag.js',
                 'js/wiki/dialog_pubmark.js',
 
                 # views
diff --git a/redakcja/static/js/wiki/dialog_addtag.js b/redakcja/static/js/wiki/dialog_addtag.js
deleted file mode 100644 (file)
index 1a90ccf..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Dialog for saving document to the server
- *
- */
-(function($){
-
-    function AddTagDialog(element, options){
-        if (!options.revision  && options.revision != 0)
-            throw "AddTagDialog needs a revision number.";
-
-        this.ctx = $.wiki.exitContext();
-        this.clearForm();
-
-        /* fill out hidden fields */
-        this.$form = $('form', element);
-
-        $("input[name='addtag-id']", this.$form).val(CurrentDocument.id);
-        $("input[name='addtag-revision']", this.$form).val(options.revision);
-
-        $.wiki.cls.GenericDialog.call(this, element);
-    };
-
-    AddTagDialog.prototype = $.extend(new $.wiki.cls.GenericDialog(), {
-        cancelAction: function(){
-            $.wiki.enterContext(this.ctx);
-            this.hide();
-        },
-
-        saveAction: function(){
-            var self = this;
-
-            self.$elem.block({
-                message: "Dodawanie tagu",
-                fadeIn: 0,
-            });
-
-            CurrentDocument.setTag({
-                form: self.$form,
-                success: function(doc, changed, info){
-                    self.$elem.block({
-                        message: info,
-                        timeout: 2000,
-                        fadeOut: 0,
-                        onUnblock: function(){
-                            self.hide();
-                            $.wiki.enterContext(self.ctx);
-                        }
-                    });
-                },
-                failure: function(doc, info){
-                    console.log("Failure", info);
-                    self.reportErrors(info);
-                    self.$elem.unblock();
-                }
-            });
-        }
-    });
-
-    /* make it global */
-    $.wiki.cls.AddTagDialog = AddTagDialog;
-})(jQuery);
index bd6b27b..85adca0 100644 (file)
                                self.makeDiff();
                        });
 
-                       $('#tag-changeset-button').click(function() {
-                               self.showTagForm();
-                       });
-
                        $('#pubmark-changeset-button').click(function() {
                                self.showPubmarkForm();
                        });
         });
     };
 
-       HistoryPerspective.prototype.showTagForm = function(){
-               var selected = $('#changes-list .entry.selected');
-
-               if (selected.length != 1) {
-            window.alert("Musisz zaznaczyć dokładnie jedną wersję.");
-            return;
-        }
-
-               var version = parseInt($("*[data-stub-value='version']", selected[0]).text());
-               $.wiki.showDialog('#add_tag_dialog', {'revision': version});
-       };
-
        HistoryPerspective.prototype.showPubmarkForm = function(){
                var selected = $('#changes-list .entry.selected');
 
index cbed73b..3907e8e 100644 (file)
@@ -15,7 +15,7 @@
         */
        function reverse() {
                var vname = arguments[0];
-               var base_path = "/documents";
+               var base_path = "/editor";
 
                if (vname == "ajax_document_text") {
                        var path = "/text/" + arguments[1] + '/';
         if (vname == "ajax_document_rev")
             return base_path + "/rev/" + arguments[1] + '/';
 
-               if (vname == "ajax_document_addtag")
-                       return base_path + "/tag/" + arguments[1] + '/';
-
                if (vname == "ajax_document_pubmark")
                        return base_path + "/pubmark/" + arguments[1] + '/';
 
-               if (vname == "ajax_publish")
-                       return base_path + "/publish/" + arguments[1] + '/';
-
                console.log("Couldn't reverse match:", vname);
                return "/404.html";
        };
                        }
                });
        };
-       WikiDocument.prototype.setTag = function(params) {
-               params = $.extend({}, noops, params);
-               var self = this;
-               var data = {
-                       "addtag-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) {
-                               params.success(self, data.message);
-                       },
-                       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."]
-                                               });
-                                       };
-                               };
-                       }
-               });
-       };
 
        WikiDocument.prototype.pubmark = function(params) {
                params = $.extend({}, noops, params);
index 46726c1..0390fd4 100644 (file)
@@ -40,7 +40,7 @@ function withThemes(code_block, onError)
 {
     if (typeof withThemes.canon == 'undefined') {
         $.ajax({
-            url: '/themes',
+            url: '/editor/themes',
             dataType: 'text',
             success: function(data) {
                 withThemes.canon = data.split('\n');
index edb5fc2..1d76ee8 100644 (file)
@@ -4,14 +4,13 @@ from django.conf.urls.defaults import *
 from django.contrib import admin
 from django.conf import settings
 
-import wiki.urls
 
 admin.autodiscover()
 
 urlpatterns = patterns('',
     # Auth
     url(r'^accounts/login/$', 'django.contrib.auth.views.login', name='login'),
-    url(r'^accounts/logout/$', 'wiki.views.logout_then_redirect', name='logout'),
+    url(r'^accounts/logout/$', 'catalogue.views.logout_then_redirect', name='logout'),
 
     # Admin panel
     (r'^admin/filebrowser/', include('filebrowser.urls')),
@@ -21,9 +20,9 @@ urlpatterns = patterns('',
     (r'^comments/', include('django.contrib.comments.urls')),
 
     url(r'^$', 'django.views.generic.simple.redirect_to', {'url': '/documents/'}),
-    url(r'^documents/', include('wiki.urls')),
-    url(r'^storage/', include('dvcs.urls')),
+    url(r'^documents/', include('catalogue.urls')),
     url(r'^apiclient/', include('apiclient.urls')),
+    url(r'^editor/', include('wiki.urls')),
 
     # Static files (should be served by Apache)
     url(r'^%s(?P<path>.+)$' % settings.MEDIA_URL[1:], 'django.views.static.serve',
@@ -32,8 +31,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}),
-    (r'^documents/', include(wiki.urls)),
-    url(r'^themes$', 'wiki.views.themes', name="themes"),
+
     url(r'^$', 'django.views.generic.simple.redirect_to', {'url': '/documents/'}),
 
 )