django 1.3, comments on books, last activity log, some minor changes
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Thu, 30 Jun 2011 15:58:46 +0000 (17:58 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Thu, 30 Jun 2011 15:58:46 +0000 (17:58 +0200)
20 files changed:
apps/dvcs/admin.py
apps/dvcs/migrations/0001_initial.py
apps/dvcs/migrations/0002_auto__add_field_document_user__add_field_document_stage.py [deleted file]
apps/dvcs/models.py
apps/wiki/migrations/0003_auto__add_book__add_chunk__add_unique_chunk_book_number__add_unique_ch.py
apps/wiki/models.py
apps/wiki/templates/wiki/book_detail.html
apps/wiki/templates/wiki/document_create_missing.html
apps/wiki/templates/wiki/document_details_base.html
apps/wiki/templates/wiki/document_list.html
apps/wiki/templates/wiki/document_upload.html
apps/wiki/templates/wiki/wall.html [new file with mode: 0755]
apps/wiki/templatetags/wiki.py
redakcja/settings/__init__.py
redakcja/settings/common.py
redakcja/static/css/filelist.css
redakcja/static/js/wiki/view_history.js
redakcja/static/js/wiki/wikiapi.js
redakcja/urls.py
requirements.txt

index 984798d..f6a1ab3 100644 (file)
@@ -1,6 +1,11 @@
-from django.contrib.admin import site
-from dvcs.models import Document, Change, Tag
+from django.contrib import admin
 
-site.register(Tag)
-site.register(Document)
-site.register(Change)
+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)
index 6bff095..bf8d3e2 100644 (file)
@@ -4,7 +4,6 @@ from south.db import db
 from south.v2 import SchemaMigration
 from django.db import models
 
-
 class Migration(SchemaMigration):
 
     def forwards(self, orm):
@@ -22,7 +21,8 @@ class Migration(SchemaMigration):
         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_desc', self.gf('django.db.models.fields.CharField')(max_length=128, 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)),
@@ -48,8 +48,10 @@ class Migration(SchemaMigration):
         # 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')(to=orm['auth.User'], null=True, blank=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'])
 
@@ -84,7 +86,7 @@ class Migration(SchemaMigration):
             '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'},
+            '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'}),
@@ -116,7 +118,8 @@ class Migration(SchemaMigration):
         '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'}),
+            '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'}),
@@ -130,9 +133,11 @@ class Migration(SchemaMigration):
         },
         'dvcs.document': {
             'Meta': {'object_name': 'Document'},
-            'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', '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['dvcs.Change']", 'null': 'True', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'},
diff --git a/apps/dvcs/migrations/0002_auto__add_field_document_user__add_field_document_stage.py b/apps/dvcs/migrations/0002_auto__add_field_document_user__add_field_document_stage.py
deleted file mode 100644 (file)
index 41ab9b2..0000000
+++ /dev/null
@@ -1,96 +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 field 'Document.user'
-        db.add_column('dvcs_document', 'user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True), keep_default=False)
-
-        # Adding field 'Document.stage'
-        db.add_column('dvcs_document', 'stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dvcs.Tag'], null=True), keep_default=False)
-
-
-    def backwards(self, orm):
-        
-        # Deleting field 'Document.user'
-        db.delete_column('dvcs_document', 'user_id')
-
-        # Deleting field 'Document.stage'
-        db.delete_column('dvcs_document', 'stage_id')
-
-
-    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'}),
-            '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'}),
-            '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'})
-        }
-    }
-
-    complete_apps = ['dvcs']
index f81f2ca..86f66bc 100644 (file)
@@ -60,7 +60,8 @@ class Change(models.Model):
         Data contains a pickled diff needed to reproduce the initial document.
     """
     author = models.ForeignKey(User, null=True, blank=True)
-    author_desc = models.CharField(max_length=128, null=True, blank=True)
+    author_name = models.CharField(max_length=128, null=True, blank=True)
+    author_email = models.CharField(max_length=128, null=True, blank=True)
     patch = models.TextField(blank=True)
     tree = models.ForeignKey('Document')
     revision = models.IntegerField(db_index=True)
@@ -94,7 +95,10 @@ class Change(models.Model):
                 self.author.last_name, 
                 self.author.email)
         else:
-            return self.author_desc
+            return "%s <%s>" % (
+                self.author_name,
+                self.author_email
+                )
 
 
     def save(self, *args, **kwargs):
@@ -127,20 +131,22 @@ class Change(models.Model):
         return text
 
     def make_child(self, patch, description, author=None,
-            author_desc=None, tags=None):
+            author_name=None, author_email=None, tags=None):
         ch = self.children.create(patch=patch,
                         tree=self.tree, author=author,
-                        author_desc=author_desc,
+                        author_name=author_name,
+                        author_email=author_email,
                         description=description)
         if tags is not None:
             ch.tags = tags
         return ch
 
     def make_merge_child(self, patch, description, author=None, 
-            author_desc=None, tags=None):
+            author_name=None, author_email=None, tags=None):
         ch = self.merge_children.create(patch=patch,
                         tree=self.tree, author=author,
-                        author_desc=author_desc,
+                        author_name=author_name,
+                        author_email=author_email,
                         description=description,
                         tags=tags)
         if tags is not None:
@@ -150,7 +156,8 @@ class Change(models.Model):
     def apply_to(self, text):
         return mdiff.patch(text, pickle.loads(self.patch.encode('ascii')))
 
-    def merge_with(self, other, author=None, author_desc=None,
+    def merge_with(self, other, author=None, 
+            author_name=None, author_email=None, 
             description=u"Automatic merge."):
         assert self.tree_id == other.tree_id  # same tree
         if other.parent_id == self.pk:
@@ -166,7 +173,9 @@ class Change(models.Model):
         patch = self.make_patch(local, result)
         return self.children.create(
                     patch=patch, merge_parent=other, tree=self.tree,
-                    author=author, author_desc=author_desc,
+                    author=author,
+                    author_name=author_name,
+                    author_email=author_email,
                     description=description)
 
     def revert(self, **kwargs):
@@ -225,7 +234,8 @@ class Document(models.Model):
             patch = kwargs['patch']
 
         author = kwargs.get('author', None)
-        author_desc = kwargs.get('author_desc', None)
+        author_name = kwargs.get('author_name', None)
+        author_email = kwargs.get('author_email', None)
         tags = kwargs.get('tags', [])
         if tags:
             # set stage to next tag after the commited one
@@ -234,15 +244,18 @@ class Document(models.Model):
         old_head = self.head
         if parent != old_head:
             change = parent.make_merge_child(patch, author=author, 
-                    author_desc=author_desc,
+                    author_name=author_name,
+                    author_email=author_email,
                     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)
+                    author_name=author_name,
+                    author_email=author_email)
         else:
             self.head = parent.make_child(patch, author=author, 
-                    author_desc=author_desc, 
+                    author_name=author_name,
+                    author_email=author_email,
                     description=kwargs.get('description', ''),
                     tags=tags)
 
index 3db141a..deb649f 100644 (file)
@@ -15,6 +15,7 @@ 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):
@@ -84,8 +85,7 @@ def migrate_file_from_hg(orm, fname, entry):
     chunk = orm.Chunk.objects.create(
         book=book,
         number=1,
-        slug='1',
-        comment='cz. 1')
+        slug='1')
     head = orm['dvcs.Change'].objects.create(
         tree=chunk,
         revision=-1,
@@ -123,13 +123,27 @@ def migrate_file_from_hg(orm, fname, entry):
 
         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_desc=fctx.user().decode("utf-8", 'replace'),
+            author=author,
+            author_name=author_name,
+            author_email=author_email,
             parent=chunk.head
             )
         head.tags = tags
@@ -252,7 +266,8 @@ class Migration(SchemaMigration):
         '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'}),
+            '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'}),
index 9eb77a5..28fdab8 100644 (file)
@@ -157,7 +157,7 @@ class Chunk(dvcs_models.Document):
     book = models.ForeignKey(Book, editable=False)
     number = models.IntegerField()
     slug = models.SlugField()
-    comment = models.CharField(max_length=255)
+    comment = models.CharField(max_length=255, blank=True)
 
     class Meta:
         unique_together = [['book', 'number'], ['book', 'slug']]
@@ -177,8 +177,13 @@ class Chunk(dvcs_models.Document):
             return cls.objects.get(book__slug=slug, slug=chunk)
 
     def pretty_name(self):
-        return "%s, %s (%d/%d)" % (self.book.title, self.comment, 
-                self.number, len(self.book))
+        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))
+        return title
 
     def split(self, slug, comment='', creator=None):
         """ Create an empty chunk after this one """
index 347b7a3..84d2097 100755 (executable)
@@ -1,5 +1,5 @@
 {% extends "wiki/base.html" %}
-{% load i18n %}
+{% load comments i18n %}
 
 {% block leftcolumn %}
 
@@ -97,6 +97,7 @@
 {% endblock leftcolumn %}
 
 {% block rightcolumn %}
-
+{% render_comment_list for book %}
+{% render_comment_form for book %}
 
 {% endblock rightcolumn %}
index 351e87a..9414f7c 100644 (file)
@@ -3,6 +3,7 @@
 
 {% block leftcolumn %}
        <form enctype="multipart/form-data" method="POST" action="">
+    {% csrf_token %}
        {{ form.as_p }}
 
        <p><button type="submit">{% trans "Create document" %}</button></p>
@@ -10,4 +11,4 @@
 {% endblock leftcolumn %}
 
 {% block rightcolumn %}
-{% endblock rightcolumn %}
\ No newline at end of file
+{% endblock rightcolumn %}
index 3eda939..5f98b73 100644 (file)
@@ -20,6 +20,7 @@
 
        <span data-key="gallery">{{ chunk.book.gallery }}</span>
        <span data-key="revision">{{ revision }}</span>
+    <span data-key="diff">{{ request.GET.diff }}</span>
 
        {% block meta-extra %} {% endblock %}
 </div>
index 9525078..2a0a5ef 100644 (file)
@@ -94,4 +94,7 @@ $(function() {
                        {% endfor %}
                </ol>
        </div>
+
+    <h2>{% trans "Recent activity" %}</h2>
+    {% wall %}
 {% endblock rightcolumn %}
index 929ee4d..f7af2ce 100644 (file)
@@ -12,6 +12,7 @@
 </p>
 
 <form enctype="multipart/form-data" method="POST" action="">
+{% csrf_token %}
 {{ form.as_p }}
 <p><button type="submit">{% trans "Upload" %}</button></p>
 </form>
diff --git a/apps/wiki/templates/wiki/wall.html b/apps/wiki/templates/wiki/wall.html
new file mode 100755 (executable)
index 0000000..ed4685a
--- /dev/null
@@ -0,0 +1,32 @@
+{% load i18n %}
+{% load gravatar %}
+
+<ul class='wall' style='padding-left: 0; list-style: none;'>
+{% for item in wall %}
+    <li style='clear: left; border-top: 1px dotted gray;  padding-bottom:1em; margin-bottom: 1em;'>
+        <div style='float: left;margin-right: 1em;'>
+            {% if item.get_email %}
+                {% gravatar_img_for_email item.get_email 32 %}
+                <br/>
+            {% endif %}
+
+            <img src='{{ STATIC_URL }}img/wall/{{ item.tag}}.png' alt='{% trans item.tag %}' />
+        </div>
+
+        {{ item.timestamp }}
+        <br/>
+        {% if item.user %}
+            <a href="{% url wiki_user item.user.username %}">
+            {{ item.user.first_name }} {{ item.user.last_name }}</a>
+            &lt;{{ item.user.email }}>
+        {% else %}
+            {{ item.user_name }}
+            {% if item.get_email %}
+                &lt;{{ item.get_email }}>
+            {% endif %}
+        {% endif %}
+        <br/><a target="_blank" href='{{ item.url }}'>{{ item.title }}</a>
+        <br/>{{ item.summary }}
+        </li>
+{% endfor %}
+</ul>
index 1fb1a2e..337afa6 100644 (file)
@@ -1,10 +1,13 @@
 from __future__ import absolute_import
 
 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
 
 register = template.Library()
 
@@ -39,3 +42,99 @@ def main_tabs(context):
         tabs.append(Tab('admin', _('Admin'), reverse("admin:index")))
 
     return {"tabs": tabs, "active_tab": active}
+
+
+class WallItem(object):
+    title = ''
+    summary = ''
+    url = ''
+    timestamp = ''
+    user = None
+    email = ''
+
+    def __init__(self, tag):
+        self.tag = tag
+
+    def get_email(self):
+        if self.user:
+            return self.user.email
+        else:
+            return self.email
+
+
+def changes_wall(max_len):
+    qs = Change.objects.filter(revision__gt=-1).order_by('-created_at').select_related()
+    qs = qs[:max_len]
+    for item in qs:
+        tag = 'stage' if item.tags.count() else 'change'
+        chunk = item.tree.chunk
+        w  = WallItem(tag)
+        w.title = chunk.pretty_name()
+        w.summary = item.description
+        w.url = reverse('wiki_editor', 
+                args=[chunk.book.slug, chunk.slug]) + '?diff=%d' % item.revision
+        w.timestamp = item.created_at
+        w.user = item.author
+        w.email = item.author_email
+        yield w
+
+
+def published_wall(max_len):
+    qs = Book.objects.exclude(last_published=None).order_by('-last_published')
+    qs = qs[:max_len]
+    for item in qs:
+        w  = WallItem('publish')
+        w.title = item.title
+        w.summary = item.title
+        w.url = chunk.book.get_absolute_url()
+        w.timestamp = item.last_published
+        w.user = item.last_published_by
+        yield w
+
+
+def comments_wall(max_len):
+    qs = Comment.objects.filter(is_public=True).select_related().order_by('-submit_date')
+    qs = qs[:max_len]
+    for item in qs:
+        w  = WallItem('comment')
+        w.title = item.content_object
+        w.summary = item.comment
+        w.url = item.content_object.get_absolute_url()
+        w.timestamp = item.submit_date
+        w.user = item.user
+        w.email = item.user_email
+        yield w
+
+
+def big_wall(max_len, *args):
+    """
+        Takes some WallItem iterators and zips them into one big wall.
+        Input iterators must already be sorted by timestamp.
+    """
+    subwalls = []
+    for w in args:
+        try:
+            subwalls.append([next(w), w])
+        except StopIteration:
+            pass
+
+    while max_len and subwalls:
+        i, next_item = max(enumerate(subwalls), key=lambda x: x[1][0].timestamp)
+        yield next_item[0]
+        max_len -= 1
+        try:
+            next_item[0] = next(next_item[1])
+        except StopIteration:
+            del subwalls[i]
+
+
+@register.inclusion_tag("wiki/wall.html", takes_context=True)
+def wall(context, max_len=10):
+    return {
+        "request": context['request'],
+        "STATIC_URL": context['STATIC_URL'],
+        "wall": big_wall(max_len,
+            changes_wall(max_len),
+            published_wall(max_len),
+            comments_wall(max_len),
+        )}
index 6f1c094..600533b 100644 (file)
@@ -1,12 +1,17 @@
 from __future__ import absolute_import
+from os import path
 from redakcja.settings.common import *
 
-DATABASE_ENGINE = 'sqlite3'    # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
-DATABASE_NAME = PROJECT_ROOT + '/dev.sqlite'             # Or path to database file if using sqlite3.
-DATABASE_USER = ''             # Not used with sqlite3.
-DATABASE_PASSWORD = ''         # Not used with sqlite3.
-DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
-DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': path.join(PROJECT_ROOT, 'dev.sqlite'), # Or path to database file if using sqlite3.
+        'USER': '',                      # Not used with sqlite3.
+        'PASSWORD': '',                  # Not used with sqlite3.
+        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
+        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
+    }
+}
 
 try:
     LOGGING_CONFIG_FILE
index 36c54b0..f55a877 100644 (file)
@@ -36,6 +36,8 @@ SITE_ID = 1
 # If you set this to False, Django will make some optimizations so as not
 # to load the internationalization machinery.
 USE_I18N = True
+USE_L10N = True
+
 
 # Absolute path to the directory that holds media.
 # Example: "/home/media/media.lawrence.com/"
@@ -69,12 +71,14 @@ TEMPLATE_CONTEXT_PROCESSORS = (
     "django.core.context_processors.debug",
     "django.core.context_processors.i18n",
     "redakcja.context_processors.settings", # this is instead of media
+    'django.core.context_processors.csrf',
     "django.core.context_processors.request",
 )
 
 
 MIDDLEWARE_CLASSES = (
     'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
 
     'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -112,6 +116,7 @@ INSTALLED_APPS = (
     'django.contrib.sites',
     'django.contrib.admin',
     'django.contrib.admindocs',
+    'django.contrib.comments',
 
     'django_cas',
     'compress',
@@ -119,6 +124,7 @@ INSTALLED_APPS = (
     'sorl.thumbnail',
     'filebrowser',
     'pagination',
+    'gravatar',
 
     'dvcs',
     'wiki',
index 4c5ffb3..61e6af2 100644 (file)
@@ -58,7 +58,7 @@ body {
 
 
 
-#file-list {
+#wiki_layout_left_column {
        overflow: visible;
        float: left;
        /*max-width: 50%;*/
@@ -67,7 +67,7 @@ body {
 
 }
 
-#last-edited-list {
+#wiki_layout_right_column {
        float: left;
        max-width: 35%;
        margin-left: 5%;
index fe3af69..bd6b27b 100644 (file)
@@ -5,6 +5,20 @@
 
                options.callback = function() {
                        var self = this;
+            if (CurrentDocument.diff) {
+                rev_from = CurrentDocument.diff[0];
+                rev_to = CurrentDocument.diff[1];
+                this.doc.fetchDiff({
+                    from: rev_from,
+                    to: rev_to,
+                    success: function(doc, data){
+                        var result = $.wiki.newTab(doc, ''+rev_from +' -> ' + rev_to, 'DiffPerspective');
+
+                        $(result.view).html(data);
+                        $.wiki.switchToTab(result.tab);
+                    }
+                });
+            }
 
                        // first time page is rendered
                $('#make-diff-button').click(function() {
index a1f2fb9..cbed73b 100644 (file)
                this.readonly = !!$("*[data-key='readonly']", meta).text();
 
                this.galleryLink = $("*[data-key='gallery']", meta).text();
+
+        var diff = $("*[data-key='diff']", meta).text();
+        diff = diff.split(',');
+        if (diff.length == 2 && diff[0] < diff[1])
+            this.diff = diff;
+        else if (diff.length == 1) {
+            diff = parseInt(diff);
+            if (diff != NaN)
+                this.diff = [diff - 1, diff];
+        }
+
                this.galleryImages = [];
                this.text = null;
                this.has_local_changes = false;
index 8aa569b..3fd6ff5 100644 (file)
@@ -18,6 +18,8 @@ urlpatterns = patterns('',
     url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
     (r'^admin/', include(admin.site.urls)),
 
+    (r'^comments/', include('django.contrib.comments.urls')),
+
     url(r'^$', 'django.views.generic.simple.redirect_to', {'url': '/documents/'}),
     url(r'^documents/', include('wiki.urls')),
     url(r'^storage/', include('dvcs.urls')),
index 6ba7739..c5d6b04 100644 (file)
@@ -10,10 +10,11 @@ httplib2 # oauth2 dependency
 # git+git://github.com/fnp/librarian.git@master#egg=librarian
 
 ## Django
-Django>=1.1.1,<1.2
+Django>=1.3,<1.4
 sorl-thumbnail>=3.2
 django-maintenancemode>=0.9
 django-pagination
+django-gravatar
 
 # migrations
 south>=0.6