Form used for uploading new documents.
"""
file = forms.FileField(required=True, label=_('ZIP file'))
+ dirs = forms.BooleanField(label=_('Directories are documents in chunks'),
+ widget = forms.CheckboxInput(attrs={'disabled':'disabled'}))
def clean(self):
file = self.cleaned_data['file']
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
-
+def cached_in_field(field_name):
+ def decorator(f):
+ @property
+ @wraps(f)
+ def wrapped(self, *args, **kwargs):
+ value = getattr(self, field_name)
+ if value is None:
+ value = f(self, *args, **kwargs)
+ type(self)._default_manager.filter(pk=self.pk).update(**{field_name: value})
+ return value
+ return wrapped
+ return decorator
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"
+"POT-Creation-Date: 2011-10-03 15:56+0200\n"
+"PO-Revision-Date: 2011-10-03 15:56+0100\n"
"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
"Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org.pl>\n"
-"Language: \n"
+"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
-#: forms.py:32
-msgid "Publishable"
-msgstr "Gotowe do publikacji"
-
-#: forms.py:68
+#: forms.py:46
msgid "ZIP file"
msgstr "Plik ZIP"
+#: forms.py:47
+msgid "Directories are documents in chunks"
+msgstr "Katalogi zawierają dokumenty w częściach"
+
+#: forms.py:85
#: 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
+#: forms.py:109
msgid "Append to"
msgstr "Dołącz do"
-#: models.py:25
+#: views.py:137
+#, python-format
+msgid "Slug already used for %s"
+msgstr "Slug taki sam jak dla pliku %s"
+
+#: views.py:139
+msgid "Slug already used in repository."
+msgstr "Dokument o tym slugu już istnieje w repozytorium."
+
+#: views.py:145
+msgid "File should be UTF-8 encoded."
+msgstr "Plik powinien mieć kodowanie UTF-8."
+
+#: models/book.py:20
+#: models/chunk.py:24
msgid "title"
msgstr "tytuł"
-#: models.py:26
+#: models/book.py:21
+#: models/chunk.py:23
msgid "slug"
-msgstr ""
+msgstr "slug"
-#: models.py:27
+#: models/book.py:22
msgid "scan gallery name"
msgstr "nazwa galerii skanów"
-#: models.py:29
+#: models/book.py:25
msgid "parent"
msgstr "rodzic"
-#: models.py:30
+#: models/book.py:26
msgid "parent number"
msgstr "numeracja rodzica"
-#: models.py:40
+#: models/book.py:43
+#: models/chunk.py:21
+#: models/publish_log.py:17
msgid "book"
msgstr "książka"
-#: models.py:41
+#: models/book.py:44
msgid "books"
msgstr "książki"
-#: models.py:206
-msgid "name"
-msgstr "nazwa"
+#: models/book.py:45
+msgid "Can mark for publishing"
+msgstr "Oznacza do publikacji"
-#: models.py:210
-msgid "theme"
-msgstr "motyw"
+#: models/chunk.py:22
+msgid "number"
+msgstr "numer"
-#: models.py:211
-msgid "themes"
-msgstr "motywy"
+#: models/chunk.py:39
+msgid "chunk"
+msgstr "część"
-#: views.py:241
-#, python-format
-msgid "Slug already used for %s"
-msgstr "Slug taki sam jak dla pliku %s"
+#: models/chunk.py:40
+msgid "chunks"
+msgstr "części"
-#: views.py:243
-msgid "Slug already used in repository."
-msgstr "Dokument o tym slugu już istnieje w repozytorium."
+#: models/publish_log.py:18
+msgid "time"
+msgstr "czas"
-#: views.py:249
-msgid "File should be UTF-8 encoded."
-msgstr "Plik powinien mieć kodowanie UTF-8."
+#: models/publish_log.py:19
+msgid "user"
+msgstr "użytkownik"
-#: views.py:655
-msgid "Tag added"
-msgstr "Dodano tag"
+#: models/publish_log.py:24
+#: models/publish_log.py:33
+msgid "book publish record"
+msgstr "zapis publikacji książki"
-#: views.py:677
-msgid "Revision marked"
-msgstr "Wersja oznaczona"
+#: models/publish_log.py:25
+msgid "book publish records"
+msgstr "zapisy publikacji książek"
-#: views.py:679
-msgid "Nothing changed"
-msgstr "Nic nie uległo zmianie"
+#: models/publish_log.py:34
+msgid "change"
+msgstr "zmiana"
-#: templates/wiki/base.html:9
+#: models/publish_log.py:38
+msgid "chunk publish record"
+msgstr "zapis publikacji części"
+
+#: models/publish_log.py:39
+msgid "chunk publish records"
+msgstr "zapisy publikacji części"
+
+#: templates/catalogue/base.html:9
msgid "Platforma Redakcyjna"
-msgstr ""
+msgstr "Platforma Redakcyjna"
-#: templates/wiki/book_append_to.html:8
+#: templates/catalogue/book_append_to.html:9
msgid "Append book"
msgstr "Dołącz książkę"
-#: templates/wiki/book_detail.html:6
-#: templates/wiki/book_detail.html.py:46
+#: templates/catalogue/book_detail.html:6
+#: templates/catalogue/book_detail.html:46
msgid "edit"
msgstr "edytuj"
-#: templates/wiki/book_detail.html:16
+#: templates/catalogue/book_detail.html:16
msgid "add basic document structure"
msgstr "dodaj podstawową strukturę dokumentu"
-#: templates/wiki/book_detail.html:20
+#: templates/catalogue/book_detail.html:20
msgid "change master tag to"
msgstr "zmień tak master na"
-#: templates/wiki/book_detail.html:24
+#: templates/catalogue/book_detail.html:24
msgid "add begin trimming tag"
msgstr "dodaj początkowy ogranicznik"
-#: templates/wiki/book_detail.html:28
+#: templates/catalogue/book_detail.html:28
msgid "add end trimming tag"
msgstr "dodaj końcowy ogranicznik"
-#: templates/wiki/book_detail.html:34
+#: templates/catalogue/book_detail.html:34
msgid "unstructured text"
msgstr "tekst bez struktury"
-#: templates/wiki/book_detail.html:38
+#: templates/catalogue/book_detail.html:38
msgid "unknown XML"
msgstr "nieznany XML"
-#: templates/wiki/book_detail.html:42
+#: templates/catalogue/book_detail.html:42
msgid "broken document"
msgstr "uszkodzony dokument"
-#: templates/wiki/book_detail.html:60
+#: templates/catalogue/book_detail.html:61
msgid "Apply fixes"
msgstr "Wykonaj zmiany"
-#: templates/wiki/book_detail.html:66
+#: templates/catalogue/book_detail.html:67
msgid "Append to other book"
msgstr "Dołącz do innej książki"
-#: templates/wiki/book_detail.html:68
+#: templates/catalogue/book_detail.html:69
msgid "Last published"
msgstr "Ostatnio opublikowano"
-#: templates/wiki/book_detail.html:72
+#: templates/catalogue/book_detail.html:73
msgid "Full XML"
msgstr "Pełny XML"
-#: templates/wiki/book_detail.html:73
+#: templates/catalogue/book_detail.html:74
msgid "HTML version"
msgstr "Wersja HTML"
-#: templates/wiki/book_detail.html:74
+#: templates/catalogue/book_detail.html:75
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
+#: templates/catalogue/book_detail.html:92
msgid "Publish"
msgstr "Opublikuj"
-#: templates/wiki/book_detail.html:94
+#: templates/catalogue/book_detail.html:96
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
+#: templates/catalogue/book_edit.html:9
+#: templates/catalogue/chunk_edit.html:9
msgid "Save"
msgstr "Zapisz"
-#: templates/wiki/chunk_add.html:8
+#: templates/catalogue/chunk_add.html:9
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
+#: templates/catalogue/document_create_missing.html:9
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
+#: templates/catalogue/document_upload.html:8
msgid "Bulk documents upload"
msgstr "Hurtowe dodawanie dokumentów"
-#: templates/wiki/document_upload.html:11
+#: templates/catalogue/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
+#: templates/catalogue/document_upload.html:17
+#: templatetags/catalogue.py:34
msgid "Upload"
msgstr "Załaduj"
-#: templates/wiki/document_upload.html:23
+#: templates/catalogue/document_upload.html:24
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
+#: templates/catalogue/document_upload.html:25
msgid "Offending files"
msgstr "Błędne pliki"
-#: templates/wiki/document_upload.html:32
+#: templates/catalogue/document_upload.html:33
msgid "Correct files"
msgstr "Poprawne pliki"
-#: templates/wiki/document_upload.html:43
+#: templates/catalogue/document_upload.html:44
msgid "Files have been successfully uploaded to the repository."
msgstr "Pliki zostały dodane do repozytorium."
-#: templates/wiki/document_upload.html:44
+#: templates/catalogue/document_upload.html:45
msgid "Uploaded files"
msgstr "Dodane pliki"
-#: templates/wiki/document_upload.html:54
+#: templates/catalogue/document_upload.html:55
msgid "Skipped files"
msgstr "Pominięte pliki"
-#: templates/wiki/document_upload.html:55
+#: templates/catalogue/document_upload.html:56
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/catalogue/my_page.html:13
+msgid "Your last edited documents"
+msgstr "Twoje ostatnie edycje"
-#: templates/wiki/revert_dialog.html:38
-msgid "Revert"
-msgstr "Przywróć"
+#: templates/catalogue/my_page.html:22
+#: templates/catalogue/user_page.html:13
+msgid "Recent activity for"
+msgstr "Ostatnia aktywność dla:"
-#: templates/wiki/user_list.html:7
-#: templatetags/wiki.py:33
+#: templates/catalogue/user_list.html:7
+#: templatetags/catalogue.py:32
msgid "Users"
msgstr "Użytkownicy"
-#: templates/wiki/tabs/annotations_view.html:9
-msgid "all"
-msgstr "wszystkie"
+#: templates/catalogue/book_list/book.html:6
+#: templates/catalogue/book_list/book.html:25
+msgid "Book settings"
+msgstr "Ustawienia książki"
-#: templates/wiki/tabs/annotations_view_item.html:3
-msgid "Annotations"
-msgstr "Przypisy"
+#: templates/catalogue/book_list/book.html:7
+#: templates/catalogue/book_list/chunk.html:5
+msgid "Chunk settings"
+msgstr "Ustawienia części"
-#: templates/wiki/tabs/gallery_view.html:7
-msgid "Previous"
-msgstr "Poprzednie"
+#: templates/catalogue/book_list/book_list.html:19
+msgid "Show hidden books"
+msgstr "Pokaż ukryte książki"
-#: templates/wiki/tabs/gallery_view.html:13
-msgid "Next"
-msgstr "Następne"
+#: templates/catalogue/book_list/book_list.html:24
+msgid "Search in book titles"
+msgstr "Szukaj w tytułach książek"
-#: templates/wiki/tabs/gallery_view.html:15
-msgid "Zoom in"
-msgstr "Powiększ"
+#: templates/catalogue/book_list/book_list.html:29
+msgid "stage"
+msgstr "etap"
-#: templates/wiki/tabs/gallery_view.html:16
-msgid "Zoom out"
-msgstr "Zmniejsz"
+#: templates/catalogue/book_list/book_list.html:31
+#: templates/catalogue/book_list/book_list.html:42
+msgid "none"
+msgstr "brak"
-#: templates/wiki/tabs/gallery_view_item.html:3
-msgid "Gallery"
-msgstr "Galeria"
+#: templates/catalogue/book_list/book_list.html:40
+msgid "editor"
+msgstr "redaktor"
-#: templates/wiki/tabs/history_view.html:5
-msgid "Compare versions"
-msgstr "Porównaj wersje"
+#: templates/catalogue/book_list/book_list.html:51
+msgid "status"
+msgstr "status"
-#: templates/wiki/tabs/history_view.html:7
-msgid "Mark for publishing"
-msgstr "Oznacz do publikacji"
+#: templates/catalogue/book_list/book_list.html:75
+#, python-format
+msgid "%(c)s book"
+msgid_plural "%(c)s books"
+msgstr[0] "%(c)s książka"
+msgstr[1] "%(c)s książki"
+msgstr[2] "%(c)s książek"
-#: templates/wiki/tabs/history_view.html:9
-msgid "Revert document"
-msgstr "Przywróć wersję"
+#: templates/catalogue/book_list/book_list.html:80
+msgid "No books found."
+msgstr "Nie znaleziono książek."
-#: templates/wiki/tabs/history_view.html:12
-msgid "View version"
-msgstr "Zobacz wersję"
+#: templatetags/book_list.py:81
+msgid "publishable"
+msgstr "do publikacji"
-#: templates/wiki/tabs/history_view_item.html:3
-msgid "History"
-msgstr "Historia"
+#: templatetags/book_list.py:82
+msgid "changed"
+msgstr "zmienione"
-#: templates/wiki/tabs/search_view.html:3
-#: templates/wiki/tabs/search_view.html:5
-msgid "Search"
-msgstr "Szukaj"
+#: templatetags/book_list.py:83
+msgid "published"
+msgstr "opublikowane"
-#: templates/wiki/tabs/search_view.html:8
-msgid "Replace with"
-msgstr "Zamień na"
+#: templatetags/book_list.py:84
+msgid "unpublished"
+msgstr "nie opublikowane"
-#: templates/wiki/tabs/search_view.html:10
-msgid "Replace"
-msgstr "Zamień"
+#: templatetags/book_list.py:85
+msgid "empty"
+msgstr "puste"
-#: templates/wiki/tabs/search_view.html:13
-msgid "Options"
-msgstr "Opcje"
+#: templatetags/catalogue.py:28
+msgid "My page"
+msgstr "Moja strona"
-#: templates/wiki/tabs/search_view.html:15
-msgid "Case sensitive"
-msgstr "Rozróżniaj wielkość liter"
+#: templatetags/catalogue.py:30
+msgid "Activity"
+msgstr "Aktywność"
-#: templates/wiki/tabs/search_view.html:17
-msgid "From cursor"
-msgstr "Zacznij od kursora"
+#: templatetags/catalogue.py:31
+msgid "All"
+msgstr "Wszystkie"
-#: templates/wiki/tabs/search_view_item.html:3
-msgid "Search and replace"
-msgstr "Znajdź i zamień"
+#: templatetags/catalogue.py:33
+msgid "Add"
+msgstr "Dodaj"
-#: templates/wiki/tabs/source_editor_item.html:5
-msgid "Source code"
-msgstr "Kod źródłowy"
+#: templatetags/catalogue.py:37
+msgid "Admin"
+msgstr "Administracja"
-#: templates/wiki/tabs/summary_view.html:9
-msgid "Title"
-msgstr "Tytuł"
+#: templatetags/wall.py:43
+msgid "Related edit"
+msgstr "Powiązana zmiana"
-#: templates/wiki/tabs/summary_view.html:14
-msgid "Document ID"
-msgstr "ID dokumentu"
+#: templatetags/wall.py:45
+msgid "Edit"
+msgstr "Zmiana"
-#: templates/wiki/tabs/summary_view.html:18
-msgid "Current version"
-msgstr "Aktualna wersja"
+#: templatetags/wall.py:67
+msgid "Publication"
+msgstr "Publikacja"
-#: templates/wiki/tabs/summary_view.html:21
-msgid "Last edited by"
-msgstr "Ostatnio edytowane przez"
+#: templatetags/wall.py:84
+msgid "Comment"
+msgstr "Komentarz"
-#: templates/wiki/tabs/summary_view.html:25
-msgid "Link to gallery"
-msgstr "Link do galerii"
+#~ msgid "Author"
+#~ msgstr "Autor"
-#: templates/wiki/tabs/summary_view_item.html:3
-msgid "Summary"
-msgstr "Podsumowanie"
+#~ msgid "Your name"
+#~ msgstr "Imię i nazwisko"
-#: templates/wiki/tabs/wysiwyg_editor.html:9
-msgid "Insert theme"
-msgstr "Wstaw motyw"
+#~ msgid "Author's email"
+#~ msgstr "E-mail autora"
-#: templates/wiki/tabs/wysiwyg_editor.html:12
-msgid "Insert annotation"
-msgstr "Wstaw przypis"
+#~ msgid "Your email address, so we can show a gravatar :)"
+#~ msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)"
-#: templates/wiki/tabs/wysiwyg_editor_item.html:3
-msgid "Visual editor"
-msgstr "Edytor wizualny"
+#~ msgid "Describe changes you made."
+#~ msgstr "Opisz swoje zmiany"
-#: templatetags/wiki.py:30
-msgid "Assigned to me"
-msgstr "Przypisane do mnie"
+#~ msgid "Completed"
+#~ msgstr "Ukończono"
-#: templatetags/wiki.py:32
-msgid "Unassigned"
-msgstr "Nie przypisane"
+#~ msgid "If you completed a life cycle stage, select it."
+#~ msgstr "Jeśli został ukończony etap prac, wskaż go."
-#: templatetags/wiki.py:34
-msgid "All"
-msgstr "Wszystkie"
+#~ msgid "Describe the reason for reverting."
+#~ msgstr "Opisz powód przywrócenia."
-#: templatetags/wiki.py:35
-msgid "Add"
-msgstr "Dodaj"
+#~ msgid "name"
+#~ msgstr "nazwa"
-#: templatetags/wiki.py:39
-msgid "Admin"
-msgstr "Administracja"
+#~ msgid "theme"
+#~ msgstr "motyw"
+
+#~ msgid "themes"
+#~ msgstr "motywy"
+
+#~ msgid "Tag added"
+#~ msgstr "Dodano tag"
+
+#~ msgid "Revision marked"
+#~ msgstr "Wersja oznaczona"
+
+#~ msgid "EPUB version"
+#~ msgstr "Wersja EPUB"
+
+#~ msgid "PDF version"
+#~ msgstr "Wersja PDF"
+
+#~ msgid "Old version"
+#~ msgstr "Stara wersja"
+
+#~ msgid "New version"
+#~ msgstr "Nowa wersja"
+
+#~ msgid "Click to open/close gallery"
+#~ msgstr "Kliknij, aby (ro)zwinąć galerię"
+
+#~ msgid "Help"
+#~ msgstr "Pomoc"
+
+#~ msgid "Version"
+#~ msgstr "Wersja"
+
+#~ msgid "Unknown"
+#~ msgstr "nieznana"
+
+#~ msgid "Save attempt in progress"
+#~ msgstr "Trwa zapisywanie"
+
+#~ msgid "There is a newer version of this document!"
+#~ msgstr "Istnieje nowsza wersja tego dokumentu!"
+
+#~ msgid "Clear filter"
+#~ msgstr "Wyczyść filtr"
+
+#~ msgid "Cancel"
+#~ msgstr "Anuluj"
+
+#~ msgid "Revert"
+#~ msgstr "Przywróć"
+
+#~ msgid "all"
+#~ msgstr "wszystkie"
+
+#~ msgid "Annotations"
+#~ msgstr "Przypisy"
+
+#~ msgid "Previous"
+#~ msgstr "Poprzednie"
+
+#~ msgid "Next"
+#~ msgstr "Następne"
+
+#~ msgid "Zoom in"
+#~ msgstr "Powiększ"
+
+#~ msgid "Zoom out"
+#~ msgstr "Zmniejsz"
+
+#~ msgid "Gallery"
+#~ msgstr "Galeria"
+
+#~ msgid "Compare versions"
+#~ msgstr "Porównaj wersje"
+
+#~ msgid "Revert document"
+#~ msgstr "Przywróć wersję"
+
+#~ msgid "View version"
+#~ msgstr "Zobacz wersję"
+
+#~ msgid "History"
+#~ msgstr "Historia"
+
+#~ msgid "Search"
+#~ msgstr "Szukaj"
+
+#~ msgid "Replace with"
+#~ msgstr "Zamień na"
+
+#~ msgid "Replace"
+#~ msgstr "Zamień"
+
+#~ msgid "Options"
+#~ msgstr "Opcje"
+
+#~ msgid "Case sensitive"
+#~ msgstr "Rozróżniaj wielkość liter"
+
+#~ msgid "From cursor"
+#~ msgstr "Zacznij od kursora"
+
+#~ msgid "Search and replace"
+#~ msgstr "Znajdź i zamień"
+
+#~ msgid "Source code"
+#~ msgstr "Kod źródłowy"
+
+#~ msgid "Title"
+#~ msgstr "Tytuł"
+
+#~ msgid "Document ID"
+#~ msgstr "ID dokumentu"
+
+#~ msgid "Current version"
+#~ msgstr "Aktualna wersja"
+
+#~ msgid "Last edited by"
+#~ msgstr "Ostatnio edytowane przez"
+
+#~ msgid "Link to gallery"
+#~ msgstr "Link do galerii"
+
+#~ msgid "Summary"
+#~ msgstr "Podsumowanie"
+
+#~ msgid "Insert theme"
+#~ msgstr "Wstaw motyw"
+
+#~ msgid "Insert annotation"
+#~ msgstr "Wstaw przypis"
+
+#~ msgid "Visual editor"
+#~ msgstr "Edytor wizualny"
+
+#~ msgid "Assigned to me"
+#~ msgstr "Przypisane do mnie"
+
+#~ msgid "Unassigned"
+#~ msgstr "Nie przypisane"
#~ msgid "First correction"
#~ msgstr "Autokorekta"
# -*- coding: utf-8 -*-
+from collections import defaultdict
import json
from optparse import make_option
import urllib2
transaction.managed(True)
if verbose:
- print 'Reading currently managed files.'
- slugs = {}
- for b in Book.objects.all():
+ print 'Reading currently managed files (skipping hidden ones).'
+ slugs = defaultdict(list)
+ for b in Book.objects.exclude(slug__startswith='.').all():
if verbose:
print b.slug
text = b.materialize().encode('utf-8')
except (ParseError, ValidationError):
pass
else:
- slugs[info.slug] = b
+ slugs[info.slug].append(b)
+
+ #~ conflicts = []
+ #~ for slug, book_list in slugs.items():
+ #~ if len(book_list) > 1:
+ #~ conflicts.append((slug, book_list))
+ #~ if conflicts:
+ #~ print self.style.ERROR("There is more than one book "
+ #~ "with the same slug in dc:url. "
+ #~ "Merge or hide them before proceeding.")
+ #~ for slug, book_list in sorted(conflicts):
+ #~ print slug
+ #~ print "\n".join(b.slug for b in book_list)
+ #~ print
+ #~ return
book_count = 0
commit_args = {
"author_name": 'Platforma',
- "description": 'Import from WL',
+ "description": 'Automatycznie zaimportowane z Wolnych Lektur',
+ "publishable": True,
}
if verbose:
print 'Opening books list'
- for book in json.load(urllib2.urlopen(WL_API)):
+ for book in json.load(urllib2.urlopen(WL_API))[:10]:
book_detail = json.load(urllib2.urlopen(book['href']))
xml_text = urllib2.urlopen(book_detail['xml']).read()
info = BookInfo.from_string(xml_text)
- previous_book = slugs.get(info.slug, None)
- if previous_book:
+ previous_books = slugs.get(info.slug)
+ if previous_books:
+ if len(previous_books) > 1:
+ print self.style.ERROR("There is more than one book "
+ "with slug %s:"),
+ previous_book = previous_books[0]
comm = previous_book.slug
else:
+ previous_book = None
comm = '*'
print book_count, info.slug , '-->', comm
Book.import_xml_text(xml_text, title=info.title,
- slug=info.slug, previous_book=slugs.get(info.slug, None))
+ slug=info.slug, previous_book=previous_book,
+ commit_args=commit_args)
book_count += 1
# Print results
return "".join(common)
-def print_guess(dry_run=True):
- from collections import defaultdict
- from pipes import quote
- import re
-
- def read_slug(slug):
- res = []
- res.append((re.compile(ur'__?(przedmowa)$'), -1))
- res.append((re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P<n>\d*)$'), None))
- res.append((re.compile(ur'__?(rozdzialy__?)?(?P<n>\d*)-'), None))
-
- for r, default in res:
- m = r.search(slug)
- if m:
- start = m.start()
- try:
- return int(m.group('n')), slug[:start]
- except IndexError:
- return default, slug[:start]
- return None, slug
-
- 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)
-
- merges = defaultdict(list)
- for b in Book.objects.all():
- n, ns = read_slug(b.slug)
- if n is not None:
- merges[ns].append((n, b))
-
- for slug in sorted(merges.keys()):
- merge_list = sorted(merges[slug])
- if len(merge_list) < 2:
- continue
-
- title = file_to_title(slug)
- print "./manage.py merge_books %s--title=%s --slug=%s \\\n %s\n" % (
- '--dry-run ' if dry_run else '',
- quote(title), slug,
- " \\\n ".join(b.slug for i, b in merge_list)
- )
-
-
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('-s', '--slug', dest='new_slug', metavar='SLUG',
help='Try to guess what merges are needed (but do not apply them).'),
make_option('-d', '--dry-run', action='store_true', dest='dry_run', default=False,
help='Dry run: do not actually change anything.'),
+ make_option('-f', '--force', action='store_true', dest='force', default=False,
+ help='On slug conflict, hide the original book to archive.'),
)
help = 'Merges multiple books into one.'
args = '[slug]...'
+
+ def print_guess(self, dry_run=True, force=False):
+ from collections import defaultdict
+ from pipes import quote
+ import re
+
+ def read_slug(slug):
+ res = []
+ res.append((re.compile(ur'__?(przedmowa)$'), -1))
+ res.append((re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P<n>\d*)$'), None))
+ res.append((re.compile(ur'__?(rozdzialy__?)?(?P<n>\d*)-'), None))
+
+ for r, default in res:
+ m = r.search(slug)
+ if m:
+ start = m.start()
+ try:
+ return int(m.group('n')), slug[:start]
+ except IndexError:
+ return default, slug[:start]
+ return None, slug
+
+ 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)
+
+ merges = defaultdict(list)
+ slugs = []
+ for b in Book.objects.all():
+ slugs.append(b.slug)
+ n, ns = read_slug(b.slug)
+ if n is not None:
+ merges[ns].append((n, b))
+
+ conflicting_slugs = []
+ for slug in sorted(merges.keys()):
+ merge_list = sorted(merges[slug])
+ if len(merge_list) < 2:
+ continue
+
+ merge_slugs = [b.slug for i, b in merge_list]
+ if slug in slugs and slug not in merge_slugs:
+ conflicting_slugs.append(slug)
+
+ title = file_to_title(slug)
+ print "./manage.py merge_books %s%s--title=%s --slug=%s \\\n %s\n" % (
+ '--dry-run ' if dry_run else '',
+ '--force ' if force else '',
+ quote(title), slug,
+ " \\\n ".join(merge_slugs)
+ )
+
+ if conflicting_slugs:
+ if force:
+ print self.style.NOTICE('# These books will be archived:')
+ else:
+ print self.style.ERROR('# ERROR: Conflicting slugs:')
+ for slug in conflicting_slugs:
+ print '#', slug
+
+
def handle(self, *slugs, **options):
self.style = color_style()
+ force = options.get('force')
guess = options.get('guess')
dry_run = options.get('dry_run')
new_slug = options.get('new_slug')
print "Please specify either slugs, or --guess."
return
else:
- print_guess(dry_run)
+ self.print_guess(dry_run, force)
return
if not slugs:
print "Please specify some book slugs"
return
-
# Start transaction management.
transaction.commit_unless_managed()
transaction.enter_transaction_management()
transaction.managed(True)
-
books = [Book.objects.get(slug=slug) for slug in slugs]
common_slug = common_prefix(slugs)
common_title = common_prefix([b.title for b in books])
elif common_slug.startswith(new_slug):
common_slug = new_slug
+ if slugs[0] != new_slug and Book.objects.filter(slug=new_slug).exists():
+ self.style.ERROR('Book already exists, skipping!')
+
+
if dry_run and verbose:
print self.style.NOTICE('DRY RUN: nothing will be changed.')
print
print
if not dry_run:
+ try:
+ conflict = Book.objects.get(slug=new_slug)
+ except Book.DoesNotExist:
+ conflict = None
+ else:
+ if conflict == books[0]:
+ conflict = None
+
+ if conflict:
+ if force:
+ # FIXME: there still may be a conflict
+ conflict.slug = '.' + conflict.slug
+ conflict.save()
+ print self.style.NOTICE('Book with slug "%s" moved to "%s".' % (new_slug, conflict.slug))
+ else:
+ print self.style.ERROR('ERROR: Book with slug "%s" exists.' % new_slug)
+ return
+
if i:
books[0].append(books[i], slugs=chunk_slugs, titles=chunk_titles)
else:
--- /dev/null
+from django.db import models
+
+class VisibleManager(models.Manager):
+ def get_query_set(self):
+ return super(VisibleManager, self).get_query_set().exclude(_hidden=True)
from south.v2 import SchemaMigration
from django.db import models
-
class Migration(SchemaMigration):
def forwards(self, orm):
('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)),
+ ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+ ('_single', self.gf('django.db.models.fields.NullBooleanField')(db_index=True, null=True, blank=True)),
+ ('_new_publishable', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)),
+ ('_published', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)),
))
db.send_create_signal('catalogue', ['Book'])
('number', self.gf('django.db.models.fields.IntegerField')()),
('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
+ ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+ ('_hidden', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)),
+ ('_changed', self.gf('django.db.models.fields.NullBooleanField')(null=True, 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)),
))
('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)),
- ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
('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'])),
('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'])),
+ ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
))
db.send_create_signal('catalogue', ['ChunkChange'])
# Adding model 'BookPublishRecord'
db.create_table('catalogue_bookpublishrecord', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Book'])),
+ ('book', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.Book'])),
('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
))
db.create_table('catalogue_chunkpublishrecord', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('book_record', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.BookPublishRecord'])),
- ('change', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ChunkChange'])),
+ ('change', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.ChunkChange'])),
))
db.send_create_signal('catalogue', ['ChunkPublishRecord'])
- if not db.dry_run:
- from django.core.management import call_command
- call_command("loaddata", "stages.json")
-
def backwards(self, orm):
},
'catalogue.book': {
'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'},
+ '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
},
'catalogue.bookpublishrecord': {
'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
+ 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'catalogue.chunk': {
'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
+ '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'title': ('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'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
'catalogue.chunkchange': {
'catalogue.chunkpublishrecord': {
'Meta': {'object_name': 'ChunkPublishRecord'},
'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkChange']"}),
+ 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'catalogue.chunktag': {
+++ /dev/null
-# encoding: utf-8
-import datetime
-from zlib import compress
-import os
-import os.path
-import re
-import urllib
-
-from django.db import models
-from mercurial import hg, ui
-from south.db import db
-from south.v2 import DataMigration
-
-from django.conf import settings
-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 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')
- try:
- chunk.stage = orm.ChunkTag.objects.order_by('ordering')[0]
- except IndexError:
- chunk.stage = None
-
- maxrev = entry.filerev()
- gallery_link = None
-
- # this will fail if directory exists
- os.makedirs(os.path.join(settings.DVCS_REPO_PATH, str(chunk.pk)))
-
- 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,
- created_at=datetime.datetime.fromtimestamp(fctx.date()[0]),
- description=description,
- author=author,
- author_name=author_name,
- author_email=author_email,
- parent=chunk.head
- )
-
- path = "%d/%d" % (chunk.pk, head.pk)
- abs_path = os.path.join(settings.DVCS_REPO_PATH, path)
- f = open(abs_path, 'wb')
- f.write(compress(data))
- f.close()
- head.data = path
-
- head.tags = tags
- head.save()
-
- chunk.head = head
-
- chunk.save()
- if gallery_link:
- book.gallery = gallery_link
- book.save()
-
-
-class Migration(DataMigration):
-
- def forwards(self, orm):
- try:
- hg_path = settings.WIKI_REPOSITORY_PATH
- except:
- print 'repository not configured, skipping'
- else:
- print 'migrate from', hg_path
- repo = hg.repository(ui.ui(), hg_path)
- tip = repo['tip']
- for fname in tip:
- if fname.startswith('.') or not fname.startswith('a'):
- continue
- migrate_file_from_hg(orm, fname, tip[fname])
-
-
- def backwards(self, orm):
- "Write your backwards methods here."
- pass
-
-
- 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'}),
- '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.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'title': ('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'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
- },
- 'contenttypes.contenttype': {
- 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
- 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
- }
- }
-
- complete_apps = ['catalogue']
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+
+ from django.core.management import call_command
+ call_command("loaddata", "stages.json")
+
+
+ def backwards(self, orm):
+ "Write your backwards methods here."
+
+
+ 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'},
+ '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+ 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+ 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
+ },
+ 'catalogue.bookpublishrecord': {
+ 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
+ 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'catalogue.chunk': {
+ 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
+ '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_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'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+ },
+ 'catalogue.chunkchange': {
+ 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+ 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+ 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
+ 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
+ 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
+ },
+ 'catalogue.chunkpublishrecord': {
+ 'Meta': {'object_name': 'ChunkPublishRecord'},
+ 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
+ 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'catalogue.chunktag': {
+ 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'ordering': ('django.db.models.fields.IntegerField', [], {}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['catalogue']
--- /dev/null
+# encoding: utf-8
+import datetime
+from zlib import compress
+import os
+import os.path
+import re
+import urllib
+
+from django.db import models
+from mercurial import hg, ui
+from south.db import db
+from south.v2 import DataMigration
+
+from django.conf import settings
+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 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')
+ try:
+ chunk.stage = orm.ChunkTag.objects.order_by('ordering')[0]
+ except IndexError:
+ chunk.stage = None
+
+ maxrev = entry.filerev()
+ gallery_link = None
+
+ # this will fail if directory exists
+ os.makedirs(os.path.join(settings.CATALOGUE_REPO_PATH, str(chunk.pk)))
+
+ 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,
+ created_at=datetime.datetime.fromtimestamp(fctx.date()[0]),
+ description=description,
+ author=author,
+ author_name=author_name,
+ author_email=author_email,
+ parent=chunk.head
+ )
+
+ path = "%d/%d" % (chunk.pk, head.pk)
+ abs_path = os.path.join(settings.CATALOGUE_REPO_PATH, path)
+ f = open(abs_path, 'wb')
+ f.write(compress(data))
+ f.close()
+ head.data = path
+
+ head.tags = tags
+ head.save()
+
+ chunk.head = head
+
+ chunk.save()
+ if gallery_link:
+ book.gallery = gallery_link
+ book.save()
+
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+ try:
+ hg_path = settings.WIKI_REPOSITORY_PATH
+ except:
+ print 'repository not configured, skipping'
+ else:
+ 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])
+
+
+ def backwards(self, orm):
+ "Write your backwards methods here."
+ pass
+
+
+ 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'},
+ '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+ 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
+ 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
+ },
+ 'catalogue.bookpublishrecord': {
+ 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
+ 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'catalogue.chunk': {
+ 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
+ '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_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'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+ },
+ 'catalogue.chunkchange': {
+ 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+ 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+ 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
+ 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
+ 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
+ },
+ 'catalogue.chunkpublishrecord': {
+ 'Meta': {'object_name': 'ChunkPublishRecord'},
+ 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
+ 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'catalogue.chunktag': {
+ 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'ordering': ('django.db.models.fields.IntegerField', [], {}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['catalogue']
+++ /dev/null
-# -*- 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.core.urlresolvers import reverse
-from django.db import models
-from django.utils.translation import ugettext_lazy as _
-from django.db.utils import IntegrityError
-
-from slughifi import slughifi
-
-from dvcs import models as dvcs_models
-from catalogue.xml_tools import compile_text, split_xml
-
-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)
-
- class NoTextError(BaseException):
- pass
-
- class Meta:
- ordering = ['parent_number', 'title']
- verbose_name = _('book')
- verbose_name_plural = _('books')
- permissions = [('can_pubmark', 'Can mark for publishing')]
-
- def __unicode__(self):
- return self.title
-
- def get_absolute_url(self):
- return reverse("catalogue_book", args=[self.slug])
-
- @classmethod
- def import_xml_text(cls, text=u'', creator=None, previous_book=None,
- *args, **kwargs):
-
- texts = split_xml(text)
- if previous_book:
- instance = previous_book
- else:
- instance = cls(*args, **kwargs)
- instance.save()
-
- # if there are more parts, set the rest to empty strings
- book_len = len(instance)
- for i in range(book_len - len(texts)):
- texts.append(u'pusta część %d' % (i + 1), u'')
-
- i = 0
- for i, (title, text) in enumerate(texts):
- if not title:
- title = u'część %d' % (i + 1)
-
- slug = slughifi(title)
-
- if i < book_len:
- chunk = instance[i]
- chunk.slug = slug
- chunk.title = title
- chunk.save()
- else:
- chunk = instance.add(slug, title, creator, adjust_slug=True)
-
- chunk.commit(text, author=creator)
-
- return instance
-
- @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(text, author=creator)
- return instance
-
- def __iter__(self):
- return iter(self.chunk_set.all())
-
- def __getitem__(self, chunk):
- return self.chunk_set.all()[chunk]
-
- def __len__(self):
- return self.chunk_set.count()
-
- def __nonzero__(self):
- """
- Necessary so that __len__ isn't used for bool evaluation.
- """
- return True
-
- def get_current_changes(self, publishable=True):
- """
- Returns a list containing one Change for every Chunk in the Book.
- Takes the most recent revision (publishable, if set).
- Throws an error, if a proper revision is unavailable for a Chunk.
- """
- if publishable:
- changes = [chunk.publishable() for chunk in self]
- else:
- changes = [chunk.head for chunk in self if chunk.head is not None]
- if None in changes:
- raise self.NoTextError('Some chunks have no available text.')
- return changes
-
- def materialize(self, publishable=False, changes=None):
- """
- Get full text of the document compiled from chunks.
- Takes the current versions of all texts
- or versions most recently tagged for publishing,
- or a specified iterable changes.
- """
- if changes is None:
- changes = self.get_current_changes(publishable)
- return compile_text(change.materialize() for change in changes)
-
- def publishable(self):
- if not self.chunk_set.exists():
- return False
- for chunk in self:
- if not chunk.publishable():
- return False
- return True
-
- def publish(self, user):
- """
- Publishes a book on behalf of a (local) user.
- """
- from apiclient import api_call
-
- changes = self.get_current_changes(publishable=True)
- book_xml = book.materialize(changes=changes)
- #api_call(user, "books", {"book_xml": book_xml})
- # record the publish
- br = BookPublishRecord.objects.create(book=self, user=user)
- for c in changes:
- ChunkPublishRecord.objects.create(book_record=br, change=c)
-
- 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, slugs=None, titles=None):
- """Add all chunks of another book to self."""
- number = self[len(self) - 1].number + 1
- len_other = len(other)
- single = len_other == 1
-
- if slugs is not None:
- assert len(slugs) == len_other
- if titles is not None:
- assert len(titles) == len_other
- if slugs is None:
- slugs = [slughifi(t) for t in titles]
-
- for i, chunk in enumerate(other):
- # move chunk to new book
- chunk.book = self
- chunk.number = number
-
- if titles is None:
- # 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.title = 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.title = "%s, %s" % (other_title_part, chunk.title)
- else:
- chunk.slug = slugs[i]
- chunk.title = titles[i]
-
- chunk.slug = self.make_chunk_slug(chunk.slug)
- chunk.save()
- number += 1
- other.delete()
-
- def add(self, *args, **kwargs):
- """Add a new chunk at the end."""
- return self.chunk_set.reverse()[0].split(*args, **kwargs)
-
- @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()
- title = 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.title)
-
- 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.title:
- title += ", %s" % self.title
- if book_length > 1:
- title += " (%d/%d)" % (self.number, book_length)
- return title
-
- def split(self, slug, title='', creator=None, adjust_slug=False):
- """ Create an empty chunk after this one """
- self.book.chunk_set.filter(number__gt=self.number).update(
- number=models.F('number')+1)
- new_chunk = None
- while not new_chunk:
- new_slug = self.book.make_chunk_slug(slug)
- try:
- new_chunk = self.book.chunk_set.create(number=self.number+1,
- creator=creator, slug=new_slug, title=title)
- except IntegrityError:
- pass
- 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)
-
-
-class BookPublishRecord(models.Model):
- """
- A record left after publishing a Book.
- """
-
- book = models.ForeignKey(Book)
- timestamp = models.DateTimeField(auto_now_add=True)
- user = models.ForeignKey(User)
-
- class Meta:
- ordering = ['-timestamp']
-
-
-class ChunkPublishRecord(models.Model):
- """
- BookPublishRecord details for each Chunk.
- """
-
- book_record = models.ForeignKey(BookPublishRecord)
- change = models.ForeignKey(Chunk.change_model)
--- /dev/null
+# -*- 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 catalogue.models.chunk import Chunk
+from catalogue.models.publish_log import BookPublishRecord, ChunkPublishRecord
+from catalogue.models.book import Book
+from catalogue.models.listeners import *
--- /dev/null
+# -*- 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.db import models
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext_lazy as _
+from slughifi import slughifi
+from catalogue.helpers import cached_in_field
+from catalogue.models import BookPublishRecord, ChunkPublishRecord
+from catalogue.signals import post_publish
+from catalogue.tasks import refresh_instance
+from catalogue.xml_tools import compile_text, split_xml
+
+
+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)
+
+ #wl_slug = models.CharField(_('title'), max_length=255, null=True, db_index=True, editable=False)
+ 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)
+
+ # Cache
+ _short_html = models.TextField(null=True, blank=True, editable=False)
+ _single = models.NullBooleanField(editable=False, db_index=True)
+ _new_publishable = models.NullBooleanField(editable=False)
+ _published = models.NullBooleanField(editable=False)
+
+ # Managers
+ objects = models.Manager()
+
+ class NoTextError(BaseException):
+ pass
+
+ class Meta:
+ app_label = 'catalogue'
+ ordering = ['parent_number', 'title']
+ verbose_name = _('book')
+ verbose_name_plural = _('books')
+ permissions = [('can_pubmark', 'Can mark for publishing')]
+
+
+ # Representing
+ # ============
+
+ def __iter__(self):
+ return iter(self.chunk_set.all())
+
+ def __getitem__(self, chunk):
+ return self.chunk_set.all()[chunk]
+
+ def __len__(self):
+ return self.chunk_set.count()
+
+ def __nonzero__(self):
+ """
+ Necessary so that __len__ isn't used for bool evaluation.
+ """
+ return True
+
+ def __unicode__(self):
+ return self.title
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ("catalogue_book", [self.slug])
+
+
+ # Creating & manipulating
+ # =======================
+
+ @classmethod
+ def create(cls, creator, text, *args, **kwargs):
+ b = cls.objects.create(*args, **kwargs)
+ b.chunk_set.all().update(creator=creator)
+ b[0].commit(text, author=creator)
+ return b
+
+ def add(self, *args, **kwargs):
+ """Add a new chunk at the end."""
+ return self.chunk_set.reverse()[0].split(*args, **kwargs)
+
+ @classmethod
+ def import_xml_text(cls, text=u'', previous_book=None,
+ commit_args=None, **kwargs):
+ """Imports a book from XML, splitting it into chunks as necessary."""
+ texts = split_xml(text)
+ if previous_book:
+ instance = previous_book
+ else:
+ instance = cls(**kwargs)
+ instance.save()
+
+ # if there are more parts, set the rest to empty strings
+ book_len = len(instance)
+ for i in range(book_len - len(texts)):
+ texts.append(u'pusta część %d' % (i + 1), u'')
+
+ i = 0
+ for i, (title, text) in enumerate(texts):
+ if not title:
+ title = u'część %d' % (i + 1)
+
+ slug = slughifi(title)
+
+ if i < book_len:
+ chunk = instance[i]
+ chunk.slug = slug
+ chunk.title = title
+ chunk.save()
+ else:
+ chunk = instance.add(slug, title, adjust_slug=True)
+
+ chunk.commit(text, **commit_args)
+
+ return instance
+
+ 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, slugs=None, titles=None):
+ """Add all chunks of another book to self."""
+ number = self[len(self) - 1].number + 1
+ len_other = len(other)
+ single = len_other == 1
+
+ if slugs is not None:
+ assert len(slugs) == len_other
+ if titles is not None:
+ assert len(titles) == len_other
+ if slugs is None:
+ slugs = [slughifi(t) for t in titles]
+
+ for i, chunk in enumerate(other):
+ # move chunk to new book
+ chunk.book = self
+ chunk.number = number
+
+ if titles is None:
+ # 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.title = 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.title = "%s, %s" % (other_title_part, chunk.title)
+ else:
+ chunk.slug = slugs[i]
+ chunk.title = titles[i]
+
+ chunk.slug = self.make_chunk_slug(chunk.slug)
+ chunk.save()
+ number += 1
+ other.delete()
+
+
+ # State & cache
+ # =============
+
+ def last_published(self):
+ try:
+ return self.publish_log.all()[0].timestamp
+ except IndexError:
+ return None
+
+ def publishable(self):
+ if not self.chunk_set.exists():
+ return False
+ for chunk in self:
+ if not chunk.publishable():
+ return False
+ return True
+
+ def hidden(self):
+ return self.slug.startswith('.')
+
+ def is_new_publishable(self):
+ """Checks if book is ready for publishing.
+
+ Returns True if there is a publishable version newer than the one
+ already published.
+
+ """
+ new_publishable = False
+ if not self.chunk_set.exists():
+ return False
+ for chunk in self:
+ change = chunk.publishable()
+ if not change:
+ return False
+ if not new_publishable and not change.publish_log.exists():
+ new_publishable = True
+ return new_publishable
+ new_publishable = cached_in_field('_new_publishable')(is_new_publishable)
+
+ def is_published(self):
+ return self.publish_log.exists()
+ published = cached_in_field('_published')(is_published)
+
+ def is_single(self):
+ return len(self) == 1
+ single = cached_in_field('_single')(is_single)
+
+ @cached_in_field('_short_html')
+ def short_html(self):
+ return render_to_string('catalogue/book_list/book.html', {'book': self})
+
+ def touch(self):
+ update = {
+ "_new_publishable": self.is_new_publishable(),
+ "_published": self.is_published(),
+ "_single": self.is_single(),
+ "_short_html": None,
+ }
+ Book.objects.filter(pk=self.pk).update(**update)
+ refresh_instance(self)
+
+ def refresh(self):
+ """This should be done offline."""
+ self.short_html
+ self.single
+ self.new_publishable
+ self.published
+
+ # Materializing & publishing
+ # ==========================
+
+ def get_current_changes(self, publishable=True):
+ """
+ Returns a list containing one Change for every Chunk in the Book.
+ Takes the most recent revision (publishable, if set).
+ Throws an error, if a proper revision is unavailable for a Chunk.
+ """
+ if publishable:
+ changes = [chunk.publishable() for chunk in self]
+ else:
+ changes = [chunk.head for chunk in self if chunk.head is not None]
+ if None in changes:
+ raise self.NoTextError('Some chunks have no available text.')
+ return changes
+
+ def materialize(self, publishable=False, changes=None):
+ """
+ Get full text of the document compiled from chunks.
+ Takes the current versions of all texts
+ or versions most recently tagged for publishing,
+ or a specified iterable changes.
+ """
+ if changes is None:
+ changes = self.get_current_changes(publishable)
+ return compile_text(change.materialize() for change in changes)
+
+ def publish(self, user):
+ """
+ Publishes a book on behalf of a (local) user.
+ """
+ from apiclient import api_call
+
+ changes = self.get_current_changes(publishable=True)
+ book_xml = self.materialize(changes=changes)
+ #api_call(user, "books", {"book_xml": book_xml})
+ # record the publish
+ br = BookPublishRecord.objects.create(book=self, user=user)
+ for c in changes:
+ ChunkPublishRecord.objects.create(book_record=br, change=c)
+ post_publish.send(sender=br)
--- /dev/null
+# -*- 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.conf import settings
+from django.db import models
+from django.db.utils import IntegrityError
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext_lazy as _
+from catalogue.helpers import cached_in_field
+from catalogue.managers import VisibleManager
+from catalogue.tasks import refresh_instance
+from dvcs import models as dvcs_models
+
+
+class Chunk(dvcs_models.Document):
+ """ An editable chunk of text. Every Book text is divided into chunks. """
+ REPO_PATH = settings.CATALOGUE_REPO_PATH
+
+ book = models.ForeignKey('Book', editable=False, verbose_name=_('book'))
+ number = models.IntegerField(_('number'))
+ slug = models.SlugField(_('slug'))
+ title = models.CharField(_('title'), max_length=255, blank=True)
+
+ # cache
+ _short_html = models.TextField(null=True, blank=True, editable=False)
+ _hidden = models.NullBooleanField(editable=False)
+ _changed = models.NullBooleanField(editable=False)
+
+ # managers
+ objects = models.Manager()
+ visible_objects = VisibleManager()
+
+ class Meta:
+ app_label = 'catalogue'
+ unique_together = [['book', 'number'], ['book', 'slug']]
+ ordering = ['number']
+ verbose_name = _('chunk')
+ verbose_name_plural = _('chunks')
+
+ # Representing
+ # ============
+
+ def __unicode__(self):
+ return "%d:%d: %s" % (self.book_id, self.number, self.title)
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ("wiki_editor", [self.book.slug, self.slug])
+
+ def pretty_name(self, book_length=None):
+ title = self.book.title
+ if self.title:
+ title += ", %s" % self.title
+ if book_length > 1:
+ title += " (%d/%d)" % (self.number, book_length)
+ return title
+
+
+ # Creating and manipulation
+ # =========================
+
+ def split(self, slug, title='', adjust_slug=False, **kwargs):
+ """ Create an empty chunk after this one """
+ self.book.chunk_set.filter(number__gt=self.number).update(
+ number=models.F('number')+1)
+ new_chunk = None
+ while not new_chunk:
+ new_slug = self.book.make_chunk_slug(slug)
+ try:
+ new_chunk = self.book.chunk_set.create(number=self.number+1,
+ slug=new_slug, title=title, **kwargs)
+ except IntegrityError:
+ pass
+ return new_chunk
+
+ @classmethod
+ def get(cls, book_slug, chunk_slug=None):
+ if chunk_slug is None:
+ return cls.objects.get(book__slug=book_slug, number=1)
+ else:
+ return cls.objects.get(book__slug=book_slug, slug=chunk_slug)
+
+
+ # State & cache
+ # =============
+
+ def new_publishable(self):
+ change = self.publishable()
+ if not change:
+ return False
+ return change.publish_log.exists()
+
+ def is_changed(self):
+ if self.head is None:
+ return False
+ return not self.head.publishable
+ changed = cached_in_field('_changed')(is_changed)
+
+ def is_hidden(self):
+ return self.book.hidden()
+ hidden = cached_in_field('_hidden')(is_hidden)
+
+ @cached_in_field('_short_html')
+ def short_html(self):
+ return render_to_string(
+ 'catalogue/book_list/chunk.html', {'chunk': self})
+
+ def touch(self):
+ update = {
+ "_changed": self.is_changed(),
+ "_hidden": self.is_hidden(),
+ "_short_html": None,
+ }
+ Chunk.objects.filter(pk=self.pk).update(**update)
+ refresh_instance(self)
+
+ def refresh(self):
+ """This should be done offline."""
+ self.changed
+ self.hidden
+ self.short_html
--- /dev/null
+# -*- 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 import models
+from catalogue.models import Book, Chunk
+from catalogue.signals import post_publish
+from dvcs.signals import post_publishable
+
+
+def book_changed(sender, instance, created, **kwargs):
+ instance.touch()
+ for c in instance:
+ c.touch()
+models.signals.post_save.connect(book_changed, sender=Book)
+
+
+def chunk_changed(sender, instance, created, **kwargs):
+ instance.book.touch()
+ instance.touch()
+models.signals.post_save.connect(chunk_changed, sender=Chunk)
+
+
+def user_changed(sender, instance, *args, **kwargs):
+ books = set()
+ for c in instance.chunk_set.all():
+ books.add(c.book)
+ c.touch()
+ for b in books:
+ b.touch()
+models.signals.post_save.connect(user_changed, sender=User)
+
+
+def publish_listener(sender, *args, **kwargs):
+ sender.book.touch()
+ for c in sender.book:
+ c.touch()
+post_publish.connect(publish_listener)
+
+
+def listener_create(sender, instance, created, **kwargs):
+ if created:
+ instance.chunk_set.create(number=1, slug='1')
+models.signals.post_save.connect(listener_create, sender=Book)
+
+
--- /dev/null
+# -*- 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 import models
+from django.utils.translation import ugettext_lazy as _
+from catalogue.models import Chunk
+
+
+class BookPublishRecord(models.Model):
+ """
+ A record left after publishing a Book.
+ """
+
+ book = models.ForeignKey('Book', verbose_name=_('book'), related_name='publish_log')
+ timestamp = models.DateTimeField(_('time'), auto_now_add=True)
+ user = models.ForeignKey(User, verbose_name=_('user'))
+
+ class Meta:
+ app_label = 'catalogue'
+ ordering = ['-timestamp']
+ verbose_name = _('book publish record')
+ verbose_name = _('book publish records')
+
+
+class ChunkPublishRecord(models.Model):
+ """
+ BookPublishRecord details for each Chunk.
+ """
+
+ book_record = models.ForeignKey(BookPublishRecord, verbose_name=_('book publish record'))
+ change = models.ForeignKey(Chunk.change_model, related_name='publish_log', verbose_name=_('change'))
+
+ class Meta:
+ app_label = 'catalogue'
+ verbose_name = _('chunk publish record')
+ verbose_name = _('chunk publish records')
--- /dev/null
+from django.dispatch import Signal
+
+post_publish = Signal()
--- /dev/null
+from celery.task import task
+
+
+@task
+def refresh_by_pk(cls, pk):
+ cls._default_manager.get(pk=pk).refresh()
+
+
+def refresh_instance(instance):
+ refresh_by_pk.delay(type(instance), instance.pk)
+
--- /dev/null
+{% extends "catalogue/base.html" %}
+
+{% load wall %}
+
+{% block leftcolumn %}
+ {% wall %}
+{% endblock leftcolumn %}
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
- {% compressed_css 'listing' %}
+ {% compressed_css 'catalogue' %}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}style.css" />
<title>{% block title %}{% trans "Platforma Redakcyjna" %}{% endblock title %}</title>
</head>
</div>
-{% compressed_js 'listing' %}
+
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
+{% compressed_js 'catalogue' %}
{% block extrabody %}
{% endblock %}
</body>
--- /dev/null
+{% load i18n %}
+
+{% if book.single %}
+ {% with book.0 as chunk %}
+ <tr>
+ <td><a target="_blank" href="{% url catalogue_book book.slug %}" title='{% trans "Book settings" %}'>[B]</a></td>
+ <td><a href="{% url catalogue_chunk_edit book.slug chunk.slug %}" title='{% trans "Chunk settings" %}'>[c]</a></td>
+ <td><a target="_blank"
+ href="{% url wiki_editor book.slug %}">
+ {{ book.title }}</a></td>
+ <td>{% if chunk.stage %}
+ {{ chunk.stage }}
+ {% else %}–
+ {% endif %}</td>
+ <td class='user-column'>{% if chunk.user %}<a href="{% url catalogue_user chunk.user.username %}">{{ chunk.user.first_name }} {{ chunk.user.last_name }}</a>{% endif %}</td>
+ <td>
+ {% if chunk.published %}P{% endif %}
+ {% if book.new_publishable %}p{% endif %}
+ {% if chunk.changed %}+{% endif %}
+ </td>
+ </tr>
+ {% endwith %}
+{% else %}
+ <tr>
+ <td><a target="_blank" href="{% url catalogue_book book.slug %}" title='{% trans "Book settings" %}'>[B]</a></td>
+ <td></td>
+ <td>{{ book.title }}</td>
+ <td></td><td></td>
+ <td>
+ {% if book.published %}P{% endif %}
+ {% if book.new_publishable %}p{% endif %}
+ </td>
+ </tr>
+{% endif %}
--- /dev/null
+{% load i18n %}
+{% load pagination_tags %}
+
+
+<form name='filter' action=''>
+<input type='hidden' name="title" value="{{ request.GET.title }}" />
+<input type='hidden' name="stage" value="{{ request.GET.stage }}" />
+{% if not viewed_user %}
+ <input type='hidden' name="user" value="{{ request.GET.user }}" />
+{% endif %}
+<input type='hidden' name="all" value="{{ request.GET.all }}" />
+<input type='hidden' name="status" value="{{ request.GET.status }}" />
+</form>
+
+<table id="file-list"{% if viewed_user %} class="book-list-user"{% endif %}>
+ <thead><tr>
+ <th></th>
+ <th>
+ <input class='check-filter' type='checkbox' name='all' title='{% trans "Show hidden books" %}'
+ {% if request.GET.all %}checked='checked'{% endif %} />
+ </th>
+ <th class='book-search-column'>
+ <form>
+ <input title='{% trans "Search in book titles" %}' name="title"
+ class='text-filter' value="{{ request.GET.title }}" />
+ </form>
+ </th>
+ <th><select name="stage" class="filter">
+ <option value=''>- {% trans "stage" %} -</option>
+ <option {% if request.GET.stage == '-' %}selected="selected"
+ {% endif %}value="-">- {% trans "none" %} -</option>
+ {% for stage in stages %}
+ <option {% if request.GET.stage == stage.slug %}selected="selected"
+ {% endif %}value="{{ stage.slug }}">{{ stage.name }}</option>
+ {% endfor %}
+ </select></th>
+
+ {% if not viewed_user %}
+ <th><select name="user" class="filter">
+ <option value=''>- {% trans "editor" %} -</option>
+ <option {% if request.GET.user == '-' %}selected="selected"
+ {% endif %}value="-">- {% trans "none" %} -</option>
+ {% for user in users %}
+ <option {% if request.GET.user == user.username %}selected="selected"
+ {% endif %}value="{{ user.username }}">{{ user.first_name }} {{ user.last_name }} ({{ user.count }})</option>
+ {% endfor %}
+ </select></th>
+ {% endif %}
+
+ <th><select name="status" class="filter">
+ <option value=''>- {% trans "status" %} -</option>
+ {% for state, label in states %}
+ <option {% if request.GET.status == state %}selected="selected"
+ {% endif %}value='{{ state }}'>{{ label }}</option>
+ {% endfor %}
+ </select></th>
+
+ </tr></thead>
+
+ {% with cnt=books|length %}
+ {% autopaginate books 100 %}
+ <tbody>
+ {% for item in books %}
+ {% with item.book as book %}
+ {{ book.short_html|safe }}
+ {% if not book.single %}
+ {% for chunk in item.chunks %}
+ {{ chunk.short_html|safe }}
+ {% endfor %}
+ {% endif %}
+ {% endwith %}
+ {% endfor %}
+ <tr><th class='paginator' colspan="5">
+ {% paginate %}
+ {% blocktrans count c=cnt %}{{c}} book{% plural %}{{c}} books{% endblocktrans %}</th></tr>
+ </tbody>
+ {% endwith %}
+</table>
+{% if not books %}
+ <p>{% trans "No books found." %}</p>
+{% endif %}
--- /dev/null
+{% load i18n %}
+
+<tr>
+ <td></td>
+ <td><a href="{% url catalogue_chunk_edit chunk.book.slug chunk.slug %}" title='{% trans "Chunk settings" %}'>[c]</a></td>
+ <td><a target="_blank" href="{{ chunk.get_absolute_url }}">
+ <span class='chunkno'>{{ chunk.number }}.</span>
+ {{ chunk.title }}</a></td>
+ <td>{% if chunk.stage %}
+ {{ chunk.stage }}
+ {% else %}
+ –
+ {% endif %}</td>
+ <td class='user-column'>{% if chunk.user %}
+ <a href="{% url catalogue_user chunk.user.username %}">
+ {{ chunk.user.first_name }} {{ chunk.user.last_name }}
+ </a>{% else %}
+
+ {% endif %}</td>
+</td>
+<td>
+ {% if chunk.new_publishable %}p{% endif %}
+ {% if chunk.changed %}+{% endif %}
+</td>
+</tr>
{% extends "catalogue/base.html" %}
{% load i18n %}
-{% load pagination_tags %}
-{% load catalogue %}
+{% load catalogue book_list %}
-{% block extrabody %}
-{{ block.super }}
-<script type="text/javascript" charset="utf-8">
-$(function() {
- $("select.filter").change(function() {
- document.filter[this.name].value = this.value;
- document.filter.submit();
- });
-
- $('#book-search').keypress(function(e)
- {
- if (e.which == 13)
- {
- document.filter[this.name] = this.value;
- document.filter.submit();
- }
- });
-
-});
-</script>
-{% endblock %}
{% block leftcolumn %}
-
- <form name='filter' action=''>
- <input type='hidden' name="title" value="{{ request.GET.title }}" />
- <input type='hidden' name="stage" value="{{ request.GET.stage }}" />
- <input type='hidden' name="user" value="{{ request.GET.user }}" />
- </form>
-
- <table id="file-list">
- <thead><tr>
- <th></th>
- <th></th>
- <th id='th-book-search' style='width:300px;'>
- <form action='#'>
- <input name="title" class='filter' style='width:300px;' value="{{ request.GET.title }}" />
- </form>
- </th>
- <th><select name="stage" class="filter">
- <option value=''>- {% trans "filter by stage" %} -</option>
- <option {% if request.GET.stage == '-' %}selected="selected"
- {% endif %}value="-">- {% trans "none" %} -</option>
- {% for stage in stages %}
- <option {% if request.GET.stage == stage.slug %}selected="selected"
- {% endif %}value="{{ stage.slug }}">{{ stage.name }}</option>
- {% endfor %}
- </select></th>
-
- {% if not viewed_user %}
- <th><select name="user" class="filter">
- <option value=''>- {% trans "filter by user" %} -</option>
- <option {% if request.GET.user == '-' %}selected="selected"
- {% endif %}value="-">- {% trans "none" %} -</option>
- {% for user in users %}
- <option {% if request.GET.user == user.username %}selected="selected"
- {% endif %}value="{{ user.username }}">{{ user.first_name }} {{ user.last_name }} ({{ user.count }})</option>
- {% endfor %}
- </select></th>
- {% endif %}
-
- </tr></thead>
-
- <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>{% if chunk.stage %}
- ({{ chunk.stage }})
- {% else %}–
- {% endif %}</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.title }}</a></td>
- <td>{% if chunk.stage %}
- {{ chunk.stage }}
- {% else %}
- –
- {% endif %}</td>
- {% if not viewed_user %}
- <td>{% if chunk.user %}
- <a href="{% url catalogue_user chunk.user.username %}">
- {{ chunk.user.first_name }} {{ chunk.user.last_name }}
- </a>{% else %}
-
- {% endif %}</td>
- {% endif %}
- </td></tr>
- {% endfor %}
- {% endifequal %}
- {% endwith %}
- {% endfor %}
- <tr><td colspan="3">{% paginate %}</td></tr>
- </tbody>
- </table>
+ {% book_list %}
{% 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>
-
- {% if viewed_user %}
- <h2>{% trans "Recent activity for" %} {{ viewed_user }}</h2>
- {% wall viewed_user %}
- {% else %}
- <h2>{% trans "Recent activity" %}</h2>
- {% wall %}
- {% endif %}
-{% endblock rightcolumn %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+
+{% load i18n %}
+{% load catalogue book_list wall %}
+
+
+{% block leftcolumn %}
+ {% book_list request.user %}
+{% 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 for" %} {{ request.user|nice_name }}</h2>
+ {% wall request.user 10 %}
+{% endblock rightcolumn %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+
+{% load i18n %}
+{% load catalogue book_list wall %}
+
+
+{% block leftcolumn %}
+ <h1>{{ viewed_user|nice_name }}</h1>
+ {% book_list viewed_user %}
+{% endblock leftcolumn %}
+
+{% block rightcolumn %}
+ <h2>{% trans "Recent activity for" %} {{ viewed_user|nice_name }}</h2>
+ {% wall viewed_user 10 %}
+{% endblock rightcolumn %}
<!--img src='{{ STATIC_URL }}img/wall/{{ item.tag}}.png' alt='{% trans item.tag %}' /-->
</div>
- {{ item.timestamp }}
- <br/>
+ <span style='float:right'>{{ item.timestamp }}</span>
+ <h3>{{ item.header }}</h3>
{% if item.user %}
<a href="{% url catalogue_user item.user.username %}">
{{ item.user.first_name }} {{ item.user.last_name }}</a>
--- /dev/null
+from __future__ import absolute_import
+
+from re import split
+from django.db.models import Q, Count
+from django import template
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.auth.models import User
+from catalogue.models import Chunk
+
+register = template.Library()
+
+
+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.chunk_qs = chunk_qs.select_related('book__hidden')
+
+ 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]))
+ 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):
+ self.book = book
+ self.chunks = chunks
+
+
+def foreign_filter(qs, value, filter_field, model, model_field='slug', unset='-'):
+ if value == unset:
+ return qs.filter(**{filter_field: None})
+ if not value:
+ return qs
+ try:
+ obj = model._default_manager.get(**{model_field: value})
+ except model.DoesNotExist:
+ return qs.none()
+ else:
+ return qs.filter(**{filter_field: obj})
+
+
+def search_filter(qs, value, filter_field):
+ if not value:
+ return qs
+ return qs.filter(**{"%s__icontains" % filter_field: value})
+
+
+_states = [
+ ('publishable', _('publishable'), Q(book___new_publishable=True)),
+ ('changed', _('changed'), Q(_changed=True)),
+ ('published', _('published'), Q(book___published=True)),
+ ('unpublished', _('unpublished'), Q(book___published=False)),
+ ('empty', _('empty'), Q(head=None)),
+ ]
+_states_options = [s[:2] for s in _states]
+_states_dict = dict([(s[0], s[2]) for s in _states])
+
+
+def document_list_filter(request, **kwargs):
+
+ def arg_or_GET(field):
+ return kwargs.get(field, request.GET.get(field))
+
+ if arg_or_GET('all'):
+ chunks = Chunk.objects.all()
+ else:
+ chunks = Chunk.visible_objects.all()
+
+ chunks = chunks.order_by('book__title', 'book', 'number')
+
+ state = arg_or_GET('status')
+ if state in _states_dict:
+ chunks = chunks.filter(_states_dict[state])
+
+ chunks = foreign_filter(chunks, arg_or_GET('user'), 'user', User, 'username')
+ chunks = foreign_filter(chunks, arg_or_GET('stage'), 'stage', Chunk.tag_model, 'slug')
+ chunks = search_filter(chunks, arg_or_GET('title'), 'book__title')
+ return chunks
+
+
+@register.inclusion_tag('catalogue/book_list/book_list.html', takes_context=True)
+def book_list(context, user=None):
+ request = context['request']
+
+ if user:
+ filters = {"user": user}
+ new_context = {"viewed_user": user}
+ else:
+ filters = {}
+ new_context = {"users": User.objects.annotate(
+ count=Count('chunk')).filter(count__gt=0).order_by(
+ '-count', 'last_name', 'first_name')}
+
+ new_context.update({
+ "request": request,
+ "books": ChunksList(document_list_filter(request, **filters)),
+ "stages": Chunk.tag_model.objects.all(),
+ "states": _states_options,
+ })
+
+ return new_context
+
from __future__ import absolute_import
-from django.db.models import Count, Q
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, BookPublishRecord
-
register = template.Library()
if user.is_authenticated():
tabs.append(Tab('my', _('My page'), reverse("catalogue_user")))
+ tabs.append(Tab('activity', _('Activity'), reverse("catalogue_activity")))
tabs.append(Tab('all', _('All'), reverse("catalogue_document_list")))
tabs.append(Tab('users', _('Users'), reverse("catalogue_users")))
tabs.append(Tab('create', _('Add'), reverse("catalogue_create_missing")))
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(user, max_len):
- qs = Chunk.change_model.objects.filter(revision__gt=-1).order_by('-created_at')
- qs = qs.select_related('author', 'tree', 'tree__book__title')
- if user:
- qs = qs.filter(Q(author=user) | Q(tree__user=user))
- 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
-
-
-# TODO: marked for publishing
-
-
-def published_wall(user, max_len):
- qs = BookPublishRecord.objects.select_related('book__title')
- if user:
- # TODO: published my book
- qs = qs.filter(Q(user=user))
- qs = qs[:max_len]
- for item in qs:
- w = WallItem('publish')
- w.title = item.book.title
- #w.summary =
- w.url = chunk.book.get_absolute_url()
- yield w
-
-
-def comments_wall(user, max_len):
- qs = Comment.objects.filter(is_public=True).select_related().order_by('-submit_date')
- if user:
- # TODO: comments concerning my books
- qs = qs.filter(Q(user=user))
- 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.filter
+def nice_name(user):
+ return user.get_full_name() or user.username
-@register.inclusion_tag("catalogue/wall.html", takes_context=True)
-def wall(context, user=None, max_len=10):
- print user
- return {
- "request": context['request'],
- "STATIC_URL": context['STATIC_URL'],
- "wall": big_wall(max_len,
- changes_wall(user, max_len),
- published_wall(user, max_len),
- comments_wall(user, max_len),
- )}
--- /dev/null
+from __future__ import absolute_import
+
+from django.db.models import Q
+from django.core.urlresolvers import reverse
+from django.contrib.comments.models import Comment
+from django import template
+from django.utils.translation import ugettext as _
+
+from catalogue.models import Chunk, BookPublishRecord
+
+register = template.Library()
+
+
+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(user, max_len):
+ qs = Chunk.change_model.objects.filter(revision__gt=-1).order_by('-created_at')
+ qs = qs.select_related('author', 'tree', 'tree__book__title')
+ if user:
+ qs = qs.filter(Q(author=user) | Q(tree__user=user))
+ qs = qs[:max_len]
+ for item in qs:
+ tag = 'stage' if item.tags.count() else 'change'
+ chunk = item.tree
+ w = WallItem(tag)
+ if user and item.author != user:
+ w.header = _('Related edit')
+ else:
+ w.header = _('Edit')
+ 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
+
+
+# TODO: marked for publishing
+
+
+def published_wall(user, max_len):
+ qs = BookPublishRecord.objects.select_related('book__title')
+ if user:
+ # TODO: published my book
+ qs = qs.filter(Q(user=user))
+ qs = qs[:max_len]
+ for item in qs:
+ w = WallItem('publish')
+ w.header = _('Publication')
+ w.title = item.book.title
+ w.timestamp = item.timestamp
+ w.url = item.book.get_absolute_url()
+ w.user = item.user
+ w.email = item.user.email
+ yield w
+
+
+def comments_wall(user, max_len):
+ qs = Comment.objects.filter(is_public=True).select_related().order_by('-submit_date')
+ if user:
+ # TODO: comments concerning my books
+ qs = qs.filter(Q(user=user))
+ qs = qs[:max_len]
+ for item in qs:
+ w = WallItem('comment')
+ w.header = _('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, user=None, max_len=100):
+ return {
+ "request": context['request'],
+ "STATIC_URL": context['STATIC_URL'],
+ "wall": big_wall(max_len,
+ changes_wall(user, max_len),
+ published_wall(user, max_len),
+ comments_wall(user, max_len),
+ )}
+++ /dev/null
-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(), [])
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'^activity/$', 'activity', name='catalogue_activity'),
url(r'^upload/$',
'upload', name='catalogue_upload'),
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.db.models import Count, Q
from django import http
from django.http import Http404
-from django.shortcuts import get_object_or_404
+from django.shortcuts import get_object_or_404, render
from django.utils.http import urlquote_plus
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.http import require_POST
logger = logging.getLogger("fnp.catalogue")
-def foreign_filter(qs, value, filter_field, model, model_field='slug', unset='-'):
- if value == unset:
- return qs.filter(**{filter_field: None})
- if not value:
- return qs
- try:
- obj = model._default_manager.get(**{model_field: value})
- except model.DoesNotExist:
- return qs.none()
- else:
- return qs.filter(**{filter_field: obj})
-
-
-def search_filter(qs, value, filter_field):
- if not value:
- return qs
- return qs.filter(**{"%s__icontains" % filter_field: value})
-
-
@active_tab('all')
@never_cache
-def document_list(request, filters=None):
- chunks = Chunk.objects.order_by('book__title', 'book', 'number')
-
- chunks = foreign_filter(chunks, request.GET.get('user', None), 'user', User, 'username')
- chunks = foreign_filter(chunks, request.GET.get('stage', None), 'stage', Chunk.tag_model, 'slug')
- chunks = search_filter(chunks, request.GET.get('title', None), 'book__title')
-
- chunks_list = helpers.ChunksList(chunks)
-
- users = User.objects.annotate(count=Count('chunk')).filter(count__gt=0).order_by('-count', 'last_name', 'first_name')
- #users = User.objects.annotate(count=Count('chunk')).order_by('-count', 'last_name', 'first_name')
-
-
- 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),
- 'stages': Chunk.tag_model.objects.all(),
- 'users': users,
- })
+def document_list(request):
+ return render(request, 'catalogue/document_list.html')
@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)
+def user(request, username):
+ user = get_object_or_404(User, username=username)
+ return render(request, 'catalogue/user_page.html', {"viewed_user": user})
- 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,
+@login_required
+@active_tab('my')
+@never_cache
+def my(request):
+ return render(request, 'catalogue/my_page.html', {
'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
key=lambda x: x[1]['time'], reverse=True),
- 'viewed_user': user,
- 'stages': Chunk.tag_model.objects.all(),
- })
-my = login_required(active_tab('my')(user))
+ })
@active_tab('users')
})
+@active_tab('activity')
+def activity(request):
+ return render(request, 'catalogue/activity.html')
+
+
@never_cache
def logout_then_redirect(request):
auth.logout(request)
creator = request.user
else:
creator = None
- book = Book.create(creator=creator,
+ book = Book.objects.create(
slug=form.cleaned_data['slug'],
title=form.cleaned_data['title'],
- text=form.cleaned_data['text'],
)
+ book.chunk_set.all().update(creator=creator)
+ book[0].commit(text=form.cleaned_data['text'], author=creator)
return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
else:
for a in name_elem.findall('.//' + tag):
a.text=''
del a[:]
- name = etree.tostring(name_elem, method='text', encoding='utf-8')
+ name = etree.tostring(name_elem, method='text', encoding='utf-8').strip()
# in the original, remove everything from the start of the last chapter
parent = element.getparent()
--- /dev/null
+# 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: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-10-03 15:35+0200\n"
+"PO-Revision-Date: 2011-10-03 15:35+0100\n"
+"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2)\n"
+
+#: models.py:19
+msgid "name"
+msgstr "nazwa"
+
+#: models.py:20
+msgid "slug"
+msgstr "slug"
+
+#: models.py:22
+msgid "ordering"
+msgstr "kolejność"
+
+#: models.py:29
+msgid "tag"
+msgstr "tag"
+
+#: models.py:30 models.py:196
+msgid "tags"
+msgstr "tagi"
+
+#: models.py:72
+msgid "author"
+msgstr "autor"
+
+#: models.py:73
+msgid "author name"
+msgstr "imię i nazwisko autora"
+
+#: models.py:75 models.py:79
+msgid "Used if author is not set."
+msgstr "Używane, gdy nie jest ustawiony autor."
+
+#: models.py:77
+msgid "author email"
+msgstr "e-mail autora"
+
+#: models.py:81
+msgid "revision"
+msgstr "rewizja"
+
+#: models.py:85
+msgid "parent"
+msgstr "rodzic"
+
+#: models.py:90
+msgid "merge parent"
+msgstr "drugi rodzic"
+
+#: models.py:93
+msgid "description"
+msgstr "opis"
+
+#: models.py:96
+msgid "publishable"
+msgstr "do publikacji"
+
+#: models.py:102
+msgid "change"
+msgstr "zmiana"
+
+#: models.py:103
+msgid "changes"
+msgstr "zmiany"
+
+#: models.py:195
+msgid "document"
+msgstr "dokument"
+
+#: models.py:197
+msgid "data"
+msgstr "dane"
+
+#: models.py:211
+msgid "stage"
+msgstr "etap"
+
+#: models.py:219
+msgid "head"
+msgstr "głowica"
+
+#: models.py:220
+msgid "This document's current head."
+msgstr "Aktualna wersja dokumentu."
+
+#: models.py:224
+msgid "creator"
+msgstr "utworzył"
+
+#: models.py:239
+msgid "user"
+msgstr "użytkownik"
+
+#: models.py:239
+msgid "Work assignment."
+msgstr "Przypisanie pracy użytkownikowi."
from datetime import datetime
+import os.path
+from django.contrib.auth.models import User
from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage
from django.db import models
from django.db.models.base import ModelBase
-from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from mercurial import mdiff, simplemerge
-from dvcs.fields import GzipFileSystemStorage
-from dvcs.settings import REPO_PATH
+from django.conf import settings
+from dvcs.signals import post_commit
+from dvcs.storage import GzipFileSystemStorage
class Tag(models.Model):
- """
- a tag (e.g. document stage) which can be applied to a change
- """
-
+ """A tag (e.g. document stage) which can be applied to a Change."""
name = models.CharField(_('name'), max_length=64)
slug = models.SlugField(_('slug'), unique=True, max_length=64,
null=True, blank=True)
class Meta:
abstract = True
ordering = ['ordering']
+ verbose_name = _("tag")
+ verbose_name_plural = _("tags")
def __unicode__(self):
return self.name
models.signals.pre_save.connect(Tag.listener_changed, sender=Tag)
-repo = GzipFileSystemStorage(location=REPO_PATH)
-
def data_upload_to(instance, filename):
return "%d/%d" % (instance.tree.pk, instance.pk)
Data file contains a gzipped text of the document.
"""
- author = models.ForeignKey(User, null=True, blank=True)
- author_name = models.CharField(max_length=128, null=True, blank=True)
- author_email = models.CharField(max_length=128, null=True, blank=True)
- data = models.FileField(upload_to=data_upload_to, storage=repo)
- revision = models.IntegerField(db_index=True)
+ author = models.ForeignKey(User, null=True, blank=True, verbose_name=_('author'))
+ author_name = models.CharField(_('author name'), max_length=128,
+ null=True, blank=True,
+ help_text=_("Used if author is not set.")
+ )
+ author_email = models.CharField(_('author email'), max_length=128,
+ null=True, blank=True,
+ help_text=_("Used if author is not set.")
+ )
+ revision = models.IntegerField(_('revision'), db_index=True)
parent = models.ForeignKey('self',
null=True, blank=True, default=None,
+ verbose_name=_('parent'),
related_name="children")
merge_parent = models.ForeignKey('self',
null=True, blank=True, default=None,
+ verbose_name=_('merge parent'),
related_name="merge_children")
- description = models.TextField(blank=True, default='')
+ description = models.TextField(_('description'), blank=True, default='')
created_at = models.DateTimeField(editable=False, db_index=True,
default=datetime.now)
- publishable = models.BooleanField(default=False)
+ publishable = models.BooleanField(_('publishable'), default=False)
class Meta:
abstract = True
ordering = ('created_at',)
unique_together = ['tree', 'revision']
+ verbose_name = _("change")
+ verbose_name_plural = _("changes")
def __unicode__(self):
return u"Id: %r, Tree %r, Parent %r, Data: %s" % (self.id, self.tree_id, self.parent_id, self.data)
""" commit this version of a doc as new head """
self.tree.commit(text=self.materialize(), **kwargs)
+ def set_publishable(self, publishable):
+ self.publishable = publishable
+ self.save()
+ post_publishable(sender=self, publishable=publishable).send()
+
def create_tag_model(model):
name = model.__name__ + 'Tag'
+
+ class Meta(Tag.Meta):
+ app_label = model._meta.app_label
+
attrs = {
'__module__': model.__module__,
+ 'Meta': Meta,
}
return type(name, (Tag,), attrs)
def create_change_model(model):
name = model.__name__ + 'Change'
+ repo = GzipFileSystemStorage(location=model.REPO_PATH)
+
+ class Meta(Change.Meta):
+ app_label = model._meta.app_label
attrs = {
'__module__': model.__module__,
- 'tree': models.ForeignKey(model, related_name='change_set'),
- 'tags': models.ManyToManyField(model.tag_model, related_name='change_set'),
+ 'tree': models.ForeignKey(model, related_name='change_set', verbose_name=_('document')),
+ 'tags': models.ManyToManyField(model.tag_model, verbose_name=_('tags'), related_name='change_set'),
+ 'data': models.FileField(_('data'), upload_to=data_upload_to, storage=repo),
+ 'Meta': Meta,
}
return type(name, (Change,), attrs)
-
class DocumentMeta(ModelBase):
"Metaclass for Document models."
def __new__(cls, name, bases, attrs):
+
model = super(DocumentMeta, cls).__new__(cls, name, bases, attrs)
if not model._meta.abstract:
# create a real Tag object and `stage' fk
model.tag_model = create_tag_model(model)
- models.ForeignKey(model.tag_model,
+ models.ForeignKey(model.tag_model, verbose_name=_('stage'),
null=True, blank=True).contribute_to_class(model, 'stage')
# create real Change model and `head' fk
model.change_model = create_change_model(model)
+
models.ForeignKey(model.change_model,
null=True, blank=True, default=None,
+ verbose_name=_('head'),
help_text=_("This document's current head."),
editable=False).contribute_to_class(model, 'head')
- return model
+ models.ForeignKey(User, null=True, blank=True, editable=False,
+ verbose_name=_('creator'), related_name="created_%s" % name.lower()
+ ).contribute_to_class(model, 'creator')
+ return model
class Document(models.Model):
- """
- File in repository.
- """
+ """File in repository. Subclass it to use version control in your app."""
+
__metaclass__ = DocumentMeta
- creator = models.ForeignKey(User, null=True, blank=True, editable=False,
- related_name="created_documents")
+ # default repository path
+ REPO_PATH = os.path.join(settings.MEDIA_ROOT, 'dvcs')
- user = models.ForeignKey(User, null=True, blank=True)
+ user = models.ForeignKey(User, null=True, blank=True,
+ verbose_name=_('user'), help_text=_('Work assignment.'))
class Meta:
abstract = True
def __unicode__(self):
return u"{0}, HEAD: {1}".format(self.id, self.head_id)
- @models.permalink
- def get_absolute_url(self):
- return ('dvcs.views.document_data', (), {
- 'document_id': self.id,
- 'version': self.head_id,
- })
-
def materialize(self, change=None):
if self.head is None:
return u''
change = self.change_set.get(pk=change)
return change.materialize()
- def commit(self, text, **kwargs):
+ def commit(self, text, author=None, author_name=None, author_email=None,
+ publishable=False, **kwargs):
+ """Commits a new revision.
+
+ This will automatically merge the commit into the main branch,
+ if parent is not document's head.
+
+ :param unicode text: new version of the document
+ :param parent: parent revision (head, if not specified)
+ :type parent: Change or None
+ :param User author: the commiter
+ :param unicode author_name: commiter name (if ``author`` not specified)
+ :param unicode author_email: commiter e-mail (if ``author`` not specified)
+ :param Tag[] tags: list of tags to apply to the new commit
+ :param bool publishable: set new commit as ready to publish
+ :returns: new head
+ """
if 'parent' not in kwargs:
parent = self.head
else:
if parent is not None and not isinstance(parent, Change):
parent = self.change_set.objects.get(pk=kwargs['parent'])
- author = kwargs.get('author', None)
- author_name = kwargs.get('author_name', None)
- author_email = kwargs.get('author_email', None)
tags = kwargs.get('tags', [])
if tags:
# set stage to next tag after the commited one
author_name=author_name,
author_email=author_email,
description=kwargs.get('description', ''),
+ publishable=publishable,
parent=parent)
change.tags = tags
else:
self.head = change
self.save()
+
+ post_commit.send(sender=self.head)
+
return self.head
def history(self):
return self.change_set.get(revision=rev)
def publishable(self):
- changes = self.change_set.filter(publishable=True).order_by('-created_at')[:1]
- if changes.count():
- return changes[0]
+ changes = self.change_set.filter(publishable=True)
+ if changes.exists():
+ return changes.order_by('-created_at')[0]
else:
return None
+++ /dev/null
-from django.conf import settings
-
-REPO_PATH = settings.DVCS_REPO_PATH
--- /dev/null
+from django.dispatch import Signal
+
+post_commit = Signal()
+post_publishable = Signal(providing_args=['publishable'])
+++ /dev/null
-from django.test import TestCase
-from dvcs.models import Change, Document
-from django.contrib.auth.models import User
-
-class DocumentModelTests(TestCase):
-
- def setUp(self):
- self.user = User.objects.create_user("tester", "tester@localhost.local")
-
- def assertTextEqual(self, given, expected):
- return self.assertEqual(given, expected,
- "Expected '''%s'''\n differs from text: '''%s'''" % (expected, given)
- )
-
- def test_empty_file(self):
- doc = Document.objects.create(name=u"Sample Document", creator=self.user)
- self.assert_(doc.head is not None)
- self.assertEqual(doc.materialize(), u"")
-
- def test_single_commit(self):
- doc = Document.objects.create(name=u"Sample Document", creator=self.user)
- doc.commit(text=u"Ala ma kota", description="Commit #1", author=self.user)
- self.assert_(doc.head is not None)
- self.assertEqual(doc.change_set.count(), 2)
- self.assertEqual(doc.materialize(), u"Ala ma kota")
-
- def test_chained_commits(self):
- doc = Document.objects.create(name=u"Sample Document", creator=self.user)
- c1 = doc.commit(description="Commit #1", text=u"""
- Line #1
- Line #2 is cool
- """, author=self.user)
- c2 = doc.commit(description="Commit #2", text=u"""
- Line #1
- Line #2 is hot
- """, author=self.user)
- c3 = doc.commit(description="Commit #3", text=u"""
- Line #1
- ... is hot
- Line #3 ate Line #2
- """, author=self.user)
- self.assert_(doc.head is not None)
- self.assertEqual(doc.change_set.count(), 4)
-
- self.assertEqual(doc.materialize(), u"""
- Line #1
- ... is hot
- Line #3 ate Line #2
- """)
- self.assertEqual(doc.materialize(version=c3), u"""
- Line #1
- ... is hot
- Line #3 ate Line #2
- """)
- self.assertEqual(doc.materialize(version=c2), u"""
- Line #1
- Line #2 is hot
- """)
- self.assertEqual(doc.materialize(version=c1), """
- Line #1
- Line #2 is cool
- """)
-
-
- def test_parallel_commit_noconflict(self):
- doc = Document.objects.create(name=u"Sample Document", creator=self.user)
- self.assert_(doc.head is not None)
- base = doc.head
- base = doc.commit(description="Commit #1", text=u"""
- Line #1
- Line #2
-""", author=self.user)
-
- c1 = doc.commit(description="Commit #2", text=u"""
- Line #1 is hot
- Line #2
-""", parent=base, author=self.user)
- self.assertTextEqual(c1.materialize(), u"""
- Line #1 is hot
- Line #2
-""")
- c2 = doc.commit(description="Commit #3", text=u"""
- Line #1
- Line #2
- Line #3
-""", parent=base, author=self.user)
- self.assertEqual(doc.change_set.count(), 5)
- self.assertTextEqual(doc.materialize(), u"""
- Line #1 is hot
- Line #2
- Line #3
-""")
-
- def test_parallel_commit_conflict(self):
- doc = Document.objects.create(name=u"Sample Document", creator=self.user)
- self.assert_(doc.head is not None)
- base = doc.head
- base = doc.commit(description="Commit #1", text=u"""
-Line #1
-Line #2
-Line #3
-""", author=self.user)
-
- c1 = doc.commit(description="Commit #2", text=u"""
-Line #1
-Line #2 is hot
-Line #3
-""", parent=base, author=self.user)
- c2 = doc.commit(description="Commit #3", text=u"""
-Line #1
-Line #2 is cool
-Line #3
-""", parent=base, author=self.user)
- self.assertEqual(doc.change_set.count(), 5)
- self.assertTextEqual(doc.materialize(), u"""
-Line #1
-<<<<<<<
-Line #2 is hot
-=======
-Line #2 is cool
->>>>>>>
-Line #3
-""")
-
- def test_multiply_parallel_commits(self):
- doc = Document.objects.create(name=u"Sample Document", creator=self.user)
- self.assert_(doc.head is not None)
- c1 = doc.commit(description="Commit A1", text=u"""
-Line #1
-
-Line #2
-
-Line #3
-""", author=self.user)
- c2 = doc.commit(description="Commit A2", text=u"""
-Line #1 *
-
-Line #2
-
-Line #3
-""", author=self.user)
- c3 = doc.commit(description="Commit B1", text=u"""
-Line #1
-
-Line #2 **
-
-Line #3
-""", parent=c1, author=self.user)
- c4 = doc.commit(description="Commit C1", text=u"""
-Line #1 *
-
-Line #2
-
-Line #3 ***
-""", parent=c2, author=self.user)
- self.assertEqual(doc.change_set.count(), 7)
- self.assertTextEqual(doc.materialize(), u"""
-Line #1 *
-
-Line #2 **
-
-Line #3 ***
-""")
-
--- /dev/null
+from nose.tools import *
+from django.test import TestCase
+from dvcs.models import Document
+
+
+class ADocument(Document):
+ pass
+
+
+class DocumentModelTests(TestCase):
+
+ def assertTextEqual(self, given, expected):
+ return self.assertEqual(given, expected,
+ "Expected '''%s'''\n differs from text: '''%s'''" % (expected, given)
+ )
+
+ def test_empty_file(self):
+ doc = ADocument.objects.create()
+ self.assertTextEqual(doc.materialize(), u"")
+
+ def test_single_commit(self):
+ doc = ADocument.objects.create()
+ doc.commit(text=u"Ala ma kota", description="Commit #1")
+ self.assertTextEqual(doc.materialize(), u"Ala ma kota")
+
+ def test_chained_commits(self):
+ doc = ADocument.objects.create()
+ text1 = u"""
+ Line #1
+ Line #2 is cool
+ """
+ text2 = u"""
+ Line #1
+ Line #2 is hot
+ """
+ text3 = u"""
+ Line #1
+ ... is hot
+ Line #3 ate Line #2
+ """
+
+ c1 = doc.commit(description="Commit #1", text=text1)
+ c2 = doc.commit(description="Commit #2", text=text2)
+ c3 = doc.commit(description="Commit #3", text=text3)
+
+ self.assertTextEqual(doc.materialize(), text3)
+ self.assertTextEqual(doc.materialize(change=c3), text3)
+ self.assertTextEqual(doc.materialize(change=c2), text2)
+ self.assertTextEqual(doc.materialize(change=c1), text1)
+
+ def test_parallel_commit_noconflict(self):
+ doc = ADocument.objects.create()
+ text1 = u"""
+ Line #1
+ Line #2
+ """
+ text2 = u"""
+ Line #1 is hot
+ Line #2
+ """
+ text3 = u"""
+ Line #1
+ Line #2
+ Line #3
+ """
+ text_merged = u"""
+ Line #1 is hot
+ Line #2
+ Line #3
+ """
+
+ base = doc.commit(description="Commit #1", text=text1)
+ c1 = doc.commit(description="Commit #2", text=text2)
+ commits = doc.change_set.count()
+ c2 = doc.commit(description="Commit #3", text=text3, parent=base)
+ self.assertEqual(doc.change_set.count(), commits + 2,
+ u"Parallel commits should create an additional merge commit")
+ self.assertTextEqual(doc.materialize(), text_merged)
+
+ def test_parallel_commit_conflict(self):
+ doc = ADocument.objects.create()
+ text1 = u"""
+ Line #1
+ Line #2
+ Line #3
+ """
+ text2 = u"""
+ Line #1
+ Line #2 is hot
+ Line #3
+ """
+ text3 = u"""
+ Line #1
+ Line #2 is cool
+ Line #3
+ """
+ text_merged = u"""
+ Line #1
+<<<<<<<
+ Line #2 is hot
+=======
+ Line #2 is cool
+>>>>>>>
+ Line #3
+ """
+ base = doc.commit(description="Commit #1", text=text1)
+ c1 = doc.commit(description="Commit #2", text=text2)
+ commits = doc.change_set.count()
+ c2 = doc.commit(description="Commit #3", text=text3, parent=base)
+ self.assertEqual(doc.change_set.count(), commits + 2,
+ u"Parallel commits should create an additional merge commit")
+ self.assertTextEqual(doc.materialize(), text_merged)
+
+
+ def test_multiple_parallel_commits(self):
+ text_a1 = u"""
+ Line #1
+
+ Line #2
+
+ Line #3
+ """
+ text_a2 = u"""
+ Line #1 *
+
+ Line #2
+
+ Line #3
+ """
+ text_b1 = u"""
+ Line #1
+
+ Line #2 **
+
+ Line #3
+ """
+ text_c1 = u"""
+ Line #1
+
+ Line #2
+
+ Line #3 ***
+ """
+ text_merged = u"""
+ Line #1 *
+
+ Line #2 **
+
+ Line #3 ***
+ """
+
+
+ doc = ADocument.objects.create()
+ c1 = doc.commit(description="Commit A1", text=text_a1)
+ c2 = doc.commit(description="Commit A2", text=text_a2, parent=c1)
+ c3 = doc.commit(description="Commit B1", text=text_b1, parent=c1)
+ c4 = doc.commit(description="Commit C1", text=text_c1, parent=c1)
+ self.assertTextEqual(doc.materialize(), text_merged)
+++ /dev/null
-# -*- coding: utf-8
-from django.conf.urls.defaults import *
-
-urlpatterns = patterns('dvcs.views',
- url(r'^data/(?P<document_id>[^/]+)/(?P<version>.*)$', 'document_data', name='storage_document_data'),
-)
+++ /dev/null
-# Create your views here.
-from django.views.generic.simple import direct_to_template
-from django import http
-from dvcs.models import Document
-
-def document_list(request, template_name="dvcs/document_list.html"):
- return direct_to_template(request, template_name, {
- "documents": Document.objects.all(),
- })
-
-def document_data(request, document_id, version=None):
- doc = Document.objects.get(pk=document_id)
- return http.HttpResponse(doc.materialize(version or None), content_type="text/plain")
-
-def document_history(request, docid, template_name="dvcs/document_history.html"):
- document = Document.objects.get(pk=docid)
- return direct_to_template(request, template_name, {
- "document": document,
- "changes": document.history(),
- })
-
+++ /dev/null
-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(), [])
publishable = form.cleaned_data['publishable']
change = doc.at_revision(revision)
if publishable != change.publishable:
- change.publishable = publishable
- change.save()
+ change.set_publishable(publishable)
return JSONResponse({"message": _("Revision marked")})
else:
return JSONResponse({"message": _("Nothing changed")})
] + sys.path
# Run Django
+os.environ["CELERY_LOADER"] = "django"
os.environ['DJANGO_SETTINGS_MODULE'] = '$PROJECT_NAME.settings'
from django.core.handlers.wsgi import WSGIHandler
MAINTENANCE_MODE = False
ADMINS = (
- # (u'Marek Stępniowski', 'marek@stepniowski.com'),
- # (u'Łukasz Rekucki', 'lrekucki@gmail.com'),
(u'Radek Czajka', 'radoslaw.czajka@nowoczesnapolska.org.pl'),
)
'filebrowser',
'pagination',
'gravatar',
+ 'djcelery',
+ 'djkombu',
'catalogue',
'dvcs',
IMAGE_DIR = 'images'
-WL_API_CONFIG = {
- "URL": "http://localhost:7000/api/",
- "AUTH_REALM": "WL API",
- "AUTH_USER": "platforma",
- "AUTH_PASSWD": "platforma",
-}
+import djcelery
+djcelery.setup_loader()
+
+
SHOW_APP_VERSION = False
),
'output_filename': 'compressed/detail_styles_?.css',
},
- 'listing': {
+ 'catalogue': {
'source_filenames': (
'css/filelist.css',
),
- 'output_filename': 'compressed/listing_styles_?.css',
+ 'output_filename': 'compressed/catalogue_styles_?.css',
}
}
),
'output_filename': 'compressed/detail_scripts_?.js',
},
- 'listing': {
+ 'catalogue': {
'source_filenames': (
- 'js/lib/jquery-1.4.2.min.js',
+ 'js/catalogue.js',
'js/slugify.js',
),
- 'output_filename': 'compressed/listing_scripts_?.js',
+ 'output_filename': 'compressed/catalogue_scripts_?.js',
}
}
import tempfile
-WIKI_REPOSITORY_PATH = tempfile.mkdtemp(prefix='wikirepo')
+CATALOGUE_REPO_PATH = tempfile.mkdtemp(prefix='wikirepo')
INSTALLED_APPS += ('django_nose',)
TEST_RUNNER = 'django_nose.run_tests'
-TEST_MODULES = ('wiki', 'toolbar', 'vstorage')
+TEST_MODULES = ('catalogue', 'dvcs.tests', 'wiki', 'toolbar')
NOSE_ARGS = (
'--tests=' + ','.join(TEST_MODULES),
'--cover-package=' + ','.join(TEST_MODULES),
--- /dev/null
+(function($) {
+ $(function() {
+
+
+ $(function() {
+ $('.filter').change(function() {
+ document.filter[this.name].value = this.value;
+ document.filter.submit();
+ });
+
+ $('.check-filter').change(function() {
+ document.filter[this.name].value = this.checked ? '1' : '';
+ document.filter.submit();
+ });
+
+ $('.text-filter').each(function() {
+ var inp = this;
+ $(inp).parent().submit(function() {
+ document.filter[inp.name].value = inp.value;
+ document.filter.submit();
+ return false;
+ });
+ });
+ });
+
+
+ });
+})(jQuery)
+
--- /dev/null
+{% if is_paginated %}
+{% load i18n %}
+<div class="pagination">
+ {% if page_obj.has_previous %}
+ <a href="?page={{ page_obj.previous_page_number }}{{ getvars }}{{ hashtag }}" class="prev">‹‹ {% trans "previous" %}</a>
+ {% else %}
+ <span class="disabled prev">‹‹ {% trans "previous" %}</span>
+ {% endif %}
+ {% for page in pages %}
+ {% if page %}
+ {% ifequal page page_obj.number %}
+ <span class="current page">{{ page }}</span>
+ {% else %}
+ <a href="?page={{ page }}{{ getvars }}{{ hashtag }}" class="page">{{ page }}</a>
+ {% endifequal %}
+ {% else %}
+ ...
+ {% endif %}
+ {% endfor %}
+ {% if page_obj.has_next %}
+ <a href="?page={{ page_obj.next_page_number }}{{ getvars }}{{ hashtag }}" class="next">{% trans "next" %} ››</a>
+ {% else %}
+ <span class="disabled next">{% trans "next" %} ››</span>
+ {% endif %}
+</div>
+{% endif %}
-django-nose==0.0.3
+django-nose==0.1.3
nose
nosexcover
django-maintenancemode>=0.9
django-pagination
django-gravatar
+django-celery
+django-kombu
# migrations
south>=0.6