Merge branch 'pretty' of github.com:fnp/wolnelektury into pretty
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Fri, 30 Dec 2011 08:45:20 +0000 (09:45 +0100)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Fri, 30 Dec 2011 08:45:20 +0000 (09:45 +0100)
Conflicts:
apps/catalogue/management/commands/importbooks.py
wolnelektury/templates/catalogue/book_detail.html
wolnelektury/templates/catalogue/book_short.html

26 files changed:
apps/api/handlers.py
apps/api/urls.py
apps/catalogue/import_utils.py [new file with mode: 0755]
apps/catalogue/management/commands/importbooks.py
apps/catalogue/migrations/0022_auto__add_field_book_common_slug__add_unique_book_slug__del_unique_boo.py [new file with mode: 0644]
apps/catalogue/migrations/0023_common_slug.py [new file with mode: 0644]
apps/catalogue/models.py
apps/catalogue/test_utils.py
apps/catalogue/tests/book_import.py
apps/catalogue/tests/bookmedia.py
apps/catalogue/tests/tags.py
apps/catalogue/urls.py
apps/catalogue/utils.py
apps/catalogue/views.py
apps/dictionary/templates/dictionary/note_list.html
apps/lesmianator/urls.py
apps/lesmianator/views.py
apps/picture/models.py
lib/librarian
wolnelektury/settings.py
wolnelektury/templates/catalogue/book_detail.html
wolnelektury/templates/catalogue/book_sets.html
wolnelektury/templates/catalogue/book_short.html
wolnelektury/templates/catalogue/player.html
wolnelektury/templates/catalogue/tagged_object_list.html
wolnelektury/templates/lesmianator/poem.html

index d34806f..586e921 100644 (file)
@@ -99,14 +99,10 @@ class BookDetailHandler(BaseHandler):
         'media', 'url'] + category_singular.keys()
 
     @piwik_track
-    def read(self, request, book):
+    def read(self, request, slug):
         """ Returns details of a book, identified by a slug and lang. """
-        kwargs = Book.split_urlid(book)
-        if not kwargs:
-            return rc.NOT_FOUND
-
         try:
-            return Book.objects.get(**kwargs)
+            return Book.objects.get(slug=slug)
         except Book.DoesNotExist:
             return rc.NOT_FOUND
 
@@ -127,7 +123,7 @@ class AnonymousBooksHandler(AnonymousBaseHandler):
     @classmethod
     def href(cls, book):
         """ Returns an URI for a Book in the API. """
-        return API_BASE + reverse("api_book", args=[book.urlid()])
+        return API_BASE + reverse("api_book", args=[book.slug])
 
     @classmethod
     def url(cls, book):
@@ -269,18 +265,10 @@ class FragmentDetailHandler(BaseHandler):
     fields = ['book', 'anchor', 'text', 'url', 'themes']
 
     @piwik_track
-    def read(self, request, book, anchor):
+    def read(self, request, slug, anchor):
         """ Returns details of a fragment, identified by book slug and anchor. """
-        kwargs = Book.split_urlid(book)
-        if not kwargs:
-            return rc.NOT_FOUND
-
-        fragment_kwargs = {}
-        for field, value in kwargs.items():
-            fragment_kwargs['book__' + field] = value
-
         try:
-            return Fragment.objects.get(anchor=anchor, **fragment_kwargs)
+            return Fragment.objects.get(book__slug=slug, anchor=anchor)
         except Fragment.DoesNotExist:
             return rc.NOT_FOUND
 
@@ -317,7 +305,8 @@ class FragmentsHandler(BaseHandler):
     def href(cls, fragment):
         """ Returns URI in the API for the fragment. """
 
-        return API_BASE + reverse("api_fragment", args=[fragment.book.urlid(), fragment.anchor])
+        return API_BASE + reverse("api_fragment", 
+            args=[fragment.book.slug, fragment.anchor])
 
     @classmethod
     def url(cls, fragment):
index 8f7bed3..fd97d63 100644 (file)
@@ -47,10 +47,10 @@ urlpatterns = patterns(
 
 
     # objects details
-    url(r'^books/(?P<book>%s)/$' % Book.URLID_RE, book_resource, name="api_book"),
+    url(r'^books/(?P<book>[a-z0-9-]+)/$', book_resource, name="api_book"),
     url(r'^(?P<category>[a-z0-9-]+)/(?P<slug>[a-z0-9-]+)/$',
         tag_resource, name="api_tag"),
-    url(r'^books/(?P<book>%s)/fragments/(?P<anchor>[a-z0-9-]+)/$' % Book.URLID_RE,
+    url(r'^books/(?P<book>[a-z0-9-]+)/fragments/(?P<anchor>[a-z0-9-]+)/$',
         fragment_resource, name="api_fragment"),
 
     # books by tags
diff --git a/apps/catalogue/import_utils.py b/apps/catalogue/import_utils.py
new file mode 100755 (executable)
index 0000000..bf36ea5
--- /dev/null
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from librarian import DocProvider
+
+class ORMDocProvider(DocProvider):
+    """Used for getting books' children."""
+
+    def __init__(self, book):
+        self.book = book
+
+    def by_slug(self, slug):
+        if slug == self.book.slug:
+            return open(self.book.xml_file.path)
+        else:
+            return type(self.book).objects.get(slug=slug).xml_file
\ No newline at end of file
index b6ddc55..5b4d499 100644 (file)
@@ -48,11 +48,10 @@ class Command(BaseCommand):
                                                     build_pdf=options.get('build_pdf'),
                                                     build_mobi=options.get('build_mobi'),
                                                     search_index=options.get('search_index'))
-        fileid = book.fileid()
         for ebook_format in Book.ebook_formats:
             if os.path.isfile(file_base + '.' + ebook_format):
                 getattr(book, '%s_file' % ebook_format).save(
-                    '%s.%s' % (fileid, ebook_format), 
+                    '%s.%s' % (book.slug, ebook_format), 
                     File(file(file_base + '.' + ebook_format)))
                 if verbose:
                     print "Importing %s.%s" % (file_base, ebook_format)
diff --git a/apps/catalogue/migrations/0022_auto__add_field_book_common_slug__add_unique_book_slug__del_unique_boo.py b/apps/catalogue/migrations/0022_auto__add_field_book_common_slug__add_unique_book_slug__del_unique_boo.py
new file mode 100644 (file)
index 0000000..75a1c99
--- /dev/null
@@ -0,0 +1,137 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Removing unique constraint on 'Book', fields ['slug', 'language']
+        db.delete_unique('catalogue_book', ['slug', 'language'])
+
+        # Adding field 'Book.common_slug'
+        db.add_column('catalogue_book', 'common_slug', self.gf('django.db.models.fields.SlugField')(default='-', max_length=120, db_index=True), keep_default=False)
+
+        # Adding unique constraint on 'Book', fields ['slug']
+        db.create_unique('catalogue_book', ['slug'])
+
+
+    def backwards(self, orm):
+        
+        # Removing unique constraint on 'Book', fields ['slug']
+        db.delete_unique('catalogue_book', ['slug'])
+
+        # Deleting field 'Book.common_slug'
+        db.delete_column('catalogue_book', 'common_slug')
+
+        # Adding unique constraint on 'Book', fields ['slug', 'language']
+        db.create_unique('catalogue_book', ['slug', 'language'])
+
+
+    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': "('sort_key',)", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': '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', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
diff --git a/apps/catalogue/migrations/0023_common_slug.py b/apps/catalogue/migrations/0023_common_slug.py
new file mode 100644 (file)
index 0000000..3863149
--- /dev/null
@@ -0,0 +1,123 @@
+# 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):
+        "Write your forwards methods here."
+        orm.Book.objects.all().update(common_slug=models.F('slug'))
+
+
+    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': "('sort_key',)", 'object_name': 'Book'},
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}),
+            'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': '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', [], {'default': '0'}),
+            'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        'catalogue.bookmedia': {
+            'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}),
+            'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}),
+            'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}),
+            'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'catalogue.fragment': {
+            'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'},
+            'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}),
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'short_text': ('django.db.models.fields.TextField', [], {}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        'catalogue.tag': {
+            'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'},
+            'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}),
+            'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'})
+        },
+        'catalogue.tagrelation': {
+            'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['catalogue']
index 77efe6e..78e6b0b 100644 (file)
@@ -222,7 +222,7 @@ def get_customized_pdf_path(book, customizations):
     customizations.sort()
     h = hash(tuple(customizations))
 
-    pdf_name = '%s-custom-%s' % (book.fileid(), h)
+    pdf_name = '%s-custom-%s' % (book.slug, h)
     pdf_file = get_dynamic_path(None, pdf_name, ext='pdf')
 
     return pdf_file
@@ -232,7 +232,7 @@ def get_existing_customized_pdf(book):
     """
     Returns a list of paths to generated customized pdf of a book
     """
-    pdf_glob = '%s-custom-' % (book.fileid(),)
+    pdf_glob = '%s-custom-' % (book.slug,)
     pdf_glob = get_dynamic_path(None, pdf_glob, ext='pdf')
     pdf_glob = re.sub(r"[.]([a-z0-9]+)$", "*.\\1", pdf_glob)
     return glob(path.join(settings.MEDIA_ROOT, pdf_glob))
@@ -271,7 +271,7 @@ class BookMedia(models.Model):
         try:
             old = BookMedia.objects.get(pk=self.pk)
         except BookMedia.DoesNotExist, e:
-            pass
+            old = None
         else:
             # if name changed, change the file name, too
             if slughifi(self.name) != slughifi(old.name):
@@ -280,7 +280,9 @@ class BookMedia(models.Model):
         super(BookMedia, self).save(*args, **kwargs)
 
         # remove the zip package for book with modified media
-        remove_zip(self.book.fileid())
+        if old:
+            remove_zip("%s_%s" % (old.book.slug, old.type))
+        remove_zip("%s_%s" % (self.book.slug, self.type))
 
         extra_info = self.get_extra_info_value()
         extra_info.update(self.read_meta())
@@ -349,7 +351,9 @@ class BookMedia(models.Model):
 class Book(models.Model):
     title         = models.CharField(_('title'), max_length=120)
     sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False)
-    slug          = models.SlugField(_('slug'), max_length=120, db_index=True)
+    slug = models.SlugField(_('slug'), max_length=120, db_index=True,
+            unique=True)
+    common_slug = models.SlugField(_('slug'), max_length=120, db_index=True)
     language = models.CharField(_('language code'), max_length=3, db_index=True,
                     default=settings.CATALOGUE_DEFAULT_LANGUAGE)
     description   = models.TextField(_('description'), blank=True)
@@ -374,14 +378,10 @@ class Book(models.Model):
     html_built = django.dispatch.Signal()
     published = django.dispatch.Signal()
 
-    URLID_RE = r'[a-z0-9-]+(?:/[a-z]{3})?'
-    FILEID_RE = r'[a-z0-9-]+(?:_[a-z]{3})?'
-
     class AlreadyExists(Exception):
         pass
 
     class Meta:
-        unique_together = [['slug', 'language']]
         ordering = ('sort_key',)
         verbose_name = _('book')
         verbose_name_plural = _('books')
@@ -389,42 +389,6 @@ class Book(models.Model):
     def __unicode__(self):
         return self.title
 
-    def urlid(self, sep='/'):
-        stem = self.slug
-        if self.language != settings.CATALOGUE_DEFAULT_LANGUAGE:
-            stem += sep + self.language
-        return stem
-
-    def fileid(self):
-        return self.urlid('_')
-
-    @staticmethod
-    def split_urlid(urlid, sep='/', default_lang=settings.CATALOGUE_DEFAULT_LANGUAGE):
-        """Splits a URL book id into slug and language code.
-        
-        Returns a dictionary usable i.e. for object lookup, or None.
-
-        >>> Book.split_urlid("a-slug/pol", default_lang="eng")
-        {'slug': 'a-slug', 'language': 'pol'}
-        >>> Book.split_urlid("a-slug", default_lang="eng")
-        {'slug': 'a-slug', 'language': 'eng'}
-        >>> Book.split_urlid("a-slug_pol", "_", default_lang="eng")
-        {'slug': 'a-slug', 'language': 'pol'}
-        >>> Book.split_urlid("a-slug/eng", default_lang="eng")
-
-        """
-        parts = urlid.rsplit(sep, 1)
-        if len(parts) == 2:
-            if parts[1] == default_lang:
-                return None
-            return {'slug': parts[0], 'language': parts[1]}
-        else:
-            return {'slug': urlid, 'language': default_lang}
-
-    @classmethod
-    def split_fileid(cls, fileid):
-        return cls.split_urlid(fileid, '_')
-
     def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
         from sortify import sortify
 
@@ -439,18 +403,14 @@ class Book(models.Model):
 
     @permalink
     def get_absolute_url(self):
-        return ('catalogue.views.book_detail', [self.urlid()])
+        return ('catalogue.views.book_detail', [self.slug])
 
     @property
     def name(self):
         return self.title
 
     def book_tag_slug(self):
-        stem = 'l-' + self.slug
-        if self.language != settings.CATALOGUE_DEFAULT_LANGUAGE:
-            return stem[:116] + ' ' + self.language
-        else:
-            return stem[:120]
+        return ('l-' + self.slug)[:120]
 
     def book_tag(self):
         slug = self.book_tag_slug()
@@ -563,7 +523,7 @@ class Book(models.Model):
     has_daisy_file.boolean = True
 
     def wldocument(self, parse_dublincore=True):
-        from catalogue.utils import ORMDocProvider
+        from catalogue.import_utils import ORMDocProvider
         from librarian.parser import WLDocument
 
         return WLDocument.from_file(self.xml_file.path,
@@ -599,7 +559,7 @@ class Book(models.Model):
             # we'd like to be sure not to overwrite changes happening while
             # (timely) pdf generation is taking place (async celery scenario)
             current_self = Book.objects.get(id=self.id)
-            current_self.pdf_file.save('%s.pdf' % self.fileid(),
+            current_self.pdf_file.save('%s.pdf' % self.slug,
                     File(open(pdf.get_filename())))
             self.pdf_file = current_self.pdf_file
 
@@ -622,7 +582,7 @@ class Book(models.Model):
 
         mobi = self.wldocument().as_mobi()
 
-        self.mobi_file.save('%s.mobi' % self.fileid(), File(open(mobi.get_filename())))
+        self.mobi_file.save('%s.mobi' % self.slug, File(open(mobi.get_filename())))
 
         # remove zip with all mobi files
         remove_zip(settings.ALL_MOBI_ZIP)
@@ -634,7 +594,7 @@ class Book(models.Model):
 
         epub = self.wldocument().as_epub()
 
-        self.epub_file.save('%s.epub' % self.fileid(),
+        self.epub_file.save('%s.epub' % self.slug,
                 File(open(epub.get_filename())))
 
         # remove zip package with all epub files
@@ -644,7 +604,7 @@ class Book(models.Model):
         from django.core.files.base import ContentFile
 
         text = self.wldocument().as_text()
-        self.txt_file.save('%s.txt' % self.fileid(), ContentFile(text.get_string()))
+        self.txt_file.save('%s.txt' % self.slug, ContentFile(text.get_string()))
 
 
     def build_html(self):
@@ -659,7 +619,7 @@ class Book(models.Model):
 
         html_output = self.wldocument(parse_dublincore=False).as_html()
         if html_output:
-            self.html_file.save('%s.html' % self.fileid(),
+            self.html_file.save('%s.html' % self.slug,
                     ContentFile(html_output.get_string()))
 
             # get ancestor l-tags for adding to new fragments
@@ -710,7 +670,7 @@ class Book(models.Model):
         def pretty_file_name(book):
             return "%s/%s.%s" % (
                 b.get_extra_info_value()['author'],
-                b.fileid(),
+                b.slug,
                 format_)
 
         field_name = "%s_file" % format_
@@ -724,7 +684,7 @@ class Book(models.Model):
     def zip_audiobooks(self, format_):
         bm = BookMedia.objects.filter(book=self, type=format_)
         paths = map(lambda bm: (None, bm.file.path), bm)
-        result = create_zip.delay(paths, "%s_%s" % (self.fileid(), format_))
+        result = create_zip.delay(paths, "%s_%s" % (self.slug, format_))
         return result.wait()
 
     def search_index(self, book_info=None):
@@ -768,30 +728,33 @@ class Book(models.Model):
         if hasattr(book_info, 'parts'):
             for part_url in book_info.parts:
                 try:
-                    children.append(Book.objects.get(
-                        slug=part_url.slug, language=part_url.language))
+                    children.append(Book.objects.get(slug=part_url.slug))
                 except Book.DoesNotExist, e:
-                    raise Book.DoesNotExist(_('Book "%s/%s" does not exist.') %
-                            (part_url.slug, part_url.language))
+                    raise Book.DoesNotExist(_('Book "%s" does not exist.') %
+                            part_url.slug)
 
 
         # Read book metadata
         book_slug = book_info.url.slug
-        language = book_info.language
-        if re.search(r'[^a-zA-Z0-9-]', book_slug):
+        if re.search(r'[^a-z0-9-]', book_slug):
             raise ValueError('Invalid characters in slug')
-        book, created = Book.objects.get_or_create(slug=book_slug, language=language)
+        book, created = Book.objects.get_or_create(slug=book_slug)
 
         if created:
             book_shelves = []
         else:
             if not overwrite:
-                raise Book.AlreadyExists(_('Book %s/%s already exists') % (
-                        book_slug, language))
+                raise Book.AlreadyExists(_('Book %s already exists') % (
+                        book_slug))
             # Save shelves for this book
             book_shelves = list(book.tags.filter(category='set'))
 
+        book.language = book_info.language
         book.title = book_info.title
+        if book_info.variant_of:
+            book.common_slug = book_info.variant_of.slug
+        else:
+            book.common_slug = book.slug
         book.set_extra_info_value(book_info.to_dict())
         book.save()
 
@@ -955,7 +918,7 @@ class Book(models.Model):
 
         books_by_parent = {}
         books = cls.objects.all().order_by('parent_number', 'sort_key').only(
-                'title', 'parent', 'slug', 'language')
+                'title', 'parent', 'slug')
         if filter:
             books = books.filter(filter).distinct()
             book_ids = set((book.pk for book in books))
index 58ef58a..eeda03f 100644 (file)
@@ -13,10 +13,12 @@ class WLTestCase(TestCase):
         self._MEDIA_ROOT, settings.MEDIA_ROOT = settings.MEDIA_ROOT, tempfile.mkdtemp(prefix='djangotest_')
         settings.NO_BUILD_PDF = settings.NO_BUILD_MOBI = settings.NO_BUILD_EPUB = settings.NO_BUILD_TXT = True
         settings.CELERY_ALWAYS_EAGER = True
+        self._CACHE_BACKEND, settings.CACHE_BACKEND = settings.CACHE_BACKEND, 'dummy://'
 
     def tearDown(self):
         shutil.rmtree(settings.MEDIA_ROOT, True)
         settings.MEDIA_ROOT = self._MEDIA_ROOT
+        settings.CACHE_BACKEND = self._CACHE_BACKEND
 
 class PersonStub(object):
 
@@ -29,7 +31,7 @@ class PersonStub(object):
 
 
 class BookInfoStub(object):
-    _empty_fields = ['cover_url']
+    _empty_fields = ['cover_url', 'variant_of']
     # allow single definition for multiple-value fields
     _salias = {
         'authors': 'author',
@@ -65,7 +67,7 @@ def info_args(title, language=None):
         language = u'pol'
     return {
         'title': unicode(title),
-        'url': WLURI.from_slug_and_lang(slug, language),
+        'url': WLURI.from_slug(slug),
         'about': u"http://wolnelektury.pl/example/URI/%s" % slug,
         'language': language,
     }
index a97c417..3af1bb4 100644 (file)
@@ -15,7 +15,7 @@ class BookImportLogicTests(WLTestCase):
     def setUp(self):
         WLTestCase.setUp(self)
         self.book_info = BookInfoStub(
-            url=WLURI.from_slug_and_lang(u"default-book", None),
+            url=WLURI.from_slug(u"default-book"),
             about=u"http://wolnelektury.pl/example/URI/default_book",
             title=u"Default Book",
             author=PersonStub(("Jim",), "Lazy"),
@@ -114,7 +114,7 @@ class BookImportLogicTests(WLTestCase):
     @raises(ValueError)
     def test_book_with_invalid_slug(self):
         """ Book with invalid characters in slug shouldn't be imported """
-        self.book_info.url = WLURI.from_slug_and_lang(u"default_book", None)
+        self.book_info.url = WLURI.from_slug(u"default_book")
         BOOK_TEXT = "<utwor />"
         book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info)
 
@@ -247,12 +247,15 @@ class ChildImportTests(WLTestCase):
 class MultilingualBookImportTest(WLTestCase):
     def setUp(self):
         WLTestCase.setUp(self)
+        common_uri = WLURI.from_slug('common-slug')
+
         self.pol_info = BookInfoStub(
             genre='X-Genre',
             epoch='X-Epoch',
             kind='X-Kind',
             author=PersonStub(("Joe",), "Doe"),
-            **info_args("A book")
+            variant_of=common_uri,
+            **info_args(u"Książka")
         )
 
         self.eng_info = BookInfoStub(
@@ -260,6 +263,7 @@ class MultilingualBookImportTest(WLTestCase):
             epoch='X-Epoch',
             kind='X-Kind',
             author=PersonStub(("Joe",), "Doe"),
+            variant_of=common_uri,
             **info_args("A book", "eng")
         )
 
index b6598b3..5d2ba66 100644 (file)
@@ -103,8 +103,8 @@ class BookMediaTests(WLTestCase):
         bm.file.save(None, self.file)
         bm.save()
         
-        zip_url = self.book.zip_audiobooks()
-        self.assertEqual('zip/'+self.book.slug+'.zip', zip_url)
+        zip_url = self.book.zip_audiobooks('ogg')
+        self.assertEqual('zip/'+self.book.slug+'_ogg.zip', zip_url)
         self.assertTrue(exists(join(settings.MEDIA_ROOT, zip_url)))
 
         bm2 = models.BookMedia(book=self.book, type='ogg', name="Other title")
index 7e6e667..a47e426 100644 (file)
@@ -3,9 +3,6 @@ from catalogue import models
 from catalogue.test_utils import *
 from django.core.files.base import ContentFile
 
-from nose.tools import raises
-
-
 class BooksByTagTests(WLTestCase):
     """ tests the /katalog/category/tag page for found books """
 
@@ -66,12 +63,13 @@ class BooksByTagTests(WLTestCase):
                          ['Child'])
 
 
-
+from django.test import Client
 class TagRelatedTagsTests(WLTestCase):
     """ tests the /katalog/category/tag/ page for related tags """
 
     def setUp(self):
         WLTestCase.setUp(self)
+        self.client = Client()
         author = PersonStub(("Common",), "Man")
 
         gchild_info = BookInfoStub(author=author, genre="GchildGenre", epoch='Epoch', kind="Kind",
@@ -132,14 +130,14 @@ class TagRelatedTagsTests(WLTestCase):
     def test_related_differ(self):
         """ related tags shouldn't include filtering tags """
 
-        cats = self.client.get('/katalog/rodzaj/kind/').context['categories']
+        response = self.client.get('/katalog/rodzaj/kind/')
+        cats = response.context['categories']
         self.assertFalse('Kind' in [tag.name for tag in cats['kind']],
                          'filtering tag wrongly included in related')
         cats = self.client.get('/katalog/motyw/theme/').context['categories']
         self.assertFalse('Theme' in [tag.name for tag in cats['theme']],
                          'filtering theme wrongly included in related')
 
-
     def test_parent_tag_once(self):
         """ if parent and descendants have a common tag, count it only once """
 
index 4020592..b6fd361 100644 (file)
@@ -7,22 +7,25 @@ from catalogue.feeds import AudiobookFeed
 from catalogue.models import Book
 from picture.models import Picture
 
+
+SLUG = r'[a-z0-9-]*'
+
 urlpatterns = patterns('picture.views',
                        # pictures - currently pictures are coupled with catalogue, hence the url is here
         url(r'^obraz/?$', 'picture_list'),
-        url(r'^obraz/(?P<picture>%s)/?$' % Picture.URLID_RE, 'picture_detail')
+        url(r'^obraz/(?P<picture>%s)/?$' % SLUG, 'picture_detail')
         ) + \
     patterns('catalogue.views',
     url(r'^$', 'catalogue', name='catalogue'),
     url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/formaty/$', 'shelf_book_formats', name='shelf_book_formats'),
-    url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/(?P<book>%s)/usun$' % Book.URLID_RE, 'remove_from_shelf', name='remove_from_shelf'),
+    url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/(?P<slug>%s)/usun$' % SLUG, 'remove_from_shelf', name='remove_from_shelf'),
     url(r'^polki/$', 'user_shelves', name='user_shelves'),
     url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)/usun/$', 'delete_shelf', name='delete_shelf'),
     url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)\.zip$', 'download_shelf', name='download_shelf'),
     url(r'^lektury/', 'book_list', name='book_list'),
     url(r'^audiobooki/$', 'audiobook_list', name='audiobook_list'),
     url(r'^daisy/$', 'daisy_list', name='daisy_list'),
-    url(r'^lektura/(?P<book>%s)/polki/' % Book.URLID_RE, 'book_sets', name='book_shelves'),
+    url(r'^lektura/(?P<book>%s)/polki/' % SLUG, 'book_sets', name='book_shelves'),
     url(r'^polki/nowa/$', 'new_set', name='new_set'),
     url(r'^tags/$', 'tags_starting_with', name='hint'),
     url(r'^jtags/$', 'json_tags_starting_with', name='jhint'),
@@ -32,20 +35,20 @@ urlpatterns = patterns('picture.views',
     url(r'^zip/pdf\.zip$', 'download_zip', {'format': 'pdf', 'slug': None}, 'download_zip_pdf'),
     url(r'^zip/epub\.zip$', 'download_zip', {'format': 'epub', 'slug': None}, 'download_zip_epub'),
     url(r'^zip/mobi\.zip$', 'download_zip', {'format': 'mobi', 'slug': None}, 'download_zip_mobi'),
-    url(r'^zip/mp3/(?P<book>%s)\.zip' % Book.FILEID_RE, 'download_zip', {'format': 'mp3'}, 'download_zip_mp3'),
-    url(r'^zip/ogg/(?P<book>%s)\.zip' % Book.FILEID_RE, 'download_zip', {'format': 'ogg'}, 'download_zip_ogg'),
+    url(r'^zip/mp3/(?P<slug>%s)\.zip' % SLUG, 'download_zip', {'format': 'mp3'}, 'download_zip_mp3'),
+    url(r'^zip/ogg/(?P<slug>%s)\.zip' % SLUG, 'download_zip', {'format': 'ogg'}, 'download_zip_ogg'),
 
     # Public interface. Do not change this URLs.
-    url(r'^lektura/(?P<book>%s)\.html$' % Book.FILEID_RE, 'book_text', name='book_text'),
-    url(r'^lektura/(?P<book>%s)/audiobook/$' % Book.URLID_RE, 'player', name='book_player'),
-    url(r'^lektura/(?P<book>%s)/$' % Book.URLID_RE, 'book_detail', name='book_detail'),
-    url(r'^lektura/(?P<book>%s)/motyw/(?P<theme_slug>[a-zA-Z0-9-]+)/$' % Book.URLID_RE,
+    url(r'^lektura/(?P<slug>%s)\.html$' % SLUG, 'book_text', name='book_text'),
+    url(r'^lektura/(?P<slug>%s)/audiobook/$' % SLUG, 'player', name='book_player'),
+    url(r'^lektura/(?P<slug>%s)/$' % SLUG, 'book_detail', name='book_detail'),
+    url(r'^lektura/(?P<slug>%s)/motyw/(?P<theme_slug>[a-zA-Z0-9-]+)/$' % SLUG,
         'book_fragments', name='book_fragments'),
 
     url(r'^(?P<tags>[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'),
 
     url(r'^audiobooki/(?P<type>mp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'),
 
-    url(r'^custompdf/(?P<book_fileid>%s).pdf' % Book.FILEID_RE, 'download_custom_pdf'),
+    url(r'^custompdf/(?P<slug>%s).pdf' % SLUG, 'download_custom_pdf'),
 
 ) 
index 145511e..a48ec03 100644 (file)
@@ -20,7 +20,6 @@ from errno import EEXIST, ENOENT
 from fcntl import flock, LOCK_EX
 from zipfile import ZipFile
 
-from librarian import DocProvider
 from reporting.utils import read_chunks
 from celery.task import task
 import catalogue.models
@@ -60,20 +59,6 @@ class ExistingFile(UploadedFile):
         pass
 
 
-class ORMDocProvider(DocProvider):
-    """Used for getting books' children."""
-
-    def __init__(self, book):
-        self.book = book
-
-    def by_slug_and_lang(self, slug, language):
-        if slug == self.book.slug and language == self.language:
-            return open(self.book.xml_file.path)
-        else:
-            return type(self.book).objects.get(
-                    slug=slug, language=language).xml_file
-
-
 class LockFile(object):
     """
     A file lock monitor class; createas an ${objname}.lock
@@ -180,7 +165,11 @@ class MultiQuerySet(object):
         return self.count()
         
     def __getitem__(self, item):
-        indices = (offset, stop, step) = item.indices(self.count())
+        try:
+            indices = (offset, stop, step) = item.indices(self.count())
+        except AttributeError:
+            # it's not a slice - make it one
+            return self[item : item + 1][0]
         items = []
         total_len = stop - offset
         for qs in self.querysets:
index 90fe22f..3a32832 100644 (file)
@@ -185,11 +185,8 @@ def tagged_object_list(request, tags=''):
         context_instance=RequestContext(request))
 
 
-def book_fragments(request, book, theme_slug):
-    kwargs = models.Book.split_urlid(book)
-    if kwargs is None:
-        raise Http404
-    book = get_object_or_404(models.Book, **kwargs)
+def book_fragments(request, slug, theme_slug):
+    book = get_object_or_404(models.Book, slug=slug)
 
     book_tag = book.book_tag()
     theme = get_object_or_404(models.Tag, slug=theme_slug, category='theme')
@@ -199,12 +196,9 @@ def book_fragments(request, book, theme_slug):
         context_instance=RequestContext(request))
 
 
-def book_detail(request, book):
-    kwargs = models.Book.split_urlid(book)
-    if kwargs is None:
-        raise Http404
+def book_detail(request, slug):
     try:
-        book = models.Book.objects.get(**kwargs)
+        book = models.Book.objects.get(slug=slug)
     except models.Book.DoesNotExist:
         return pdcounter_views.book_stub_detail(request, kwargs['slug'])
     
@@ -233,11 +227,8 @@ def book_detail(request, book):
         context_instance=RequestContext(request))
 
 
-def player(request, book):
-    kwargs = models.Book.split_urlid(book)
-    if kwargs is None:
-        raise Http404
-    book = get_object_or_404(models.Book, **kwargs)
+def player(request, slug):
+    book = get_object_or_404(models.Book, slug=slug)
     if not book.has_media('mp3'):
         raise Http404
 
@@ -274,11 +265,8 @@ def player(request, book):
         context_instance=RequestContext(request))
 
 
-def book_text(request, book):
-    kwargs = models.Book.split_fileid(book)
-    if kwargs is None:
-        raise Http404
-    book = get_object_or_404(models.Book, **kwargs)
+def book_text(request, slug):
+    book = get_object_or_404(models.Book, slug=slug)
 
     if not book.has_html_file():
         raise Http404
@@ -513,14 +501,11 @@ def user_shelves(request):
             context_instance=RequestContext(request))
 
 @cache.never_cache
-def book_sets(request, book):
+def book_sets(request, slug):
     if not request.user.is_authenticated():
         return HttpResponse(_('<p>To maintain your shelves you need to be logged in.</p>'))
 
-    kwargs = models.Book.split_urlid(book)
-    if kwargs is None:
-        raise Http404
-    book = get_object_or_404(models.Book, **kwargs)
+    book = get_object_or_404(models.Book, slug=slug)
 
     user_sets = models.Tag.objects.filter(category='set', user=request.user)
     book_sets = book.tags.filter(category='set', user=request.user)
@@ -553,11 +538,8 @@ def book_sets(request, book):
 @login_required
 @require_POST
 @cache.never_cache
-def remove_from_shelf(request, shelf, book):
-    kwargs = models.Book.split_urlid(book)
-    if kwargs is None:
-        raise Http404
-    book = get_object_or_404(models.Book, **kwargs)
+def remove_from_shelf(request, shelf, slug):
+    book = get_object_or_404(models.Book, slug=slug)
 
     shelf = get_object_or_404(models.Tag, slug=shelf, category='set', user=request.user)
 
@@ -608,11 +590,10 @@ def download_shelf(request, slug):
     archive = zipfile.ZipFile(temp, 'w')
 
     for book in collect_books(models.Book.tagged.with_all(shelf)):
-        fileid = book.fileid()
         for ebook_format in models.Book.ebook_formats:
             if ebook_format in formats and book.has_media(ebook_format):
                 filename = book.get_media(ebook_format).path
-                archive.write(filename, str('%s.%s' % (fileid, ebook_format)))
+                archive.write(filename, str('%s.%s' % (book.slug, ebook_format)))
     archive.close()
 
     response = HttpResponse(content_type='application/zip', mimetype='application/x-zip-compressed')
@@ -711,25 +692,20 @@ def tag_info(request, id):
     return HttpResponse(tag.description)
 
 
-def download_zip(request, format, book=None):
-    kwargs = models.Book.split_fileid(book)
-
+def download_zip(request, format, slug=None):
     url = None
     if format in models.Book.ebook_formats:
         url = models.Book.zip_format(format)
-    elif format in ('mp3', 'ogg') and kwargs is not None:
-        book = get_object_or_404(models.Book, **kwargs)
+    elif format in ('mp3', 'ogg') and slug is not None:
+        book = get_object_or_404(models.Book, slug=slug)
         url = book.zip_audiobooks(format)
     else:
         raise Http404('No format specified for zip package')
     return HttpResponseRedirect(urlquote_plus(settings.MEDIA_URL + url, safe='/?='))
 
 
-def download_custom_pdf(request, book_fileid):
-    kwargs = models.Book.split_fileid(book_fileid)
-    if kwargs is None:
-        raise Http404
-    book = get_object_or_404(models.Book, **kwargs)
+def download_custom_pdf(request, slug):
+    book = get_object_or_404(models.Book, slug=slug)
 
     if request.method == 'GET':
         form = forms.CustomPDFForm(request.GET)
@@ -740,7 +716,7 @@ def download_custom_pdf(request, book_fileid):
             if not path.exists(pdf_file):
                 result = async_build_pdf.delay(book.id, cust, pdf_file)
                 result.wait()
-            return AttachmentHttpResponse(file_name=("%s.pdf" % book_fileid), file_path=pdf_file, mimetype="application/pdf")
+            return AttachmentHttpResponse(file_name=("%s.pdf" % book.slug), file_path=pdf_file, mimetype="application/pdf")
         else:
             raise Http404(_('Incorrect customization options for PDF'))
     else:
index cd88c17..aad0ddf 100755 (executable)
@@ -42,7 +42,7 @@
     <div class='dictionary-note'>
     {{ obj.html|safe }}
     <div class='dictionary-note-source'>
-    (<a href='{% url book_text obj.book.fileid %}#{{ obj.anchor }}'>{{ obj.book.pretty_title }}</a>)
+    (<a href='{% url book_text obj.book.slug %}#{{ obj.anchor }}'>{{ obj.book.pretty_title }}</a>)
     </div>
     </div>
 {% endfor %}
index eeba610..de48644 100644 (file)
@@ -8,7 +8,7 @@ from catalogue.models import Book
 urlpatterns = patterns('lesmianator.views',
     url(r'^$', 'main_page', name='lesmianator'),
     url(r'^wiersz/$', 'new_poem', name='new_poem'),
-    url(r'^lektura/(?P<book>%s)/$' % Book.URLID_RE, 'poem_from_book', name='poem_from_book'),
+    url(r'^lektura/(?P<slug>[a-z0-9-]+)/$', 'poem_from_book', name='poem_from_book'),
     url(r'^polka/(?P<shelf>[a-zA-Z0-9-]+)/$', 'poem_from_set', name='poem_from_set'),
     url(r'^wiersz/(?P<poem>[a-zA-Z0-9-]+)/$', 'get_poem', name='get_poem'),
 )
index 28cb32a..e86febe 100644 (file)
@@ -33,11 +33,8 @@ def new_poem(request):
 
 
 @cache.never_cache
-def poem_from_book(request, book):
-    kwargs = Book.split_urlid(book)
-    if kwargs is None:
-        raise Http404
-    book = get_object_or_404(Book, **kwargs)
+def poem_from_book(request, slug):
+    book = get_object_or_404(Book, slug=slug)
     user = request.user if request.user.is_authenticated() else None
     text = Poem.write(Continuations.get(book))
     p = Poem(slug=get_random_hash(text), text=text, created_by=user)
index 862c172..0217d8b 100644 (file)
@@ -44,9 +44,6 @@ class Picture(models.Model):
         verbose_name = _('picture')
         verbose_name_plural = _('pictures')
 
-    URLID_RE = r'[a-z0-9-]+'
-    FILEID_RE = r'[a-z0-9-]+'
-
     def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
         from sortify import sortify
 
@@ -64,10 +61,7 @@ class Picture(models.Model):
 
     @permalink
     def get_absolute_url(self):
-        return ('picture.views.picture_detail', [self.urlid()])
-
-    def urlid(self):
-        return self.slug
+        return ('picture.views.picture_detail', [self.slug])
 
     @classmethod
     def from_xml_file(cls, xml_file, image_file=None, overwrite=False):
index 5fed788..a34b95a 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 5fed78856949474a36bc5e268517775a9a802e27
+Subproject commit a34b95aa7ba5fd4838541d1cdcd28358fb808062
index 00792a8..55c845e 100644 (file)
@@ -158,7 +158,8 @@ INSTALLED_APPS = [
 ]
 
 #CACHE_BACKEND = 'locmem:///?max_entries=3000'
-#CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
+CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
+#CACHE_BACKEND = None
 CACHE_MIDDLEWARE_ANONYMOUS_ONLY=True
 
 # CSS and JavaScript file groups
index f151650..1eee61d 100644 (file)
@@ -9,7 +9,7 @@
 {% if not user.tag_set.count %}
     <p>{% trans "You do not have any shelves. You can create one below, if you want to."%}</p>
 {% else %}
-    <form action="{% url catalogue.views.book_sets book.urlid %}" method="POST" id="putOnShelf" accept-charset="utf-8" class="cuteform">
+    <form action="{% url catalogue.views.book_sets book.slug %}" method="POST" id="putOnShelf" accept-charset="utf-8" class="cuteform">
     <ol>
         <li>{{ form.set_ids }}</li>
         <li><input type="submit" value="{% trans "Put on the shelf!" %}"/></li>
index 1c3a742..7f26237 100644 (file)
@@ -51,7 +51,7 @@
     <ul class="book-box-tools">
         <li class="book-box-read">
         {% if book.html_file %}
-            <a href="{% url book_text book.urlid %}" class="mono downarrow">{% trans "Read online" %}</a>
+            <a href="{% url book_text book.slug %}" class="mono downarrow">{% trans "Read online" %}</a>
         {% endif %}
         </li>
         <li class="book-box-download">
index 95fcb5e..414bf0e 100755 (executable)
@@ -77,8 +77,8 @@
 
 
     <p>{% trans "Download as" %}:
-        <a href="{% url download_zip_mp3 book.fileid %}">MP3</a>{% if have_oggs %},
-            <a href="{% url download_zip_ogg book.fileid %}">Ogg Vorbis</a>{% endif %}.
+        <a href="{% url download_zip_mp3 book.slug %}">MP3</a>{% if have_oggs %},
+            <a href="{% url download_zip_ogg book.slug %}">Ogg Vorbis</a>{% endif %}.
     </p>
 
 
index fce00dc..7c33a37 100644 (file)
             {% for book in object_list %}
                 <li>
                     {% if user_is_owner %}
-                        <a href="{% url remove_from_shelf last_tag.slug book.urlid %}" class="remove-from-shelf">{% trans "Delete" %}</a>
+                        <a href="{% url remove_from_shelf last_tag.slug book.slug %}" class="remove-from-shelf">{% trans "Delete" %}</a>
                     {% endif %}
                     {{ book.short_html }}</li>
             {% endfor %}
index 23e9437..03d1e9d 100644 (file)
@@ -15,7 +15,7 @@
                 {# shelf or global mixing #}
                 <a href='' class='menu-link'>Twórzże się jeszcze raz!</a>
             {% else %}{% if book %} 
-                <a href='{% url poem_from_book book.urlid %}' class='menu-link'>Twórzże się jeszcze raz!</a>
+                <a href='{% url poem_from_book book.slug %}' class='menu-link'>Twórzże się jeszcze raz!</a>
             {% endif %}{% endif %}
             <span style='float: right'>Wolne Lektury przepuszczone przez mikser.</a>
         </div>
@@ -40,7 +40,7 @@
             {% if book %}
                 <p>Tekst powstał przez zmiksowanie utworu
                 <a href="{{ book.get_absolute_url }}">{{ book.title }}</a>.<br/>
-                <a href="{% url poem_from_book book.urlid %}">Zmiksuj go ponownie</a>
+                <a href="{% url poem_from_book book.slug %}">Zmiksuj go ponownie</a>
                 albo <a href="{% url lesmianator %}">zobacz</a>, co jeszcze możesz zamieszać.</p>
             {% else %}{% if books %}
                 <p>Tekst powstał przez zmiksowanie utworów:</p>