changeset tagging in dvcs,
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Wed, 1 Jun 2011 10:03:43 +0000 (12:03 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Wed, 1 Jun 2011 10:03:43 +0000 (12:03 +0200)
document list items caching

12 files changed:
apps/dvcs/admin.py
apps/dvcs/migrations/0002_auto__add_tag.py [new file with mode: 0644]
apps/dvcs/models.py
apps/wiki/forms.py
apps/wiki/migrations/0004_auto__add_field_book__list_html.py [new file with mode: 0644]
apps/wiki/models.py
apps/wiki/templates/wiki/document_list.html
apps/wiki/templates/wiki/document_list_item.html [new file with mode: 0755]
apps/wiki/urls.py
apps/wiki/views.py
redakcja/static/js/wiki/view_history.js
redakcja/static/js/wiki/wikiapi.js

index c81d3b7..984798d 100644 (file)
@@ -1,5 +1,6 @@
 from django.contrib.admin import site
-from dvcs.models import Document, Change
+from dvcs.models import Document, Change, Tag
 
+site.register(Tag)
 site.register(Document)
 site.register(Change)
diff --git a/apps/dvcs/migrations/0002_auto__add_tag.py b/apps/dvcs/migrations/0002_auto__add_tag.py
new file mode 100644 (file)
index 0000000..7f5b9a3
--- /dev/null
@@ -0,0 +1,104 @@
+# 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 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'])
+
+
+    def backwards(self, orm):
+        
+        # Deleting model 'Tag'
+        db.delete_table('dvcs_tag')
+
+        # Removing M2M table for field tags on 'Change'
+        db.delete_table('dvcs_change_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', '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_desc': ('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'}),
+            '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'})
+        },
+        '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']
index 7ac7cbe..6c5796a 100644 (file)
@@ -6,6 +6,41 @@ from django.utils.translation import ugettext_lazy as _
 from mercurial import mdiff, simplemerge
 import pickle
 
+
+class Tag(models.Model):
+    """
+        a tag (e.g. document stage) which can be applied to a change
+    """
+
+    name = models.CharField(_('name'), max_length=64)
+    slug = models.SlugField(_('slug'), unique=True, max_length=64, 
+            null=True, blank=True)
+    ordering = models.IntegerField(_('ordering'))
+
+    _object_cache = {}
+
+    class Meta:
+        ordering = ['ordering']
+
+    def __unicode__(self):
+        return self.name
+
+    @classmethod
+    def get(cls, slug):
+        if slug in cls._object_cache:
+            return cls._object_cache[slug]
+        else:
+            obj = cls.objects.get(slug=slug)
+            cls._object_cache[slug] = obj
+            return obj
+
+    @staticmethod
+    def listener_changed(sender, instance, **kwargs):
+        sender._object_cache = {}
+
+models.signals.pre_save.connect(Tag.listener_changed, sender=Tag)
+
+
 class Change(models.Model):
     """
         Single document change related to previous change. The "parent"
@@ -32,6 +67,8 @@ class Change(models.Model):
     created_at = models.DateTimeField(editable=False, db_index=True, 
                         default=datetime.now)
 
+    tags = models.ManyToManyField(Tag)
+
     class Meta:
         ordering = ('created_at',)
         unique_together = ['tree', 'revision']
@@ -78,18 +115,26 @@ class Change(models.Model):
             text = change.apply_to(text)
         return text
 
-    def make_child(self, patch, description, author=None, author_desc=None):
-        return self.children.create(patch=patch,
+    def make_child(self, patch, description, author=None,
+            author_desc=None, tags=None):
+        ch = self.children.create(patch=patch,
                         tree=self.tree, author=author,
                         author_desc=author_desc,
                         description=description)
+        if tags is not None:
+            ch.tags = tags
+        return ch
 
     def make_merge_child(self, patch, description, author=None, 
-            author_desc=None):
-        return self.merge_children.create(patch=patch,
+            author_desc=None, tags=None):
+        ch = self.merge_children.create(patch=patch,
                         tree=self.tree, author=author,
                         author_desc=author_desc,
-                        description=description)
+                        description=description,
+                        tags=tags)
+        if tags is not None:
+            ch.tags = tags
+        return ch
 
     def apply_to(self, text):
         return mdiff.patch(text, pickle.loads(self.patch.encode('ascii')))
@@ -165,19 +210,22 @@ class Document(models.Model):
 
         author = kwargs.get('author', None)
         author_desc = kwargs.get('author_desc', None)
+        tags = kwargs.get('tags', [])
 
         old_head = self.head
         if parent != old_head:
             change = parent.make_merge_child(patch, author=author, 
                     author_desc=author_desc,
-                    description=kwargs.get('description', ''))
+                    description=kwargs.get('description', ''),
+                    tags=tags)
             # not Fast-Forward - perform a merge
             self.head = old_head.merge_with(change, author=author,
                     author_desc=author_desc)
         else:
             self.head = parent.make_child(patch, author=author, 
                     author_desc=author_desc, 
-                    description=kwargs.get('description', ''))
+                    description=kwargs.get('description', ''),
+                    tags=tags)
 
         self.save()
         return self.head
@@ -196,6 +244,13 @@ class Document(models.Model):
         else:
             return self.head
 
+    def last_tagged(self, tag):
+        changes = tag.change_set.filter(tree=self).order_by('-created_at')[:1]
+        if changes.count():
+            return changes[0]
+        else:
+            return None
+
     @staticmethod
     def listener_initial_commit(sender, instance, created, **kwargs):
         # run for Document and its subclasses
index b057be0..f3362e8 100644 (file)
@@ -4,10 +4,10 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django import forms
-from wiki.constants import DOCUMENT_TAGS, DOCUMENT_STAGES
 from wiki.models import Book
 from django.utils.translation import ugettext_lazy as _
 
+from dvcs.models import Tag
 
 class DocumentTagForm(forms.Form):
     """
@@ -15,7 +15,7 @@ class DocumentTagForm(forms.Form):
     """
 
     id = forms.CharField(widget=forms.HiddenInput)
-    tag = forms.ChoiceField(choices=DOCUMENT_TAGS)
+    tag = forms.ModelChoiceField(queryset=Tag.objects.all())
     revision = forms.IntegerField(widget=forms.HiddenInput)
 
 
@@ -98,8 +98,8 @@ class DocumentTextSaveForm(forms.Form):
         help_text=_(u"Describe changes you made."),
     )
 
-    stage_completed = forms.ChoiceField(
-        choices=DOCUMENT_STAGES,
+    stage_completed = forms.ModelChoiceField(
+        queryset=Tag.objects.all(),
         required=False,
         label=_(u"Completed"),
         help_text=_(u"If you completed a life cycle stage, select it."),
diff --git a/apps/wiki/migrations/0004_auto__add_field_book__list_html.py b/apps/wiki/migrations/0004_auto__add_field_book__list_html.py
new file mode 100644 (file)
index 0000000..a09dc90
--- /dev/null
@@ -0,0 +1,102 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'Book._list_html'
+        db.add_column('wiki_book', '_list_html', self.gf('django.db.models.fields.TextField')(null=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'Book._list_html'
+        db.delete_column('wiki_book', '_list_html')
+
+
+    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_desc': ('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'}),
+            'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            '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'})
+        },
+        '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'}),
+            '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 f8c9d25..6070206 100644 (file)
@@ -8,7 +8,9 @@ import re
 
 from django.core.urlresolvers import reverse
 from django.db import models
+from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext_lazy as _
+from django.template.loader import render_to_string
 
 from dvcs import models as dvcs_models
 
@@ -31,6 +33,11 @@ class Book(models.Model):
     parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children")
     parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True)
 
+    _list_html = models.TextField(editable=False, null=True)
+
+    class NoTextError(BaseException):
+        pass
+
     class Meta:
         ordering = ['parent_number', 'title']
         verbose_name = _('book')
@@ -39,6 +46,11 @@ class Book(models.Model):
     def __unicode__(self):
         return self.title
 
+    def save(self, reset_list_html=True, *args, **kwargs):
+        if reset_list_html:
+            self._list_html = None
+        return super(Book, self).save(*args, **kwargs)
+
     @classmethod
     def create(cls, creator=None, text=u'', *args, **kwargs):
         """
@@ -59,6 +71,14 @@ class Book(models.Model):
     def __len__(self):
         return self.chunk_set.count()
 
+    def list_html(self):
+        if self._list_html is None:
+            print 'rendering', self.title
+            self._list_html = render_to_string('wiki/document_list_item.html',
+                {'book': self})
+            self.save(reset_list_html=False)
+        return mark_safe(self._list_html)
+
     @staticmethod
     def trim(text, trim_begin=True, trim_end=True):
         """ 
@@ -71,7 +91,11 @@ class Book(models.Model):
             text = RE_TRIM_END.split(text, maxsplit=1)[0]
         return text
 
-    def materialize(self):
+    @staticmethod
+    def publish_tag():
+        return dvcs_models.Tag.get('publish')
+
+    def materialize(self, tag=None):
         """ 
             Get full text of the document compiled from chunks.
             Takes the current versions of all texts for now, but it should
@@ -80,10 +104,16 @@ class Book(models.Model):
             First non-empty text's beginning isn't trimmed,
             and last non-empty text's end isn't trimmed.
         """
+        if tag:
+            changes = [chunk.last_tagged(tag) for chunk in self]
+        else:
+            changes = [chunk.head for chunk in self]
+        if None in changes:
+            raise self.NoTextError('Some chunks have no available text.')
         texts = []
         trim_begin = False
         text = ''
-        for chunk in self:
+        for chunk in changes:
             next_text = chunk.materialize()
             if not next_text:
                 continue
@@ -98,6 +128,14 @@ class Book(models.Model):
         texts.append(self.trim(text, trim_begin=trim_begin, trim_end=False))
         return "".join(texts)
 
+    def publishable(self):
+        if not len(self):
+            return False
+        for chunk in self:
+            if not chunk.publishable():
+                return False
+        return True
+
     @staticmethod
     def listener_create(sender, instance, created, **kwargs):
         if created:
@@ -135,55 +173,18 @@ class Chunk(dvcs_models.Document):
         return "%s, %s (%d/%d)" % (self.book.title, self.comment, 
                 self.number, len(self.book))
 
+    def publishable(self):
+        return self.last_tagged(Book.publish_tag())
 
+    @staticmethod
+    def listener_saved(sender, instance, created, **kwargs):
+        if instance.book:
+            # save book so that its _list_html is reset
+            instance.book.save()
 
-
-'''
-from wiki import settings, constants
-from slughifi import slughifi
-
-from django.http import Http404
-
-
-
-
-class Document(object):
-
-    def add_tag(self, tag, revision, author):
-        """ Add document specific tag """
-        logger.debug("Adding tag %s to doc %s version %d", tag, self.name, revision)
-        self.storage.vstorage.add_page_tag(self.name, revision, tag, user=author)
-
-    @property
-    def plain_text(self):
-        return re.sub(self.META_REGEX, '', self.text, 1)
-
-    def meta(self):
-        result = {}
-
-        m = re.match(self.META_REGEX, self.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(self.name.replace(' ', '_')))
-
-        if gallery.startswith('/'):
-            gallery = os.path.basename(gallery)
-
-        result['gallery'] = gallery
-        return result
-
-    def info(self):
-        return self.storage.vstorage.page_meta(self.name, self.revision)
-
+models.signals.post_save.connect(Chunk.listener_saved, sender=Chunk)
 
 
-'''
 class Theme(models.Model):
     name = models.CharField(_('name'), max_length=50, unique=True)
 
index f808a1d..25b4cf2 100644 (file)
@@ -31,28 +31,7 @@ $(function() {
                </thead>
                <tbody>
        {% for book in books %}
-            <tr>
-                <td colspan="3">
-                    <a target="_blank" data-id="{{ book.slug }}"
-                    href="{% url wiki_book book.slug %}">[?]</a>
-                    {% ifequal book.chunk_set.count 1 %}
-                        <a target="_blank" data-id="{{ book.slug }}"
-                                href="{% url wiki_editor book.slug %}">
-                                {{ book.title }}</a>
-                    {% else %}
-                        {{ book.title }}
-                        <div class="chunk-list">
-                        {% for chunk in book %}
-                            <a target="_blank" data-id="{{ book.slug }}"
-                                href="{{ chunk.get_absolute_url }}">
-                                <span class='chunkno'>{{ forloop.counter }}.</span>
-                                {{ chunk.comment }}</a><br/>
-                        {% endfor %}
-                        </div>
-                    {% endifequal %}
-                </td>
-                               <!-- placeholder </td> -->
-                       </tr>
+            {{ book.list_html }}
        {% endfor %}
                </tbody>
     </table>
diff --git a/apps/wiki/templates/wiki/document_list_item.html b/apps/wiki/templates/wiki/document_list_item.html
new file mode 100755 (executable)
index 0000000..f1c4d37
--- /dev/null
@@ -0,0 +1,21 @@
+<tr>
+    <td colspan="3">
+        <a target="_blank" data-id="{{ book.slug }}"
+        href="{% url wiki_book book.slug %}">[?]</a>
+        {% ifequal book.chunk_set.count 1 %}
+            <a target="_blank" data-id="{{ book.slug }}"
+                    href="{% url wiki_editor book.slug %}">
+                    {{ book.title }}</a>
+        {% else %}
+            {{ book.title }}
+            <div class="chunk-list">
+            {% for chunk in book %}
+                <a target="_blank" data-id="{{ book.slug }}"
+                    href="{{ chunk.get_absolute_url }}">
+                    <span class='chunkno'>{{ forloop.counter }}.</span>
+                    {{ chunk.comment }}</a><br/>
+            {% endfor %}
+            </div>
+        {% endifequal %}
+    </td>
+</tr>
index ac731ef..3e5b067 100644 (file)
@@ -47,7 +47,7 @@ urlpatterns = patterns('wiki.views',
     #url(r'^(?P<name>[^/]+)/publish/(?P<version>\d+)$', 'publish', name="wiki_publish"),
 
     url(r'^diff/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$', 'diff', name="wiki_diff"),
-    #url(r'^(?P<name>[^/]+)/tags$', 'add_tag', name="wiki_add_tag"),
+    url(r'^tag/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$', 'add_tag', name="wiki_add_tag"),
 
     url(r'^book/(?P<slug>[^/]+)/$', 'book', name="wiki_book"),
     url(r'^book/(?P<slug>[^/]+)/xml$', 'book_xml', name="wiki_book_xml"),
index 8fa1fc9..6063078 100644 (file)
@@ -17,7 +17,8 @@ from django.shortcuts import get_object_or_404, redirect
 from django.http import Http404
 
 from wiki.models import Book, Chunk, Theme
-from wiki.forms import DocumentTextSaveForm, DocumentTextRevertForm, DocumentTagForm, DocumentCreateForm, DocumentsUploadForm
+from wiki.forms import (DocumentTextSaveForm, DocumentTextRevertForm, DocumentTagForm, DocumentCreateForm, DocumentsUploadForm,
+        ChunkFormSet)
 from datetime import datetime
 from django.utils.encoding import smart_unicode
 from django.utils.translation import ugettext_lazy as _
@@ -213,9 +214,6 @@ def text(request, slug, chunk=None):
     if request.method == 'POST':
         form = DocumentTextSaveForm(request.POST, prefix="textsave")
         if form.is_valid():
-            # TODO:
-            # - stage completion should be stored (as a relation)
-
             if request.user.is_authenticated():
                 author = request.user
             else:
@@ -223,10 +221,13 @@ def text(request, slug, chunk=None):
             text = form.cleaned_data['text']
             parent_revision = form.cleaned_data['parent_revision']
             parent = doc.at_revision(parent_revision)
+            stage = form.cleaned_data['stage_completed']
+            tags = [stage] if stage else []
             doc.commit(author=author,
                        text=text,
                        parent=parent,
                        description=form.cleaned_data['comment'],
+                       tags=tags,
                        )
             revision = doc.revision()
             return JSONResponse({
@@ -253,8 +254,8 @@ def text(request, slug, chunk=None):
 
 @never_cache
 def book_xml(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
-    
+    xml = get_object_or_404(Book, slug=slug).materialize(Book.publish_tag())
+
     response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
     response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
     return response
@@ -262,7 +263,7 @@ def book_xml(request, slug):
 
 @never_cache
 def book_txt(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
+    xml = get_object_or_404(Book, slug=slug).materialize(Book.publish_tag())
     output = StringIO()
     # errors?
     librarian.text.transform(StringIO(xml), output)
@@ -274,7 +275,7 @@ def book_txt(request, slug):
 
 @never_cache
 def book_html(request, slug):
-    xml = get_object_or_404(Book, slug=slug).materialize()
+    xml = get_object_or_404(Book, slug=slug).materialize(Book.publish_tag())
     output = StringIO()
     # errors?
     librarian.html.transform(StringIO(xml), output, parse_dublincore=False,
@@ -390,7 +391,7 @@ def history(request, slug, chunk=None):
                 "description": change.description,
                 "author": change.author_str(),
                 "date": change.created_at,
-                "tag": [],
+                "tag": ',\n'.join(unicode(tag) for tag in change.tags.all()),
             })
     return JSONResponse(changes)
 
@@ -403,28 +404,28 @@ def book(request, slug):
     })
 
 
-
-"""
-import wlapi
-
-
 @require_POST
 @ajax_require_permission('wiki.can_change_tags')
-def add_tag(request, name):
-    name = normalize_name(name)
-    storage = getstorage()
-
+def add_tag(request, slug, chunk=None):
     form = DocumentTagForm(request.POST, prefix="addtag")
     if form.is_valid():
-        doc = storage.get_or_404(form.cleaned_data['id'])
-        doc.add_tag(tag=form.cleaned_data['tag'],
-                    revision=form.cleaned_data['revision'],
-                    author=request.user.username)
+        try:
+            doc = Chunk.get(slug, chunk)
+        except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
+            raise Http404
+
+        tag = form.cleaned_data['tag']
+        revision = revision=form.cleaned_data['revision']
+        doc.at_revision(revision).tags.add(tag)
         return JSONResponse({"message": _("Tag added")})
     else:
         return JSONFormInvalid(form)
 
 
+"""
+import wlapi
+
+
 @require_POST
 @ajax_require_permission('wiki.can_publish')
 def publish(request, name):
index 94a369b..4fe20e2 100644 (file)
                                        stub: $stub,
                                        data: this,
                                        filters: {
-                                               tag: function(value) {
-                                                       return tags.filter("*[value='"+value+"']").text();
-                                               }
+//                                             tag: function(value) {
+//                                                     return tags.filter("*[value='"+value+"']").text();
+//                                             }
 //                        description: function(value) {
 //                                                 return value.replace('\n', ');
 //                                             }
index 5786f15..2f79b09 100644 (file)
@@ -48,7 +48,7 @@
             return base_path + "/rev/" + arguments[1] + '/';
 
                if (vname == "ajax_document_addtag")
-                       return base_path + "/tags/" + arguments[1] + '/';
+                       return base_path + "/tag/" + arguments[1] + '/';
 
                if (vname == "ajax_publish")
                        return base_path + "/publish/" + arguments[1] + '/';