make dvcs models abstract,
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Fri, 1 Jul 2011 13:28:22 +0000 (15:28 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Fri, 1 Jul 2011 13:28:59 +0000 (15:28 +0200)
refactor chunk list rendering

25 files changed:
apps/dvcs/admin.py [deleted file]
apps/dvcs/fixtures/stages.json [deleted file]
apps/dvcs/migrations/0001_initial.py [deleted file]
apps/dvcs/migrations/__init__.py [deleted file]
apps/dvcs/models.py
apps/wiki/admin.py
apps/wiki/fixtures/stages.json [new file with mode: 0644]
apps/wiki/forms.py
apps/wiki/helpers.py
apps/wiki/migrations/0003_add_dvcs.py [new file with mode: 0644]
apps/wiki/migrations/0003_auto__add_book__add_chunk__add_unique_chunk_book_number__add_unique_ch.py [deleted file]
apps/wiki/models.py
apps/wiki/templates/wiki/book_append_to.html
apps/wiki/templates/wiki/book_detail.html
apps/wiki/templates/wiki/book_edit.html
apps/wiki/templates/wiki/chunk_add.html
apps/wiki/templates/wiki/chunk_edit.html
apps/wiki/templates/wiki/document_list.html
apps/wiki/templates/wiki/pubmark_dialog.html
apps/wiki/templates/wiki/revert_dialog.html
apps/wiki/templates/wiki/save_dialog.html
apps/wiki/templates/wiki/tag_dialog.html
apps/wiki/templatetags/wiki.py
apps/wiki/views.py
redakcja/settings/common.py

diff --git a/apps/dvcs/admin.py b/apps/dvcs/admin.py
deleted file mode 100644 (file)
index f6a1ab3..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.contrib import admin
-
-from dvcs import models
-
-
-class TagAdmin(admin.ModelAdmin):
-    list_display = ['name', 'slug', 'ordering']
-
-admin.site.register(models.Tag, TagAdmin)
-admin.site.register(models.Document)
-admin.site.register(models.Change)
diff --git a/apps/dvcs/fixtures/stages.json b/apps/dvcs/fixtures/stages.json
deleted file mode 100644 (file)
index fc05d57..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-[
-    {
-        "pk": 1, 
-        "model": "dvcs.tag", 
-        "fields": {
-            "ordering": 1, 
-            "name": "Autokorekta", 
-            "slug": "first_correction"
-        }
-    }, 
-    {
-        "pk": 2, 
-        "model": "dvcs.tag", 
-        "fields": {
-            "ordering": 2, 
-            "name": "Tagowanie", 
-            "slug": "tagging"
-        }
-    }, 
-    {
-        "pk": 3, 
-        "model": "dvcs.tag", 
-        "fields": {
-            "ordering": 3, 
-            "name": "Korekta", 
-            "slug": "proofreading"
-        }
-    }, 
-    {
-        "pk": 4, 
-        "model": "dvcs.tag", 
-        "fields": {
-            "ordering": 4, 
-            "name": "Sprawdzenie przypis\u00f3w \u017ar\u00f3d\u0142a", 
-            "slug": "annotation-proofreading"
-        }
-    }, 
-    {
-        "pk": 5, 
-        "model": "dvcs.tag", 
-        "fields": {
-            "ordering": 5, 
-            "name": "Uwsp\u00f3\u0142cze\u015bnienie", 
-            "slug": "modernisation"
-        }
-    }, 
-    {
-        "pk": 6, 
-        "model": "dvcs.tag", 
-        "fields": {
-            "ordering": 6, 
-            "name": "Przypisy", 
-            "slug": "annotations"
-        }
-    }, 
-    {
-        "pk": 7, 
-        "model": "dvcs.tag", 
-        "fields": {
-            "ordering": 7, 
-            "name": "Motywy", 
-            "slug": "themes"
-        }
-    }, 
-    {
-        "pk": 8, 
-        "model": "dvcs.tag", 
-        "fields": {
-            "ordering": 8, 
-            "name": "Ostateczna redakcja literacka", 
-            "slug": "editor-proofreading"
-        }
-    }, 
-    {
-        "pk": 9, 
-        "model": "dvcs.tag", 
-        "fields": {
-            "ordering": 9, 
-            "name": "Ostateczna redakcja techniczna", 
-            "slug": "technical-editor-proofreading"
-        }
-    }
-]
diff --git a/apps/dvcs/migrations/0001_initial.py b/apps/dvcs/migrations/0001_initial.py
deleted file mode 100644 (file)
index bf8d3e2..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-# encoding: utf-8
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-class Migration(SchemaMigration):
-
-    def forwards(self, orm):
-        
-        # Adding model 'Tag'
-        db.create_table('dvcs_tag', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('name', self.gf('django.db.models.fields.CharField')(max_length=64)),
-            ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=64, unique=True, null=True, blank=True)),
-            ('ordering', self.gf('django.db.models.fields.IntegerField')()),
-        ))
-        db.send_create_signal('dvcs', ['Tag'])
-
-        # Adding model 'Change'
-        db.create_table('dvcs_change', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
-            ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
-            ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
-            ('patch', self.gf('django.db.models.fields.TextField')(blank=True)),
-            ('tree', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dvcs.Document'])),
-            ('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['dvcs.Change'])),
-            ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['dvcs.Change'])),
-            ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
-            ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)),
-            ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)),
-        ))
-        db.send_create_signal('dvcs', ['Change'])
-
-        # Adding unique constraint on 'Change', fields ['tree', 'revision']
-        db.create_unique('dvcs_change', ['tree_id', 'revision'])
-
-        # Adding M2M table for field tags on 'Change'
-        db.create_table('dvcs_change_tags', (
-            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
-            ('change', models.ForeignKey(orm['dvcs.change'], null=False)),
-            ('tag', models.ForeignKey(orm['dvcs.tag'], null=False))
-        ))
-        db.create_unique('dvcs_change_tags', ['change_id', 'tag_id'])
-
-        # Adding model 'Document'
-        db.create_table('dvcs_document', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_documents', null=True, to=orm['auth.User'])),
-            ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['dvcs.Change'], null=True, blank=True)),
-            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
-            ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dvcs.Tag'], null=True, blank=True)),
-        ))
-        db.send_create_signal('dvcs', ['Document'])
-
-        if not db.dry_run:
-            from django.core.management import call_command
-            call_command("loaddata", "stages.json")
-
-
-    def backwards(self, orm):
-        
-        # Removing unique constraint on 'Change', fields ['tree', 'revision']
-        db.delete_unique('dvcs_change', ['tree_id', 'revision'])
-
-        # Deleting model 'Tag'
-        db.delete_table('dvcs_tag')
-
-        # Deleting model 'Change'
-        db.delete_table('dvcs_change')
-
-        # Removing M2M table for field tags on 'Change'
-        db.delete_table('dvcs_change_tags')
-
-        # Deleting model 'Document'
-        db.delete_table('dvcs_document')
-
-
-    models = {
-        'auth.group': {
-            'Meta': {'object_name': 'Group'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
-            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
-        },
-        'auth.permission': {
-            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
-            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
-        },
-        'auth.user': {
-            'Meta': {'object_name': 'User'},
-            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
-            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
-            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
-            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
-            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
-            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
-            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
-            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
-        },
-        'contenttypes.contenttype': {
-            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
-            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
-        },
-        'dvcs.change': {
-            'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'Change'},
-            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
-            'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
-            'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
-            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
-            'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['dvcs.Change']"}),
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['dvcs.Change']"}),
-            'patch': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
-            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dvcs.Tag']", 'symmetrical': 'False'}),
-            'tree': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dvcs.Document']"})
-        },
-        'dvcs.document': {
-            'Meta': {'object_name': 'Document'},
-            '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['dvcs.Change']", 'null': 'True', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dvcs.Tag']", 'null': 'True', 'blank': 'True'}),
-            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
-        },
-        'dvcs.tag': {
-            'Meta': {'ordering': "['ordering']", 'object_name': 'Tag'},
-            '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'})
-        }
-    }
-
-    complete_apps = ['dvcs']
diff --git a/apps/dvcs/migrations/__init__.py b/apps/dvcs/migrations/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
index 86f66bc..5449240 100644 (file)
@@ -1,6 +1,7 @@
 from datetime import datetime
 
 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
@@ -20,6 +21,7 @@ class Tag(models.Model):
     _object_cache = {}
 
     class Meta:
+        abstract = True
         ordering = ['ordering']
 
     def __unicode__(self):
@@ -63,7 +65,6 @@ class Change(models.Model):
     author_name = models.CharField(max_length=128, null=True, blank=True)
     author_email = models.CharField(max_length=128, null=True, blank=True)
     patch = models.TextField(blank=True)
-    tree = models.ForeignKey('Document')
     revision = models.IntegerField(db_index=True)
 
     parent = models.ForeignKey('self',
@@ -79,9 +80,8 @@ class Change(models.Model):
                         default=datetime.now)
     publishable = models.BooleanField(default=False)
 
-    tags = models.ManyToManyField(Tag)
-
     class Meta:
+        abstract = True
         ordering = ('created_at',)
         unique_together = ['tree', 'revision']
 
@@ -122,8 +122,7 @@ class Change(models.Model):
         if self.parent is None and self.merge_parent is not None:
             return self.apply_to(self.merge_parent.materialize())
 
-        changes = Change.objects.exclude(parent=None).filter(
-                        tree=self.tree,
+        changes = self.tree.change_set.exclude(parent=None).filter(
                         revision__lte=self.revision).order_by('revision')
         text = u''
         for change in changes:
@@ -183,19 +182,60 @@ class Change(models.Model):
         self.tree.commit(text=self.materialize(), **kwargs)
 
 
+def create_tag_model(model):
+    name = model.__name__ + 'Tag'
+    attrs = {
+        '__module__': model.__module__,
+    }
+    return type(name, (Tag,), attrs)
+
+
+def create_change_model(model):
+    name = model.__name__ + 'Change'
+
+    attrs = {
+        '__module__': model.__module__,
+        'tree': models.ForeignKey(model, related_name='change_set'),
+        'tags': models.ManyToManyField(model.tag_model, related_name='change_set'),
+    }
+    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, 
+                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,
+                    help_text=_("This document's current head."),
+                    editable=False).contribute_to_class(model, 'head')
+
+        return model
+
+
+
 class Document(models.Model):
     """
         File in repository.        
     """
+    __metaclass__ = DocumentMeta
+
     creator = models.ForeignKey(User, null=True, blank=True, editable=False,
                 related_name="created_documents")
-    head = models.ForeignKey(Change,
-                    null=True, blank=True, default=None,
-                    help_text=_("This document's current head."),
-                    editable=False)
 
     user = models.ForeignKey(User, null=True, blank=True)
-    stage = models.ForeignKey(Tag, null=True, blank=True)
+
+    class Meta:
+        abstract = True
 
     def __unicode__(self):
         return u"{0}, HEAD: {1}".format(self.id, self.head_id)
@@ -222,7 +262,7 @@ class Document(models.Model):
         else:
             parent = kwargs['parent']
             if not isinstance(parent, Change):
-                parent = Change.objects.get(pk=kwargs['parent'])
+                parent = self.change_set.objects.get(pk=kwargs['parent'])
 
         if 'patch' not in kwargs:
             if 'text' not in kwargs:
@@ -289,7 +329,7 @@ class Document(models.Model):
         if not isinstance(instance, Document):
             return
         if created:
-            instance.head = Change.objects.create(
+            instance.head = instance.change_model.objects.create(
                     revision=-1,
                     author=instance.creator,
                     patch=Change.make_patch('', ''),
index 9725d0b..b742f70 100644 (file)
@@ -9,3 +9,5 @@ class BookAdmin(admin.ModelAdmin):
 admin.site.register(models.Book, BookAdmin)
 admin.site.register(models.Chunk)
 admin.site.register(models.Theme)
+
+admin.site.register(models.Chunk.tag_model)
diff --git a/apps/wiki/fixtures/stages.json b/apps/wiki/fixtures/stages.json
new file mode 100644 (file)
index 0000000..992ebc7
--- /dev/null
@@ -0,0 +1,83 @@
+[
+    {
+        "pk": 1, 
+        "model": "wiki.chunktag", 
+        "fields": {
+            "ordering": 1, 
+            "name": "Autokorekta", 
+            "slug": "first_correction"
+        }
+    }, 
+    {
+        "pk": 2, 
+        "model": "wiki.chunktag", 
+        "fields": {
+            "ordering": 2, 
+            "name": "Tagowanie", 
+            "slug": "tagging"
+        }
+    }, 
+    {
+        "pk": 3, 
+        "model": "wiki.chunktag", 
+        "fields": {
+            "ordering": 3, 
+            "name": "Korekta", 
+            "slug": "proofreading"
+        }
+    }, 
+    {
+        "pk": 4, 
+        "model": "wiki.chunktag", 
+        "fields": {
+            "ordering": 4, 
+            "name": "Sprawdzenie przypis\u00f3w \u017ar\u00f3d\u0142a", 
+            "slug": "annotation-proofreading"
+        }
+    }, 
+    {
+        "pk": 5, 
+        "model": "wiki.chunktag", 
+        "fields": {
+            "ordering": 5, 
+            "name": "Uwsp\u00f3\u0142cze\u015bnienie", 
+            "slug": "modernisation"
+        }
+    }, 
+    {
+        "pk": 6, 
+        "model": "wiki.chunktag", 
+        "fields": {
+            "ordering": 6, 
+            "name": "Przypisy", 
+            "slug": "annotations"
+        }
+    }, 
+    {
+        "pk": 7, 
+        "model": "wiki.chunktag", 
+        "fields": {
+            "ordering": 7, 
+            "name": "Motywy", 
+            "slug": "themes"
+        }
+    }, 
+    {
+        "pk": 8, 
+        "model": "wiki.chunktag", 
+        "fields": {
+            "ordering": 8, 
+            "name": "Ostateczna redakcja literacka", 
+            "slug": "editor-proofreading"
+        }
+    }, 
+    {
+        "pk": 9, 
+        "model": "wiki.chunktag", 
+        "fields": {
+            "ordering": 9, 
+            "name": "Ostateczna redakcja techniczna", 
+            "slug": "technical-editor-proofreading"
+        }
+    }
+]
index e153f7e..6b10909 100644 (file)
@@ -8,7 +8,6 @@ from django.db.models import Count
 from django import forms
 from django.utils.translation import ugettext_lazy as _
 
-from dvcs.models import Tag
 from wiki.constants import MASTERS
 from wiki.models import Book, Chunk
 
@@ -18,7 +17,7 @@ class DocumentTagForm(forms.Form):
     """
 
     id = forms.CharField(widget=forms.HiddenInput)
-    tag = forms.ModelChoiceField(queryset=Tag.objects.all())
+    tag = forms.ModelChoiceField(queryset=Chunk.tag_model.objects.all())
     revision = forms.IntegerField(widget=forms.HiddenInput)
 
 
@@ -114,7 +113,7 @@ class DocumentTextSaveForm(forms.Form):
     )
 
     stage_completed = forms.ModelChoiceField(
-        queryset=Tag.objects.all(),
+        queryset=Chunk.tag_model.objects.all(),
         required=False,
         label=_(u"Completed"),
         help_text=_(u"If you completed a life cycle stage, select it."),
@@ -157,7 +156,7 @@ class ChunkForm(forms.ModelForm):
         Form used for editing a chunk.
     """
     user = forms.ModelChoiceField(queryset=
-        User.objects.annotate(count=Count('document')).
+        User.objects.annotate(count=Count('chunk')).
         order_by('-count', 'last_name', 'first_name'))
 
 
index fe4b3b8..253f129 100644 (file)
@@ -1,4 +1,5 @@
 from django import http
+from django.db.models import Count
 from django.utils import simplejson as json
 from django.utils.functional import Promise
 from datetime import datetime
@@ -144,27 +145,50 @@ def active_tab(tab):
     return wrapper
 
 
-class BookChunks(object):
-    """
-        Yields the chunks of a book.
-    """
+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')
 
-    def __init__(self, book):
-        self.book = book
+        self.book_ids = [x['book_id'] for x in 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 len(self.book_ids)
+
+    def get_slice(self, slice_):
+        book_ids = self.book_ids[slice_]
+        chunk_qs = self.chunk_qs.filter(book__in=book_ids)
 
-    @property
-    def chunks(self):
-        return self.book.chunk_set.all()
+        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(BookChunks):
+class ChoiceChunks(object):
     """
         Associates the given chunks iterable for a book.
     """
 
     chunks = None
 
-    def __init__(self, book, chunks):
+    def __init__(self, book, chunks, book_length):
         self.book = book
         self.chunks = chunks
+        self.book_length = book_length
 
diff --git a/apps/wiki/migrations/0003_add_dvcs.py b/apps/wiki/migrations/0003_add_dvcs.py
new file mode 100644 (file)
index 0000000..a301abf
--- /dev/null
@@ -0,0 +1,371 @@
+# encoding: utf-8
+import datetime
+import os.path
+import cPickle
+import re
+import urllib
+
+from django.conf import settings
+from django.db import models
+from mercurial import mdiff, hg, ui
+from south.db import db
+from south.v2 import SchemaMigration
+
+from slughifi import slughifi
+
+META_REGEX = re.compile(r'\s*<!--\s(.*?)-->', re.DOTALL | re.MULTILINE)
+STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE)
+AUTHOR_RE = re.compile(r'\s*(.*?)\s*<(.*)>\s*')
+
+
+def urlunquote(url):
+    """Unqotes URL
+
+    # >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84')
+    # u'Za\u017c\xf3\u0142\u0107_g\u0119\u015bl\u0105 ja\u017a\u0144'
+    """
+    return unicode(urllib.unquote(url), 'utf-8', 'ignore')
+
+
+def split_name(name):
+    parts = name.split('__')
+    return parts
+
+
+def file_to_title(fname):
+    """ Returns a title-like version of a filename. """
+    parts = (p.replace('_', ' ').title() for p in fname.split('__'))
+    return ' / '.join(parts)
+
+
+def make_patch(src, dst):
+    if isinstance(src, unicode):
+        src = src.encode('utf-8')
+    if isinstance(dst, unicode):
+        dst = dst.encode('utf-8')
+    return cPickle.dumps(mdiff.textdiff(src, dst))
+
+
+def plain_text(text):
+    return re.sub(META_REGEX, '', text, 1)
+
+
+def gallery(slug, text):
+    result = {}
+
+    m = re.match(META_REGEX, text)
+    if m:
+        for line in m.group(1).split('\n'):
+            try:
+                k, v = line.split(':', 1)
+                result[k.strip()] = v.strip()
+            except ValueError:
+                continue
+
+    gallery = result.get('gallery', slughifi(slug))
+
+    if gallery.startswith('/'):
+        gallery = os.path.basename(gallery)
+
+    return gallery
+
+
+def migrate_file_from_hg(orm, fname, entry):
+    fname = urlunquote(fname)
+    print fname
+    if fname.endswith('.xml'):
+        fname = fname[:-4]
+    title = file_to_title(fname)
+    fname = slughifi(fname)
+    # create all the needed objects
+    # what if it already exists?
+    book = orm.Book.objects.create(
+        title=title,
+        slug=fname)
+    chunk = orm.Chunk.objects.create(
+        book=book,
+        number=1,
+        slug='1')
+    head = orm['wiki.ChunkChange'].objects.create(
+        tree=chunk,
+        revision=-1,
+        patch=make_patch('', ''),
+        created_at=datetime.datetime.fromtimestamp(entry.filectx(0).date()[0]),
+        description=''
+        )
+    chunk.head = head
+    try:
+        chunk.stage = orm['wiki.ChunkTag'].objects.order_by('ordering')[0]
+    except IndexError:
+        chunk.stage = None
+    old_data = ''
+
+    maxrev = entry.filerev()
+    gallery_link = None
+    
+    for rev in xrange(maxrev + 1):
+        fctx = entry.filectx(rev)
+        data = fctx.data()
+        gallery_link = gallery(fname, data)
+        data = plain_text(data)
+
+        # get tags from description
+        description = fctx.description().decode("utf-8", 'replace')
+        tags = STAGE_TAGS_RE.findall(description)
+        tags = [orm['wiki.ChunkTag'].objects.get(slug=slug.strip()) for slug in tags]
+
+        if tags:
+            max_ordering = max(tags, key=lambda x: x.ordering).ordering
+            try:
+                chunk.stage = orm['wiki.ChunkTag'].objects.filter(ordering__gt=max_ordering).order_by('ordering')[0]
+            except IndexError:
+                chunk.stage = None
+
+        description = STAGE_TAGS_RE.sub('', description)
+
+        author = author_name = author_email = None
+        author_desc = fctx.user().decode("utf-8", 'replace')
+        m = AUTHOR_RE.match(author_desc)
+        if m:
+            try:
+                author = orm['auth.User'].objects.get(username=m.group(1), email=m.group(2))
+            except orm['auth.User'].DoesNotExist:
+                author_name = m.group(1)
+                author_email = m.group(2)
+        else:
+            author_name = author_desc
+
+        head = orm['wiki.ChunkChange'].objects.create(
+            tree=chunk,
+            revision=rev + 1,
+            patch=make_patch(old_data, data),
+            created_at=datetime.datetime.fromtimestamp(fctx.date()[0]),
+            description=description,
+            author=author,
+            author_name=author_name,
+            author_email=author_email,
+            parent=chunk.head
+            )
+        head.tags = tags
+        chunk.head = head
+        old_data = data
+
+    chunk.save()
+    if gallery_link:
+        book.gallery = gallery_link
+        book.save()
+
+
+def migrate_from_hg(orm):
+    try:
+        hg_path = settings.WIKI_REPOSITORY_PATH
+    except:
+        pass
+
+    print 'migrate from', hg_path
+    repo = hg.repository(ui.ui(), hg_path)
+    tip = repo['tip']
+    for fname in tip:
+        if fname.startswith('.'):
+            continue
+        migrate_file_from_hg(orm, fname, tip[fname])
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding model 'Book'
+        db.create_table('wiki_book', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=128, db_index=True)),
+            ('gallery', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['wiki.Book'])),
+            ('parent_number', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)),
+            ('last_published', self.gf('django.db.models.fields.DateTimeField')(null=True)),
+            ('_list_html', self.gf('django.db.models.fields.TextField')(null=True)),
+        ))
+        db.send_create_signal('wiki', ['Book'])
+
+        # Adding model 'Chunk'
+        db.create_table('wiki_chunk', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_documents', null=True, to=orm['auth.User'])),
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
+            ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.Book'])),
+            ('number', self.gf('django.db.models.fields.IntegerField')()),
+            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)),
+            ('comment', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
+            ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.ChunkTag'], null=True, blank=True)),
+            ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['wiki.ChunkChange'], null=True, blank=True)),
+        ))
+        db.send_create_signal('wiki', ['Chunk'])
+
+        # Adding unique constraint on 'Chunk', fields ['book', 'number']
+        db.create_unique('wiki_chunk', ['book_id', 'number'])
+
+        # Adding unique constraint on 'Chunk', fields ['book', 'slug']
+        db.create_unique('wiki_chunk', ['book_id', 'slug'])
+
+        # Adding model 'ChunkTag'
+        db.create_table('wiki_chunktag', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=64)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=64, unique=True, null=True, blank=True)),
+            ('ordering', self.gf('django.db.models.fields.IntegerField')()),
+        ))
+        db.send_create_signal('wiki', ['ChunkTag'])
+
+        # Adding model 'ChunkChange'
+        db.create_table('wiki_chunkchange', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
+            ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
+            ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
+            ('patch', self.gf('django.db.models.fields.TextField')(blank=True)),
+            ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['wiki.ChunkChange'])),
+            ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['wiki.ChunkChange'])),
+            ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
+            ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)),
+            ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)),
+            ('tree', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.Chunk'])),
+        ))
+        db.send_create_signal('wiki', ['ChunkChange'])
+
+        # Adding unique constraint on 'ChunkChange', fields ['tree', 'revision']
+        db.create_unique('wiki_chunkchange', ['tree_id', 'revision'])
+
+        # Adding M2M table for field tags on 'ChunkChange'
+        db.create_table('wiki_chunkchange_tags', (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('chunkchange', models.ForeignKey(orm['wiki.chunkchange'], null=False)),
+            ('chunktag', models.ForeignKey(orm['wiki.chunktag'], null=False))
+        ))
+        db.create_unique('wiki_chunkchange_tags', ['chunkchange_id', 'chunktag_id'])
+
+        if not db.dry_run:
+            from django.core.management import call_command
+            call_command("loaddata", "stages.json")
+
+            migrate_from_hg(orm)
+
+    def backwards(self, orm):
+        
+        # Removing unique constraint on 'ChunkChange', fields ['tree', 'revision']
+        db.delete_unique('wiki_chunkchange', ['tree_id', 'revision'])
+
+        # Removing unique constraint on 'Chunk', fields ['book', 'slug']
+        db.delete_unique('wiki_chunk', ['book_id', 'slug'])
+
+        # Removing unique constraint on 'Chunk', fields ['book', 'number']
+        db.delete_unique('wiki_chunk', ['book_id', 'number'])
+
+        # Deleting model 'Book'
+        db.delete_table('wiki_book')
+
+        # Deleting model 'Chunk'
+        db.delete_table('wiki_chunk')
+
+        # Deleting model 'ChunkTag'
+        db.delete_table('wiki_chunktag')
+
+        # Deleting model 'ChunkChange'
+        db.delete_table('wiki_chunkchange')
+
+        # Removing M2M table for field tags on 'ChunkChange'
+        db.delete_table('wiki_chunkchange_tags')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'wiki.book': {
+            'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'},
+            '_list_html': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_published': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.Book']"}),
+            'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'wiki.chunk': {
+            'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
+            'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Book']"}),
+            'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}),
+            'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['wiki.ChunkChange']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'number': ('django.db.models.fields.IntegerField', [], {}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
+            'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ChunkTag']", 'null': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'wiki.chunkchange': {
+            'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['wiki.ChunkChange']"}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['wiki.ChunkChange']"}),
+            'patch': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['wiki.ChunkTag']", 'symmetrical': 'False'}),
+            'tree': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Chunk']"})
+        },
+        'wiki.chunktag': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {}),
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        'wiki.theme': {
+            'Meta': {'ordering': "('name',)", 'object_name': 'Theme'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'})
+        }
+    }
+
+    complete_apps = ['wiki']
diff --git a/apps/wiki/migrations/0003_auto__add_book__add_chunk__add_unique_chunk_book_number__add_unique_ch.py b/apps/wiki/migrations/0003_auto__add_book__add_chunk__add_unique_chunk_book_number__add_unique_ch.py
deleted file mode 100644 (file)
index deb649f..0000000
+++ /dev/null
@@ -1,323 +0,0 @@
-# encoding: utf-8
-import datetime
-import os.path
-import cPickle
-import re
-import urllib
-
-from django.conf import settings
-from django.db import models
-from mercurial import mdiff, hg, ui
-from south.db import db
-from south.v2 import SchemaMigration
-
-from slughifi import slughifi
-
-META_REGEX = re.compile(r'\s*<!--\s(.*?)-->', re.DOTALL | re.MULTILINE)
-STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE)
-AUTHOR_RE = re.compile(r'\s*(.*?)\s*<(.*)>\s*')
-
-
-def urlunquote(url):
-    """Unqotes URL
-
-    # >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84')
-    # u'Za\u017c\xf3\u0142\u0107_g\u0119\u015bl\u0105 ja\u017a\u0144'
-    """
-    return unicode(urllib.unquote(url), 'utf-8', 'ignore')
-
-
-def split_name(name):
-    parts = name.split('__')
-    return parts
-
-
-def file_to_title(fname):
-    """ Returns a title-like version of a filename. """
-    parts = (p.replace('_', ' ').title() for p in fname.split('__'))
-    return ' / '.join(parts)
-
-
-def make_patch(src, dst):
-    if isinstance(src, unicode):
-        src = src.encode('utf-8')
-    if isinstance(dst, unicode):
-        dst = dst.encode('utf-8')
-    return cPickle.dumps(mdiff.textdiff(src, dst))
-
-
-def plain_text(text):
-    return re.sub(META_REGEX, '', text, 1)
-
-
-def gallery(slug, text):
-    result = {}
-
-    m = re.match(META_REGEX, text)
-    if m:
-        for line in m.group(1).split('\n'):
-            try:
-                k, v = line.split(':', 1)
-                result[k.strip()] = v.strip()
-            except ValueError:
-                continue
-
-    gallery = result.get('gallery', slughifi(slug))
-
-    if gallery.startswith('/'):
-        gallery = os.path.basename(gallery)
-
-    return gallery
-
-
-def migrate_file_from_hg(orm, fname, entry):
-    fname = urlunquote(fname)
-    print fname
-    if fname.endswith('.xml'):
-        fname = fname[:-4]
-    title = file_to_title(fname)
-    fname = slughifi(fname)
-    # create all the needed objects
-    # what if it already exists?
-    book = orm.Book.objects.create(
-        title=title,
-        slug=fname)
-    chunk = orm.Chunk.objects.create(
-        book=book,
-        number=1,
-        slug='1')
-    head = orm['dvcs.Change'].objects.create(
-        tree=chunk,
-        revision=-1,
-        patch=make_patch('', ''),
-        created_at=datetime.datetime.fromtimestamp(entry.filectx(0).date()[0]),
-        description=''
-        )
-    chunk.head = head
-    try:
-        chunk.stage = orm['dvcs.Tag'].objects.order_by('ordering')[0]
-    except IndexError:
-        chunk.stage = None
-    old_data = ''
-
-    maxrev = entry.filerev()
-    gallery_link = None
-    
-    for rev in xrange(maxrev + 1):
-        fctx = entry.filectx(rev)
-        data = fctx.data()
-        gallery_link = gallery(fname, data)
-        data = plain_text(data)
-
-        # get tags from description
-        description = fctx.description().decode("utf-8", 'replace')
-        tags = STAGE_TAGS_RE.findall(description)
-        tags = [orm['dvcs.Tag'].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['dvcs.Tag'].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['dvcs.Change'].objects.create(
-            tree=chunk,
-            revision=rev + 1,
-            patch=make_patch(old_data, data),
-            created_at=datetime.datetime.fromtimestamp(fctx.date()[0]),
-            description=description,
-            author=author,
-            author_name=author_name,
-            author_email=author_email,
-            parent=chunk.head
-            )
-        head.tags = tags
-        chunk.head = head
-        old_data = data
-
-    chunk.save()
-    if gallery_link:
-        book.gallery = gallery_link
-        book.save()
-
-
-def migrate_from_hg(orm):
-    try:
-        hg_path = settings.WIKI_REPOSITORY_PATH
-    except:
-        pass
-
-    print 'migrate from', hg_path
-    repo = hg.repository(ui.ui(), hg_path)
-    tip = repo['tip']
-    for fname in tip:
-        if fname.startswith('.'):
-            continue
-        migrate_file_from_hg(orm, fname, tip[fname])
-
-
-class Migration(SchemaMigration):
-
-    depends_on = [
-        ('dvcs', '0001_initial'),
-    ]
-
-    def forwards(self, orm):
-        
-        # Adding model 'Book'
-        db.create_table('wiki_book', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('title', self.gf('django.db.models.fields.CharField')(max_length=255)),
-            ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=128, db_index=True)),
-            ('gallery', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
-            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['wiki.Book'])),
-            ('parent_number', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)),
-            ('last_published', self.gf('django.db.models.fields.DateTimeField')(null=True)),
-            ('_list_html', self.gf('django.db.models.fields.TextField')(null=True)),
-        ))
-        db.send_create_signal('wiki', ['Book'])
-
-        # Adding model 'Chunk'
-        db.create_table('wiki_chunk', (
-            ('document_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['dvcs.Document'], unique=True, primary_key=True)),
-            ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.Book'])),
-            ('number', self.gf('django.db.models.fields.IntegerField')()),
-            ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)),
-            ('comment', self.gf('django.db.models.fields.CharField')(max_length=255)),
-        ))
-        db.send_create_signal('wiki', ['Chunk'])
-
-        # Adding unique constraint on 'Chunk', fields ['book', 'number']
-        db.create_unique('wiki_chunk', ['book_id', 'number'])
-
-        # Adding unique constraint on 'Chunk', fields ['book', 'slug']
-        db.create_unique('wiki_chunk', ['book_id', 'slug'])
-
-        if not db.dry_run:
-            migrate_from_hg(orm)
-
-    def backwards(self, orm):
-        
-        # Removing unique constraint on 'Chunk', fields ['book', 'slug']
-        db.delete_unique('wiki_chunk', ['book_id', 'slug'])
-
-        # Removing unique constraint on 'Chunk', fields ['book', 'number']
-        db.delete_unique('wiki_chunk', ['book_id', 'number'])
-
-        # Deleting model 'Book'
-        db.delete_table('wiki_book')
-
-        # Deleting model 'Chunk'
-        db.delete_table('wiki_chunk')
-
-
-    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', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
-            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
-        },
-        'auth.user': {
-            'Meta': {'object_name': 'User'},
-            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
-            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
-            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
-            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
-            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
-            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
-            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
-            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
-            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
-        },
-        'contenttypes.contenttype': {
-            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
-            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
-        },
-        'dvcs.change': {
-            'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'Change'},
-            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
-            'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
-            'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
-            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
-            'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['dvcs.Change']"}),
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['dvcs.Change']"}),
-            'patch': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
-            'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
-            'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
-            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dvcs.Tag']", 'symmetrical': 'False'}),
-            'tree': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dvcs.Document']"})
-        },
-        'dvcs.document': {
-            'Meta': {'object_name': 'Document'},
-            'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
-            'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['dvcs.Change']", 'null': 'True', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dvcs.Tag']", 'null': 'True'}),
-            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
-        },
-        'dvcs.tag': {
-            'Meta': {'ordering': "['ordering']", 'object_name': 'Tag'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
-            'ordering': ('django.db.models.fields.IntegerField', [], {}),
-            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
-        },
-        'wiki.book': {
-            'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'},
-            '_list_html': ('django.db.models.fields.TextField', [], {'null': 'True'}),
-            'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'last_published': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.Book']"}),
-            'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
-            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
-        },
-        'wiki.chunk': {
-            'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk', '_ormbases': ['dvcs.Document']},
-            'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Book']"}),
-            'comment': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
-            'document_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['dvcs.Document']", 'unique': 'True', 'primary_key': 'True'}),
-            'number': ('django.db.models.fields.IntegerField', [], {}),
-            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
-        },
-        'wiki.theme': {
-            'Meta': {'ordering': "('name',)", 'object_name': 'Theme'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'})
-        }
-    }
-
-    complete_apps = ['wiki']
index 28fdab8..d35b3c4 100644 (file)
@@ -176,13 +176,12 @@ class Chunk(dvcs_models.Document):
         else:
             return cls.objects.get(book__slug=slug, slug=chunk)
 
-    def pretty_name(self):
+    def pretty_name(self, book_length=None):
         title = self.book.title
         if self.comment:
             title += ", %s" % self.comment
-        count = len(self.book)
-        if count > 1:
-            title += " (%d/%d)" % (self.number, len(self.book))
+        if book_length > 1:
+            title += " (%d/%d)" % (self.number, book_length)
         return title
 
     def split(self, slug, comment='', creator=None):
index 0350aa6..da6594d 100755 (executable)
@@ -3,6 +3,7 @@
 
 {% block leftcolumn %}
        <form enctype="multipart/form-data" method="POST" action="">
+    {% csrf_token %}
        {{ form.as_p }}
 
        <p><button type="submit">{% trans "Append book" %}</button></p>
index 84d2097..5ea0e3d 100755 (executable)
@@ -54,6 +54,7 @@
     {% if need_fixing %}
         <tr><td></td><td>
             <form method="POST" action="">
+                {% csrf_token %}
                 {% if choose_master %}
                     {{ form.master }}
                 {% endif %}
@@ -89,7 +90,7 @@
         <button id="publish-button" type="submit">
             <span>{% trans "Publish" %}</span></button>
         <img src="{{ STATIC_URL }}img/angel-right.png" style="vertical-align: middle" />
-        </form></form></p>
+        </form></p>
 {% else %}
     {% trans "This book cannot be published yet" %}
 {% endif %}
index d5f527d..8bc7ea7 100755 (executable)
@@ -3,6 +3,7 @@
 
 {% block leftcolumn %}
        <form enctype="multipart/form-data" method="POST" action="">
+    {% csrf_token %}
        {{ form.as_p }}
 
        <p><button type="submit">{% trans "Save" %}</button></p>
index 2b8939e..836c34d 100755 (executable)
@@ -3,6 +3,7 @@
 
 {% block leftcolumn %}
        <form enctype="multipart/form-data" method="POST" action="">
+    {% csrf_token %}
        {{ form.as_p }}
 
        <p><button type="submit">{% trans "Add chunk" %}</button></p>
index d5f527d..8bc7ea7 100755 (executable)
@@ -3,6 +3,7 @@
 
 {% block leftcolumn %}
        <form enctype="multipart/form-data" method="POST" action="">
+    {% csrf_token %}
        {{ form.as_p }}
 
        <p><button type="submit">{% trans "Save" %}</button></p>
index 2a0a5ef..434324c 100644 (file)
@@ -47,17 +47,19 @@ $(function() {
         {% endif %}
        {% for item in books %}
             {% with item.book as book %}
-            
-            {% ifequal book.chunk_set.count 1 %}
+
+            {% ifequal item.book_length 1 %}
+                {% with item.chunks.0 as chunk %}
                 <tr>
                     <td><a target="_blank" href="{% url wiki_book book.slug %}">[B]</a></td>
-                    <td><a href="{% url wiki_chunk_edit book.slug book.0.slug %}">[c]</a></td>
+                    <td><a href="{% url wiki_chunk_edit book.slug chunk.slug %}">[c]</a></td>
                     <td><a target="_blank"
                                 href="{% url wiki_editor book.slug %}">
                                 {{ book.title }}</a></td>
-                    <td>({{ book.0.stage }})</td>
-                    <td>{% if book.0.user %}<a href="{% url wiki_user book.0.user.username %}">{{ book.0.user.first_name }} {{ book.0.user.last_name }}</a>{% endif %}</td>
+                    <td>({{ chunk.stage }})</td>
+                    <td>{% if chunk.user %}<a href="{% url wiki_user chunk.user.username %}">{{ chunk.user.first_name }} {{ chunk.user.last_name }}</a>{% endif %}</td>
                 </tr>
+                {% endwith %}
             {% else %}
                 <tr>
                     <td><a target="_blank" href="{% url wiki_book book.slug %}">[B]</a></td>
index 93a9856..a70a0c3 100755 (executable)
@@ -1,6 +1,7 @@
 {% load i18n %}
 <div id="pubmark_dialog" class="dialog" data-ui-jsclass="PubmarkDialog">
        <form method="POST" action="#">
+    {% csrf_token %}
                {% for field in forms.pubmark.visible_fields %}
                <p>{{ field.label_tag }} {{ field }} <span data-ui-error-for="{{ field.name }}"> </span></p>
                <p>{{ field.help_text }}</p>
index 7e5089f..c2fc155 100644 (file)
@@ -1,6 +1,7 @@
 {% load i18n %}
 <div id="revert_dialog" class="dialog" data-ui-jsclass="RevertDialog">
        <form method="POST" action="">
+    {% csrf_token %}
        <p>{{ forms.text_revert.comment.label }}</p>
        <p class="help_text">
                {{ forms.text_revert.comment.help_text}}
index b2f53ba..c34eb9e 100644 (file)
@@ -1,6 +1,7 @@
 {% load i18n %}
 <div id="save_dialog" class="dialog" data-ui-jsclass="SaveDialog">
        <form method="POST" action="">
+    {% csrf_token %}
        <p>{{ forms.text_save.comment.label }}</p>
        <p class="help_text">
                {{ forms.text_save.comment.help_text}}
index bc601cb..aa4b242 100644 (file)
@@ -1,6 +1,7 @@
 {% load i18n %}
 <div id="add_tag_dialog" class="dialog" data-ui-jsclass="AddTagDialog">
        <form method="POST" action="#">
+    {% csrf_token %}
                {% for field in forms.add_tag.visible_fields %}
                <p>{{ field.label_tag }} {{ field }} <span data-ui-error-for="{{ field.name }}"> </span></p>
                <p>{{ field.help_text }}</p>
index 337afa6..db2eca3 100644 (file)
@@ -1,13 +1,13 @@
 from __future__ import absolute_import
 
+from django.db.models import Count
 from django.core.urlresolvers import reverse
 from django.contrib.comments.models import Comment
 from django.template.defaultfilters import stringfilter
 from django import template
 from django.utils.translation import ugettext as _
 
-from wiki.models import Book
-from dvcs.models import Change
+from wiki.models import Book, Chunk
 
 register = template.Library()
 
@@ -63,11 +63,14 @@ class WallItem(object):
 
 
 def changes_wall(max_len):
-    qs = Change.objects.filter(revision__gt=-1).order_by('-created_at').select_related()
+    qs = Chunk.change_model.objects.filter(revision__gt=-1).order_by('-created_at')
+    qs = qs.defer('patch')
+    qs = qs.select_related('author', 'tree')
+    #qs = qs.annotate(book_length=Count('chunk__book__chunk'))
     qs = qs[:max_len]
     for item in qs:
         tag = 'stage' if item.tags.count() else 'change'
-        chunk = item.tree.chunk
+        chunk = item.tree
         w  = WallItem(tag)
         w.title = chunk.pretty_name()
         w.summary = item.description
index 7bff1a0..59ba984 100644 (file)
@@ -47,8 +47,12 @@ MAX_LAST_DOCS = 10
 @active_tab('all')
 @never_cache
 def document_list(request):
+    chunks_list = helpers.ChunksList(Chunk.objects.order_by(
+        'book__title', 'book', 'number'))
+
     return direct_to_template(request, 'wiki/document_list.html', extra_context={
-        'books': [helpers.BookChunks(b) for b in Book.objects.all()],
+        'books': chunks_list,
+        #'books': [helpers.BookChunks(b) for b in Book.objects.all().select_related()],
         'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
                         key=lambda x: x[1]['time'], reverse=True),
     })
@@ -57,18 +61,11 @@ def document_list(request):
 @active_tab('unassigned')
 @never_cache
 def unassigned(request):
-    chunks = Chunk.objects.filter(user=None).order_by('book__title', 'book', 'number')
-    books = []
-    book = None
-    for chunk in chunks:
-        if chunk.book != book:
-            book = chunk.book
-            books.append(helpers.ChoiceChunks(book, [chunk]))
-        else:
-            books[-1].chunks.append(chunk)
+    chunks_list = helpers.ChunksList(Chunk.objects.filter(
+        user=None).order_by('book__title', 'book__id', 'number'))
 
     return direct_to_template(request, 'wiki/document_list.html', extra_context={
-        'books': books,
+        'books': chunks_list,
         'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
                         key=lambda x: x[1]['time'], reverse=True),
     })
@@ -84,18 +81,11 @@ def user(request, username=None):
     else:
         user = get_object_or_404(User, username=username)
 
-    chunks = Chunk.objects.filter(user=user).order_by('book__title', 'number')
-    books = []
-    book = None
-    for chunk in chunks:
-        if chunk.book != book:
-            book = chunk.book
-            books.append(helpers.ChoiceChunks(book, [chunk]))
-        else:
-            books[-1].chunks.append(chunk)
+    chunks_list = helpers.ChunksList(Chunk.objects.filter(
+        user=user).order_by('book__title', 'book', 'number'))
 
     return direct_to_template(request, 'wiki/document_list.html', extra_context={
-        'books': books,
+        'books': chunks_list,
         'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
                         key=lambda x: x[1]['time'], reverse=True),
     })
@@ -105,7 +95,7 @@ my = login_required(active_tab('my')(user))
 @active_tab('users')
 def users(request):
     return direct_to_template(request, 'wiki/user_list.html', extra_context={
-        'users': User.objects.all().annotate(count=Count('document')).order_by(
+        'users': User.objects.all().annotate(count=Count('chunk')).order_by(
             '-count', 'last_name', 'first_name'),
     })
 
index f55a877..7c40abe 100644 (file)
@@ -61,13 +61,12 @@ SESSION_COOKIE_NAME = "redakcja_sessionid"
 
 # List of callables that know how to import templates from various sources.
 TEMPLATE_LOADERS = (
-    'django.template.loaders.filesystem.load_template_source',
-    'django.template.loaders.app_directories.load_template_source',
-#     'django.template.loaders.eggs.load_template_source',
+    'django.template.loaders.filesystem.Loader',
+    'django.template.loaders.app_directories.Loader',
 )
 
 TEMPLATE_CONTEXT_PROCESSORS = (
-    "django.core.context_processors.auth",
+    "django.contrib.auth.context_processors.auth",
     "django.core.context_processors.debug",
     "django.core.context_processors.i18n",
     "redakcja.context_processors.settings", # this is instead of media