From: Radek Czajka
Date: Wed, 8 Jun 2011 13:03:59 +0000 (+0200)
Subject: teh legendary `Publish' button, oh yeah!
X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/f4921ca797953ddab6b35786864b2dd523190503?ds=sidebyside;hp=bdabaaf2da173e1778667f69f125925dcb9149df
teh legendary `Publish' button, oh yeah!
authorization to WLAPI through OAuth,
marking revisions for publication
---
diff --git a/apps/apiclient/__init__.py b/apps/apiclient/__init__.py
new file mode 100644
index 00000000..7913ac3c
--- /dev/null
+++ b/apps/apiclient/__init__.py
@@ -0,0 +1,46 @@
+import urllib
+
+from django.utils import simplejson
+import oauth2
+
+from apiclient.models import OAuthConnection
+from apiclient.settings import WL_CONSUMER_KEY, WL_CONSUMER_SECRET, WL_API_URL
+
+
+if WL_CONSUMER_KEY and WL_CONSUMER_SECRET:
+ wl_consumer = oauth2.Consumer(WL_CONSUMER_KEY, WL_CONSUMER_SECRET)
+else:
+ wl_consumer = None
+
+
+class ApiError(BaseException):
+ pass
+
+
+class NotAuthorizedError(BaseException):
+ pass
+
+
+def api_call(user, path, data=None):
+ # what if not verified?
+ conn = OAuthConnection.get(user)
+ if not conn.access:
+ raise NotAuthorizedError("No WL authorization for user %s." % user)
+ token = oauth2.Token(conn.token, conn.token_secret)
+ client = oauth2.Client(wl_consumer, token)
+ if data is not None:
+ resp, content = client.request(
+ "%s%s.json" % (WL_API_URL, path),
+ method="POST",
+ body=urllib.urlencode(data))
+ else:
+ resp, content = client.request(
+ "%s%s.json" % (WL_API_URL, path))
+ status = resp['status']
+ if status == '200':
+ return simplejson.loads(content)
+ elif status.startswith('2'):
+ return
+ else:
+ raise ApiError("WL API call error")
+
diff --git a/apps/apiclient/migrations/0001_initial.py b/apps/apiclient/migrations/0001_initial.py
new file mode 100644
index 00000000..4af28a52
--- /dev/null
+++ b/apps/apiclient/migrations/0001_initial.py
@@ -0,0 +1,75 @@
+# 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 'OAuthConnection'
+ db.create_table('apiclient_oauthconnection', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)),
+ ('access', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('token', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)),
+ ('token_secret', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)),
+ ))
+ db.send_create_signal('apiclient', ['OAuthConnection'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'OAuthConnection'
+ db.delete_table('apiclient_oauthconnection')
+
+
+ models = {
+ 'apiclient.oauthconnection': {
+ 'Meta': {'object_name': 'OAuthConnection'},
+ 'access': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
+ 'token_secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
+ },
+ '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'})
+ }
+ }
+
+ complete_apps = ['apiclient']
diff --git a/apps/apiclient/migrations/__init__.py b/apps/apiclient/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/apps/apiclient/models.py b/apps/apiclient/models.py
new file mode 100644
index 00000000..d3c8f620
--- /dev/null
+++ b/apps/apiclient/models.py
@@ -0,0 +1,20 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+
+class OAuthConnection(models.Model):
+ user = models.OneToOneField(User)
+ access = models.BooleanField(default=False)
+ token = models.CharField(max_length=64, null=True, blank=True)
+ token_secret = models.CharField(max_length=64, null=True, blank=True)
+
+ @classmethod
+ def get(cls, user):
+ try:
+ return cls.objects.get(user=user)
+ except cls.DoesNotExist:
+ o = cls(user=user)
+ o.save()
+ return o
+
+
diff --git a/apps/apiclient/settings.py b/apps/apiclient/settings.py
new file mode 100755
index 00000000..5fbf18ee
--- /dev/null
+++ b/apps/apiclient/settings.py
@@ -0,0 +1,15 @@
+from django.conf import settings
+
+
+WL_CONSUMER_KEY = getattr(settings, 'APICLIENT_WL_CONSUMER_KEY', None)
+WL_CONSUMER_SECRET = getattr(settings, 'APICLIENT_WL_CONSUMER_SECRET', None)
+
+WL_API_URL = getattr(settings, 'APICLIENT_WL_API_URL',
+ 'http://www.wolnelektury.pl/api/')
+
+WL_REQUEST_TOKEN_URL = getattr(settings, 'APICLIENT_WL_REQUEST_TOKEN_URL',
+ WL_API_URL + 'oauth/request_token/')
+WL_ACCESS_TOKEN_URL = getattr(settings, 'APICLIENT_WL_ACCESS_TOKEN_URL',
+ WL_API_URL + 'oauth/access_token/')
+WL_AUTHORIZE_URL = getattr(settings, 'APICLIENT_WL_AUTHORIZE_URL',
+ WL_API_URL + 'oauth/authorize/')
diff --git a/apps/apiclient/tests.py b/apps/apiclient/tests.py
new file mode 100644
index 00000000..2247054b
--- /dev/null
+++ b/apps/apiclient/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/apps/apiclient/urls.py b/apps/apiclient/urls.py
new file mode 100755
index 00000000..5e54965e
--- /dev/null
+++ b/apps/apiclient/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('apiclient.views',
+ url(r'^oauth/$', 'oauth', name='users_oauth'),
+ url(r'^oauth_callback/$', 'oauth_callback', name='users_oauth_callback'),
+)
diff --git a/apps/apiclient/views.py b/apps/apiclient/views.py
new file mode 100644
index 00000000..f8515904
--- /dev/null
+++ b/apps/apiclient/views.py
@@ -0,0 +1,60 @@
+import cgi
+
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect, HttpResponse
+import oauth2
+
+from apiclient.models import OAuthConnection
+from apiclient import wl_consumer
+from apiclient.settings import (WL_REQUEST_TOKEN_URL, WL_ACCESS_TOKEN_URL,
+ WL_AUTHORIZE_URL)
+
+
+@login_required
+def oauth(request):
+ if wl_consumer is None:
+ return HttpResponse("OAuth consumer not configured.")
+
+ client = oauth2.Client(wl_consumer)
+ resp, content = client.request(WL_REQUEST_TOKEN_URL)
+ if resp['status'] != '200':
+ raise Exception("Invalid response %s." % resp['status'])
+
+ request_token = dict(cgi.parse_qsl(content))
+
+ conn = OAuthConnection.get(request.user)
+ # this might reset existing auth!
+ conn.access = False
+ conn.token = request_token['oauth_token']
+ conn.token_secret = request_token['oauth_token_secret']
+ conn.save()
+
+ url = "%s?oauth_token=%s&oauth_callback=%s" % (
+ WL_AUTHORIZE_URL,
+ request_token['oauth_token'],
+ request.build_absolute_uri(reverse("users_oauth_callback")),
+ )
+
+ return HttpResponseRedirect(url)
+
+
+@login_required
+def oauth_callback(request):
+ if wl_consumer is None:
+ return HttpResponse("OAuth consumer not configured.")
+
+ oauth_verifier = request.GET.get('oauth_verifier')
+ conn = OAuthConnection.get(request.user)
+ token = oauth2.Token(conn.token, conn.token_secret)
+ token.set_verifier(oauth_verifier)
+ client = oauth2.Client(wl_consumer, token)
+ resp, content = client.request(WL_ACCESS_TOKEN_URL, method="POST")
+ access_token = dict(cgi.parse_qsl(content))
+
+ conn.access = True
+ conn.token = access_token['oauth_token']
+ conn.token_secret = access_token['oauth_token_secret']
+ conn.save()
+
+ return HttpResponseRedirect('/')
diff --git a/apps/dvcs/migrations/0001_initial.py b/apps/dvcs/migrations/0001_initial.py
index 7510e8ad..d9f0984c 100644
--- a/apps/dvcs/migrations/0001_initial.py
+++ b/apps/dvcs/migrations/0001_initial.py
@@ -8,6 +8,15 @@ 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)),
@@ -20,12 +29,21 @@ class Migration(SchemaMigration):
('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)),
@@ -40,9 +58,15 @@ class Migration(SchemaMigration):
# 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')
@@ -94,7 +118,9 @@ class Migration(SchemaMigration):
'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': {
@@ -102,6 +128,13 @@ class Migration(SchemaMigration):
'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'})
}
}
diff --git a/apps/dvcs/migrations/0002_auto__add_tag.py b/apps/dvcs/migrations/0002_auto__add_tag.py
deleted file mode 100644
index 7f5b9a3e..00000000
--- a/apps/dvcs/migrations/0002_auto__add_tag.py
+++ /dev/null
@@ -1,104 +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 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']
diff --git a/apps/dvcs/models.py b/apps/dvcs/models.py
index dddbf3ad..262472a3 100644
--- a/apps/dvcs/models.py
+++ b/apps/dvcs/models.py
@@ -66,6 +66,7 @@ class Change(models.Model):
description = models.TextField(blank=True, default='')
created_at = models.DateTimeField(editable=False, db_index=True,
default=datetime.now)
+ publishable = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag)
@@ -245,8 +246,8 @@ 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]
+ def publishable(self):
+ changes = self.change_set.filter(publishable=True).order_by('-created_at')[:1]
if changes.count():
return changes[0]
else:
diff --git a/apps/wiki/forms.py b/apps/wiki/forms.py
index fb0f958a..55b1e544 100644
--- a/apps/wiki/forms.py
+++ b/apps/wiki/forms.py
@@ -20,6 +20,17 @@ class DocumentTagForm(forms.Form):
revision = forms.IntegerField(widget=forms.HiddenInput)
+class DocumentPubmarkForm(forms.Form):
+ """
+ Form for marking revisions for publishing.
+ """
+
+ id = forms.CharField(widget=forms.HiddenInput)
+ publishable = forms.BooleanField(required=False, initial=True,
+ label=_('Publishable'))
+ revision = forms.IntegerField(widget=forms.HiddenInput)
+
+
class DocumentCreateForm(forms.ModelForm):
"""
Form used for creating new documents.
@@ -180,7 +191,8 @@ class BookAppendForm(forms.Form):
It means moving all chunks from book A to book B and deleting A.
"""
- append_to = forms.ModelChoiceField(queryset=Book.objects.all())
+ append_to = forms.ModelChoiceField(queryset=Book.objects.all(),
+ label=_("Append to"))
class BookForm(forms.ModelForm):
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
index 229825d2..010ed2a6 100644
--- 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
@@ -151,6 +151,8 @@ class Migration(SchemaMigration):
('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'])
@@ -235,7 +237,9 @@ class Migration(SchemaMigration):
'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': {
@@ -244,10 +248,19 @@ class Migration(SchemaMigration):
'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'})
+ },
'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'}),
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
deleted file mode 100644
index a09dc90e..00000000
--- a/apps/wiki/migrations/0004_auto__add_field_book__list_html.py
+++ /dev/null
@@ -1,102 +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 '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']
diff --git a/apps/wiki/models.py b/apps/wiki/models.py
index 4dc70173..7887e5da 100644
--- a/apps/wiki/models.py
+++ b/apps/wiki/models.py
@@ -28,6 +28,7 @@ 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)
+ last_published = models.DateTimeField(null=True, editable=False)
_list_html = models.TextField(editable=False, null=True)
@@ -78,18 +79,14 @@ class Book(models.Model):
self.save(reset_list_html=False)
return mark_safe(self._list_html)
- @staticmethod
- def publish_tag():
- return dvcs_models.Tag.get('publish')
-
- def materialize(self, tag=None):
+ def materialize(self, publishable=True):
"""
Get full text of the document compiled from chunks.
Takes the current versions of all texts
- or versions most recently tagged by a given tag.
+ or versions most recently tagged for publishing.
"""
- if tag:
- changes = [chunk.last_tagged(tag) for chunk in self]
+ if publishable:
+ changes = [chunk.publishable() for chunk in self]
else:
changes = [chunk.head for chunk in self]
if None in changes:
@@ -183,9 +180,6 @@ 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())
-
def split(self, slug, comment='', creator=None):
""" Create an empty chunk after this one """
self.book.chunk_set.filter(number__gt=self.number).update(
diff --git a/apps/wiki/templates/wiki/book_detail.html b/apps/wiki/templates/wiki/book_detail.html
index 26f52145..f4b15a9e 100755
--- a/apps/wiki/templates/wiki/book_detail.html
+++ b/apps/wiki/templates/wiki/book_detail.html
@@ -62,6 +62,8 @@
{% trans "Append to other book" %}
+{% trans "Last published" %}: {{ book.last_published }}
+
{% if book.publishable %}
{% trans "Full XML" %}
@@ -73,7 +75,18 @@
{% endcomment %}
-
+
+
{% else %}
{% trans "This book cannot be published yet" %}
{% endif %}
diff --git a/apps/wiki/templates/wiki/document_details.html b/apps/wiki/templates/wiki/document_details.html
index 934a8ac6..d95603f6 100644
--- a/apps/wiki/templates/wiki/document_details.html
+++ b/apps/wiki/templates/wiki/document_details.html
@@ -43,4 +43,5 @@
{% include "wiki/save_dialog.html" %}
{% include "wiki/revert_dialog.html" %}
{% include "wiki/tag_dialog.html" %}
+ {% include "wiki/pubmark_dialog.html" %}
{% endblock %}
diff --git a/apps/wiki/templates/wiki/pubmark_dialog.html b/apps/wiki/templates/wiki/pubmark_dialog.html
new file mode 100755
index 00000000..93a98563
--- /dev/null
+++ b/apps/wiki/templates/wiki/pubmark_dialog.html
@@ -0,0 +1,19 @@
+{% load i18n %}
+
diff --git a/apps/wiki/templates/wiki/tabs/history_view.html b/apps/wiki/templates/wiki/tabs/history_view.html
index 60c59a95..db207bb1 100644
--- a/apps/wiki/templates/wiki/tabs/history_view.html
+++ b/apps/wiki/templates/wiki/tabs/history_view.html
@@ -5,6 +5,8 @@
data-enabled-when="2" disabled="disabled">{% trans "Compare versions" %}
+
,
-
+ |
+
+
|
diff --git a/apps/wiki/urls.py b/apps/wiki/urls.py
index 393afa5e..c7da6ef8 100644
--- a/apps/wiki/urls.py
+++ b/apps/wiki/urls.py
@@ -43,11 +43,12 @@ urlpatterns = patterns('wiki.views',
url(r'^revert/(?P[^/]+)/(?:(?P[^/]+)/)?$',
'revert', name='wiki_revert'),
- #url(r'^(?P[^/]+)/publish$', 'publish', name="wiki_publish"),
+ url(r'^book/(?P[^/]+)/publish$', 'publish', name="wiki_publish"),
#url(r'^(?P[^/]+)/publish/(?P\d+)$', 'publish', name="wiki_publish"),
url(r'^diff/(?P[^/]+)/(?:(?P[^/]+)/)?$', 'diff', name="wiki_diff"),
url(r'^tag/(?P[^/]+)/(?:(?P[^/]+)/)?$', 'add_tag', name="wiki_add_tag"),
+ url(r'^pubmark/(?P[^/]+)/(?:(?P[^/]+)/)?$', 'pubmark', name="wiki_pubmark"),
url(r'^book/(?P[^/]+)/$', 'book', name="wiki_book"),
url(r'^book/(?P[^/]+)/xml$', 'book_xml', name="wiki_book_xml"),
diff --git a/apps/wiki/views.py b/apps/wiki/views.py
index 3739c90d..a68fa35d 100644
--- a/apps/wiki/views.py
+++ b/apps/wiki/views.py
@@ -7,6 +7,7 @@ from lxml import etree
from django.conf import settings
+from django.contrib.auth.decorators import login_required
from django.views.generic.simple import direct_to_template
from django.views.decorators.http import require_POST, require_GET
from django.core.urlresolvers import reverse
@@ -27,6 +28,7 @@ from django.middleware.gzip import GZipMiddleware
import librarian.html
import librarian.text
from wiki import xml_tools
+from apiclient import api_call
#
# Quick hack around caching problems, TODO: use ETags
@@ -82,6 +84,7 @@ def editor(request, slug, chunk=None, template_name='wiki/document_details.html'
"text_save": forms.DocumentTextSaveForm(prefix="textsave"),
"text_revert": forms.DocumentTextRevertForm(prefix="textrevert"),
"add_tag": forms.DocumentTagForm(prefix="addtag"),
+ "pubmark": forms.DocumentPubmarkForm(prefix="pubmark"),
},
'REDMINE_URL': settings.REDMINE_URL,
})
@@ -254,7 +257,7 @@ def text(request, slug, chunk=None):
@never_cache
def book_xml(request, slug):
- xml = get_object_or_404(Book, slug=slug).materialize(Book.publish_tag())
+ xml = get_object_or_404(Book, slug=slug).materialize()
response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
@@ -263,7 +266,7 @@ def book_xml(request, slug):
@never_cache
def book_txt(request, slug):
- xml = get_object_or_404(Book, slug=slug).materialize(Book.publish_tag())
+ xml = get_object_or_404(Book, slug=slug).materialize()
output = StringIO()
# errors?
librarian.text.transform(StringIO(xml), output)
@@ -275,7 +278,7 @@ def book_txt(request, slug):
@never_cache
def book_html(request, slug):
- xml = get_object_or_404(Book, slug=slug).materialize(Book.publish_tag())
+ xml = get_object_or_404(Book, slug=slug).materialize()
output = StringIO()
# errors?
librarian.html.transform(StringIO(xml), output, parse_dublincore=False,
@@ -391,6 +394,7 @@ def history(request, slug, chunk=None):
"description": change.description,
"author": change.author_str(),
"date": change.created_at,
+ "publishable": "Publishable\n" if change.publishable else "",
"tag": ',\n'.join(unicode(tag) for tag in change.tags.all()),
})
return JSONResponse(changes)
@@ -581,32 +585,50 @@ def add_tag(request, slug, chunk=None):
raise Http404
tag = form.cleaned_data['tag']
- revision = revision=form.cleaned_data['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):
- name = normalize_name(name)
+@ajax_require_permission('wiki.can_pubmark')
+def pubmark(request, slug, chunk=None):
+ form = forms.DocumentPubmarkForm(request.POST, prefix="pubmark")
+ if form.is_valid():
+ try:
+ doc = Chunk.get(slug, chunk)
+ except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
+ raise Http404
- storage = getstorage()
- document = storage.get_by_tag(name, "ready_to_publish")
+ revision = form.cleaned_data['revision']
+ publishable = form.cleaned_data['publishable']
+ change = doc.at_revision(revision)
+ print publishable, change.publishable
+ if publishable != change.publishable:
+ change.publishable = publishable
+ change.save()
+ return JSONResponse({"message": _("Revision marked")})
+ else:
+ return JSONResponse({"message": _("Nothing changed")})
+ else:
+ return JSONFormInvalid(form)
- api = wlapi.WLAPI(**settings.WL_API_CONFIG)
+@require_POST
+@login_required
+def publish(request, slug):
+ book = get_object_or_404(Book, slug=slug)
try:
- return JSONResponse({"result": api.publish_book(document)})
- except wlapi.APICallException, e:
- return JSONServerError({"message": str(e)})
-"""
+ ret = api_call(request.user, "books", {"book_xml": book.materialize()})
+ except BaseException, e:
+ return http.HttpResponse(e)
+ else:
+ book.last_published = datetime.now()
+ book.save()
+ return http.HttpResponseRedirect(book.get_absolute_url())
+
def themes(request):
prefix = request.GET.get('q', '')
diff --git a/redakcja/settings/common.py b/redakcja/settings/common.py
index 587374be..f5e90de3 100644
--- a/redakcja/settings/common.py
+++ b/redakcja/settings/common.py
@@ -117,10 +117,11 @@ INSTALLED_APPS = (
'south',
'sorl.thumbnail',
'filebrowser',
- 'dvcs',
+ 'dvcs',
'wiki',
'toolbar',
+ 'apiclient',
)
FILEBROWSER_URL_FILEBROWSER_MEDIA = STATIC_URL + 'filebrowser/'
diff --git a/redakcja/settings/compress.py b/redakcja/settings/compress.py
index 8bade8d0..5554aca8 100644
--- a/redakcja/settings/compress.py
+++ b/redakcja/settings/compress.py
@@ -45,6 +45,7 @@ COMPRESS_JS = {
'js/wiki/dialog_save.js',
'js/wiki/dialog_revert.js',
'js/wiki/dialog_addtag.js',
+ 'js/wiki/dialog_pubmark.js',
# views
'js/wiki/view_history.js',
diff --git a/redakcja/static/css/filelist.css b/redakcja/static/css/filelist.css
index 91323c0f..f6f55d7a 100644
--- a/redakcja/static/css/filelist.css
+++ b/redakcja/static/css/filelist.css
@@ -116,3 +116,30 @@ td {
.chunk-wl-broken a {color: red;}
.chunk-wl a {color: green;}
.chunk-wl-fix a {color: black;}
+
+
+/* Big cheesy publish button */
+#publish-button {
+ color: black;
+ border: 2px solid black;
+ border-radius: 20px;
+ box-shadow: 0px 0px 15px #88f;
+ /*moz-border-radius: 20px;
+ -moz-box-shadow: 10px 10px 5px #888;*/
+ font-size:1.5em;
+ padding: 1em;
+ background: -moz-linear-gradient(top, #fff, #44f);
+ -moz-transition: all 0.5s ease-in-out;
+ margin: 20px;
+}
+
+#publish-button:hover {
+ -moz-transition: all 0.5s ease-in-out;
+ -moz-transform: scale(1.1);
+ background: -moz-linear-gradient(top, #fff, #88f);
+ -moz-box-shadow: 0px 0px 30px #ff8;
+}
+
+#publish-button:active {
+ background: -moz-linear-gradient(top, #88f, #fff);
+}
diff --git a/redakcja/static/img/angel-left.png b/redakcja/static/img/angel-left.png
new file mode 100644
index 00000000..7744103d
Binary files /dev/null and b/redakcja/static/img/angel-left.png differ
diff --git a/redakcja/static/img/angel-right.png b/redakcja/static/img/angel-right.png
new file mode 100644
index 00000000..df85d33a
Binary files /dev/null and b/redakcja/static/img/angel-right.png differ
diff --git a/redakcja/static/js/wiki/dialog_pubmark.js b/redakcja/static/js/wiki/dialog_pubmark.js
new file mode 100755
index 00000000..902a737c
--- /dev/null
+++ b/redakcja/static/js/wiki/dialog_pubmark.js
@@ -0,0 +1,61 @@
+/*
+ * Dialog for marking document for publishing
+ *
+ */
+(function($){
+
+ function PubmarkDialog(element, options){
+ if (!options.revision && options.revision != 0)
+ throw "PubmarkDialog needs a revision number.";
+
+ this.ctx = $.wiki.exitContext();
+ this.clearForm();
+
+ /* fill out hidden fields */
+ this.$form = $('form', element);
+
+ $("input[name='pubmark-id']", this.$form).val(CurrentDocument.id);
+ $("input[name='pubmark-revision']", this.$form).val(options.revision);
+
+ $.wiki.cls.GenericDialog.call(this, element);
+ };
+
+ PubmarkDialog.prototype = $.extend(new $.wiki.cls.GenericDialog(), {
+ cancelAction: function(){
+ $.wiki.enterContext(this.ctx);
+ this.hide();
+ },
+
+ saveAction: function(){
+ var self = this;
+
+ self.$elem.block({
+ message: "Oznaczanie wersji",
+ fadeIn: 0,
+ });
+
+ CurrentDocument.pubmark({
+ form: self.$form,
+ success: function(doc, changed, info){
+ self.$elem.block({
+ message: info,
+ timeout: 2000,
+ fadeOut: 0,
+ onUnblock: function(){
+ self.hide();
+ $.wiki.enterContext(self.ctx);
+ }
+ });
+ },
+ failure: function(doc, info){
+ console.log("Failure", info);
+ self.reportErrors(info);
+ self.$elem.unblock();
+ }
+ });
+ }
+ });
+
+ /* make it global */
+ $.wiki.cls.PubmarkDialog = PubmarkDialog;
+})(jQuery);
diff --git a/redakcja/static/js/wiki/view_history.js b/redakcja/static/js/wiki/view_history.js
index 4fe20e25..fe3af69a 100644
--- a/redakcja/static/js/wiki/view_history.js
+++ b/redakcja/static/js/wiki/view_history.js
@@ -15,6 +15,10 @@
self.showTagForm();
});
+ $('#pubmark-changeset-button').click(function() {
+ self.showPubmarkForm();
+ });
+
$('#doc-revert-button').click(function() {
self.revertDialog();
});
@@ -140,6 +144,18 @@
$.wiki.showDialog('#add_tag_dialog', {'revision': version});
};
+ HistoryPerspective.prototype.showPubmarkForm = function(){
+ var selected = $('#changes-list .entry.selected');
+
+ if (selected.length != 1) {
+ window.alert("Musisz zaznaczyÄ dokÅadnie jednÄ
wersjÄ.");
+ return;
+ }
+
+ var version = parseInt($("*[data-stub-value='version']", selected[0]).text());
+ $.wiki.showDialog('#pubmark_dialog', {'revision': version});
+ };
+
HistoryPerspective.prototype.makeDiff = function() {
var changelist = $('#changes-list');
var selected = $('.entry.selected', changelist);
diff --git a/redakcja/static/js/wiki/wikiapi.js b/redakcja/static/js/wiki/wikiapi.js
index 2f79b09b..a1f2fb92 100644
--- a/redakcja/static/js/wiki/wikiapi.js
+++ b/redakcja/static/js/wiki/wikiapi.js
@@ -50,6 +50,9 @@
if (vname == "ajax_document_addtag")
return base_path + "/tag/" + arguments[1] + '/';
+ if (vname == "ajax_document_pubmark")
+ return base_path + "/pubmark/" + arguments[1] + '/';
+
if (vname == "ajax_publish")
return base_path + "/publish/" + arguments[1] + '/';
@@ -389,5 +392,45 @@
});
};
+ WikiDocument.prototype.pubmark = function(params) {
+ params = $.extend({}, noops, params);
+ var self = this;
+ var data = {
+ "pubmark-id": self.id,
+ };
+
+ /* unpack form */
+ $.each(params.form.serializeArray(), function() {
+ data[this.name] = this.value;
+ });
+
+ $.ajax({
+ url: reverse("ajax_document_pubmark", self.id),
+ type: "POST",
+ dataType: "json",
+ data: data,
+ success: function(data) {
+ params.success(self, data.message);
+ },
+ error: function(xhr) {
+ if (xhr.status == 403 || xhr.status == 401) {
+ params.failure(self, {
+ "__all__": ["Nie masz uprawnieÅ lub nie jesteÅ zalogowany."]
+ });
+ }
+ else {
+ try {
+ params.failure(self, $.parseJSON(xhr.responseText));
+ }
+ catch (e) {
+ params.failure(self, {
+ "__all__": ["Nie udaÅo siÄ - bÅÄ
d serwera."]
+ });
+ };
+ };
+ }
+ });
+ };
+
$.wikiapi.WikiDocument = WikiDocument;
})(jQuery);
diff --git a/redakcja/urls.py b/redakcja/urls.py
index 20994ce1..8aa569b2 100644
--- a/redakcja/urls.py
+++ b/redakcja/urls.py
@@ -21,6 +21,7 @@ urlpatterns = patterns('',
url(r'^$', 'django.views.generic.simple.redirect_to', {'url': '/documents/'}),
url(r'^documents/', include('wiki.urls')),
url(r'^storage/', include('dvcs.urls')),
+ url(r'^apiclient/', include('apiclient.urls')),
# Static files (should be served by Apache)
url(r'^%s(?P.+)$' % settings.MEDIA_URL[1:], 'django.views.static.serve',
diff --git a/requirements.txt b/requirements.txt
index c2ed8400..6e84562f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,6 +3,8 @@ lxml>=2.2.2
mercurial>=1.6,<1.7
PyYAML>=3.0
PIL>=1.1
+oauth2
+httplib2 # oauth2 dependency
## Book conversion library
# git+git://github.com/fnp/librarian.git@master#egg=librarian