From: Radek Czajka Date: Wed, 27 Feb 2019 22:15:37 +0000 (+0100) Subject: Rearrange source to src dir. X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/5913c54d19b8f6775633176032161d49f9b2f1aa Rearrange source to src dir. --- diff --git a/README.md b/README.md index 93ffb1c4..f1672f4f 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,9 @@ Testy ==== $ pip install -r requirements-test.txt - $ python redakcja/manage.py test --settings=settings.test + $ python src/manage.py test --settings=redakcja.settings.test JavaScript (wymagany node.js i xsltproc): $ npm install - $ ./node_modules/.bin/mocha -u tdd $(find -name *_test.js) - \ No newline at end of file + $ ./node_modules/.bin/mocha -u tdd $(find src -name *_test.js) diff --git a/apps/apiclient/__init__.py b/apps/apiclient/__init__.py deleted file mode 100644 index 56ecb96d..00000000 --- a/apps/apiclient/__init__.py +++ /dev/null @@ -1,51 +0,0 @@ -import urllib - -import json -import oauth2 - -from apiclient.settings import WL_CONSUMER_KEY, WL_CONSUMER_SECRET, WL_API_URL, BETA_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, beta=False): - from .models import OAuthConnection - api_url = BETA_API_URL if beta else WL_API_URL - conn = OAuthConnection.get(user=user, beta=beta) - 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: - data = json.dumps(data) - data = urllib.urlencode({"data": data}) - resp, content = client.request( - "%s%s" % (api_url, path), - method="POST", - body=data) - else: - resp, content = client.request( - "%s%s" % (api_url, path)) - status = resp['status'] - - if status == '200': - return json.loads(content) - elif status.startswith('2'): - return - elif status == '401': - raise ApiError('User not authorized for publishing.') - else: - raise ApiError("WL API call error %s, path: %s" % (status, path)) - diff --git a/apps/apiclient/migrations/0001_initial.py b/apps/apiclient/migrations/0001_initial.py deleted file mode 100644 index 4af28a52..00000000 --- a/apps/apiclient/migrations/0001_initial.py +++ /dev/null @@ -1,75 +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 '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/0002_auto__add_field_oauthconnection_beta.py b/apps/apiclient/migrations/0002_auto__add_field_oauthconnection_beta.py deleted file mode 100644 index 094606fe..00000000 --- a/apps/apiclient/migrations/0002_auto__add_field_oauthconnection_beta.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as 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 'OAuthConnection.beta' - db.add_column(u'apiclient_oauthconnection', 'beta', - self.gf('django.db.models.fields.BooleanField')(default=False), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'OAuthConnection.beta' - db.delete_column(u'apiclient_oauthconnection', 'beta') - - - models = { - u'apiclient.oauthconnection': { - 'Meta': {'object_name': 'OAuthConnection'}, - 'access': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'beta': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - u'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': u"orm['auth.User']", 'unique': 'True'}) - }, - u'auth.group': { - 'Meta': {'object_name': 'Group'}, - u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - u'auth.permission': { - 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), - u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - u'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'}), - u'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'] \ No newline at end of file diff --git a/apps/apiclient/migrations/0003_auto__chg_field_oauthconnection_user__del_unique_oauthconnection_user.py b/apps/apiclient/migrations/0003_auto__chg_field_oauthconnection_user__del_unique_oauthconnection_user.py deleted file mode 100644 index ebd0fbb4..00000000 --- a/apps/apiclient/migrations/0003_auto__chg_field_oauthconnection_user__del_unique_oauthconnection_user.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Removing unique constraint on 'OAuthConnection', fields ['user'] - db.delete_unique(u'apiclient_oauthconnection', ['user_id']) - - - # Changing field 'OAuthConnection.user' - db.alter_column(u'apiclient_oauthconnection', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])) - - def backwards(self, orm): - - # Changing field 'OAuthConnection.user' - db.alter_column(u'apiclient_oauthconnection', 'user_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)) - # Adding unique constraint on 'OAuthConnection', fields ['user'] - db.create_unique(u'apiclient_oauthconnection', ['user_id']) - - - models = { - u'apiclient.oauthconnection': { - 'Meta': {'object_name': 'OAuthConnection'}, - 'access': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'beta': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - u'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.ForeignKey', [], {'to': u"orm['auth.User']"}) - }, - u'auth.group': { - 'Meta': {'object_name': 'Group'}, - u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - u'auth.permission': { - 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), - u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - u'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'}), - u'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'] \ No newline at end of file diff --git a/apps/apiclient/migrations/__init__.py b/apps/apiclient/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/apiclient/models.py b/apps/apiclient/models.py deleted file mode 100644 index 64130bc1..00000000 --- a/apps/apiclient/models.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.db import models -from django.contrib.auth.models import User - - -class OAuthConnection(models.Model): - user = models.ForeignKey(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) - beta = models.BooleanField(default=False) - - @classmethod - def get(cls, user, beta=False): - try: - return cls.objects.get(user=user, beta=beta) - except cls.DoesNotExist: - o = cls(user=user, beta=beta) - o.save() - return o - - diff --git a/apps/apiclient/settings.py b/apps/apiclient/settings.py deleted file mode 100755 index 51b49069..00000000 --- a/apps/apiclient/settings.py +++ /dev/null @@ -1,20 +0,0 @@ -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', 'https://wolnelektury.pl/api/') - -BETA_API_URL = getattr(settings, 'APICLIENT_BETA_API_URL', 'http://dev.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/') - -BETA_REQUEST_TOKEN_URL = BETA_API_URL + 'oauth/request_token/' -BETA_ACCESS_TOKEN_URL = BETA_API_URL + 'oauth/access_token/' -BETA_AUTHORIZE_URL = BETA_API_URL + 'oauth/authorize/' diff --git a/apps/apiclient/urls.py b/apps/apiclient/urls.py deleted file mode 100755 index f623474e..00000000 --- a/apps/apiclient/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -from django.conf.urls import patterns, url - -urlpatterns = patterns('apiclient.views', - url(r'^oauth/$', 'oauth', name='apiclient_oauth'), - url(r'^oauth_callback/$', 'oauth_callback', name='apiclient_oauth_callback'), - url(r'^oauth-beta/$', 'oauth', kwargs={'beta': True}, name='apiclient_beta_oauth'), - url(r'^oauth_callback-beta/$', 'oauth_callback', kwargs={'beta': True}, name='apiclient_beta_callback'), -) diff --git a/apps/apiclient/views.py b/apps/apiclient/views.py deleted file mode 100644 index 239682a5..00000000 --- a/apps/apiclient/views.py +++ /dev/null @@ -1,60 +0,0 @@ -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 -from apiclient.settings import BETA_REQUEST_TOKEN_URL, BETA_ACCESS_TOKEN_URL, BETA_AUTHORIZE_URL - - -@login_required -def oauth(request, beta=False): - 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 not beta else BETA_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, beta) - # 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 if not beta else BETA_AUTHORIZE_URL, - request_token['oauth_token'], - request.build_absolute_uri(reverse("apiclient_oauth_callback" if not beta else "apiclient_beta_callback")), - ) - - return HttpResponseRedirect(url) - - -@login_required -def oauth_callback(request, beta=False): - if wl_consumer is None: - return HttpResponse("OAuth consumer not configured.") - - oauth_verifier = request.GET.get('oauth_verifier') - conn = OAuthConnection.get(request.user, beta) - 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 if not beta else BETA_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/catalogue/__init__.py b/apps/catalogue/__init__.py deleted file mode 100644 index c53f0e73..00000000 --- a/apps/catalogue/__init__.py +++ /dev/null @@ -1 +0,0 @@ - # pragma: no cover diff --git a/apps/catalogue/admin.py b/apps/catalogue/admin.py deleted file mode 100644 index 53e8a256..00000000 --- a/apps/catalogue/admin.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.contrib import admin - -from catalogue import models - -class BookAdmin(admin.ModelAdmin): - list_display = ['title', 'public', '_published', '_new_publishable', 'project'] - list_filter = ['public', '_published', '_new_publishable', 'project'] - prepopulated_fields = {'slug': ['title']} - search_fields = ['title'] - - -admin.site.register(models.Project) -admin.site.register(models.Book, BookAdmin) -admin.site.register(models.Chunk) -admin.site.register(models.Chunk.tag_model) - -admin.site.register(models.Image) -admin.site.register(models.Image.tag_model) diff --git a/apps/catalogue/constants.py b/apps/catalogue/constants.py deleted file mode 100644 index 0c842324..00000000 --- a/apps/catalogue/constants.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -TRIM_BEGIN = " TRIM_BEGIN " -TRIM_END = " TRIM_END " - -MASTERS = ['powiesc', - 'opowiadanie', - 'liryka_l', - 'liryka_lp', - 'dramat_wierszowany_l', - 'dramat_wierszowany_lp', - 'dramat_wspolczesny', - ] diff --git a/apps/catalogue/ebook_utils.py b/apps/catalogue/ebook_utils.py deleted file mode 100644 index dae2e769..00000000 --- a/apps/catalogue/ebook_utils.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -from StringIO import StringIO -from catalogue.models import Book -from librarian import DocProvider -from django.http import HttpResponse - - -class RedakcjaDocProvider(DocProvider): - """Used for getting books' children.""" - - def __init__(self, publishable): - self.publishable = publishable - - def by_slug(self, slug): - return StringIO(Book.objects.get(dc_slug=slug - ).materialize(publishable=self.publishable - ).encode('utf-8')) - - -def serve_file(file_path, name, mime_type): - def read_chunks(f, size=8192): - chunk = f.read(size) - while chunk: - yield chunk - chunk = f.read(size) - - response = HttpResponse(content_type=mime_type) - response['Content-Disposition'] = 'attachment; filename=%s' % name - with open(file_path) as f: - for chunk in read_chunks(f): - response.write(chunk) - return response diff --git a/apps/catalogue/feeds.py b/apps/catalogue/feeds.py deleted file mode 100644 index 4884a4cd..00000000 --- a/apps/catalogue/feeds.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -from django.contrib.syndication.views import Feed -from django.shortcuts import get_object_or_404 -from catalogue.models import Book, Chunk - -class PublishTrackFeed(Feed): - title = u"Planowane publikacje" - link = "/" - - def description(self, obj): - tag, published = obj - return u"Publikacje, które dotarły co najmniej do etapu: %s" % tag.name - - def get_object(self, request, slug): - published = request.GET.get('published') - if published is not None: - published = published == 'true' - return get_object_or_404(Chunk.tag_model, slug=slug), published - - def item_title(self, item): - return item.title - - def items(self, obj): - tag, published = obj - books = Book.objects.filter(public=True, _on_track__gte=tag.ordering - ).order_by('-_on_track', 'title') - if published is not None: - books = books.filter(_published=published) - return books diff --git a/apps/catalogue/fixtures/stages.json b/apps/catalogue/fixtures/stages.json deleted file mode 100644 index 5a46ec04..00000000 --- a/apps/catalogue/fixtures/stages.json +++ /dev/null @@ -1,83 +0,0 @@ -[ - { - "pk": 1, - "model": "catalogue.chunktag", - "fields": { - "ordering": 1, - "name": "Autokorekta", - "slug": "first_correction" - } - }, - { - "pk": 2, - "model": "catalogue.chunktag", - "fields": { - "ordering": 2, - "name": "Tagowanie", - "slug": "tagging" - } - }, - { - "pk": 3, - "model": "catalogue.chunktag", - "fields": { - "ordering": 3, - "name": "Korekta", - "slug": "proofreading" - } - }, - { - "pk": 4, - "model": "catalogue.chunktag", - "fields": { - "ordering": 4, - "name": "Sprawdzenie przypis\u00f3w \u017ar\u00f3d\u0142a", - "slug": "annotation-proofreading" - } - }, - { - "pk": 5, - "model": "catalogue.chunktag", - "fields": { - "ordering": 5, - "name": "Uwsp\u00f3\u0142cze\u015bnienie", - "slug": "modernisation" - } - }, - { - "pk": 6, - "model": "catalogue.chunktag", - "fields": { - "ordering": 6, - "name": "Przypisy", - "slug": "annotations" - } - }, - { - "pk": 7, - "model": "catalogue.chunktag", - "fields": { - "ordering": 7, - "name": "Motywy", - "slug": "themes" - } - }, - { - "pk": 8, - "model": "catalogue.chunktag", - "fields": { - "ordering": 8, - "name": "Ostateczna redakcja literacka", - "slug": "editor-proofreading" - } - }, - { - "pk": 9, - "model": "catalogue.chunktag", - "fields": { - "ordering": 9, - "name": "Ostateczna redakcja techniczna", - "slug": "technical-editor-proofreading" - } - } -] diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py deleted file mode 100644 index ea6a4aef..00000000 --- a/apps/catalogue/forms.py +++ /dev/null @@ -1,234 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from catalogue.models import User -from django.db.models import Count -from django import forms -from django.utils.translation import ugettext_lazy as _ -from django.conf import settings - -from catalogue.constants import MASTERS -from catalogue.models import Book, Chunk, Image - -class DocumentCreateForm(forms.ModelForm): - """ - Form used for creating new documents. - """ - file = forms.FileField(required=False) - text = forms.CharField(required=False, widget=forms.Textarea) - - class Meta: - model = Book - exclude = ['parent', 'parent_number', 'project'] - - def __init__(self, *args, **kwargs): - super(DocumentCreateForm, self).__init__(*args, **kwargs) - self.fields['slug'].widget.attrs={'class': 'autoslug'} - self.fields['gallery'].widget.attrs={'class': 'autoslug'} - self.fields['title'].widget.attrs={'class': 'autoslug-source'} - - def clean(self): - super(DocumentCreateForm, self).clean() - file = self.cleaned_data['file'] - - if file is not None: - try: - self.cleaned_data['text'] = file.read().decode('utf-8') - except UnicodeDecodeError: - raise forms.ValidationError(_("Text file must be UTF-8 encoded.")) - - if not self.cleaned_data["text"]: - self._errors["file"] = self.error_class([_("You must either enter text or upload a file")]) - - return self.cleaned_data - - -class DocumentsUploadForm(forms.Form): - """ - Form used for uploading new documents. - """ - file = forms.FileField(required=True, label=_('ZIP file')) - dirs = forms.BooleanField(label=_('Directories are documents in chunks'), - widget = forms.CheckboxInput(attrs={'disabled':'disabled'})) - - def clean(self): - file = self.cleaned_data['file'] - - import zipfile - try: - z = self.cleaned_data['zip'] = zipfile.ZipFile(file) - except zipfile.BadZipfile: - raise forms.ValidationError("Should be a ZIP file.") - if z.testzip(): - raise forms.ValidationError("ZIP file corrupt.") - - return self.cleaned_data - - -class ChunkForm(forms.ModelForm): - """ - Form used for editing a chunk. - """ - user = forms.ModelChoiceField(queryset= - User.objects.annotate(count=Count('chunk')). - order_by('last_name', 'first_name'), required=False, - label=_('Assigned to')) - - class Meta: - model = Chunk - fields = ['title', 'slug', 'gallery_start', 'user', 'stage'] - exclude = ['number'] - - def __init__(self, *args, **kwargs): - super(ChunkForm, self).__init__(*args, **kwargs) - self.fields['gallery_start'].widget.attrs={'class': 'number-input'} - self.fields['slug'].widget.attrs={'class': 'autoslug'} - self.fields['title'].widget.attrs={'class': 'autoslug-source'} - - def clean_slug(self): - slug = self.cleaned_data['slug'] - try: - chunk = Chunk.objects.get(book=self.instance.book, slug=slug) - except Chunk.DoesNotExist: - return slug - if chunk == self.instance: - return slug - raise forms.ValidationError(_('Chunk with this slug already exists')) - - -class ChunkAddForm(ChunkForm): - """ - Form used for adding a chunk to a document. - """ - - def clean_slug(self): - slug = self.cleaned_data['slug'] - try: - user = Chunk.objects.get(book=self.instance.book, slug=slug) - except Chunk.DoesNotExist: - return slug - raise forms.ValidationError(_('Chunk with this slug already exists')) - - -class BookAppendForm(forms.Form): - """ - Form for appending a book to another book. - It means moving all chunks from book A to book B and deleting A. - """ - append_to = forms.ModelChoiceField(queryset=Book.objects.all(), - label=_("Append to")) - - def __init__(self, book, *args, **kwargs): - ret = super(BookAppendForm, self).__init__(*args, **kwargs) - self.fields['append_to'].queryset = Book.objects.exclude(pk=book.pk) - return ret - - -class BookForm(forms.ModelForm): - """Form used for editing a Book.""" - - class Meta: - model = Book - exclude = ['project'] - - def __init__(self, *args, **kwargs): - ret = super(BookForm, self).__init__(*args, **kwargs) - self.fields['slug'].widget.attrs.update({"class": "autoslug"}) - self.fields['title'].widget.attrs.update({"class": "autoslug-source"}) - return ret - - def save(self, **kwargs): - orig_instance = Book.objects.get(pk=self.instance.pk) - old_gallery = orig_instance.gallery - new_gallery = self.cleaned_data['gallery'] - if new_gallery != old_gallery: - import shutil - import os.path - from django.conf import settings - shutil.move(orig_instance.gallery_path(), - os.path.join(settings.MEDIA_ROOT, settings.IMAGE_DIR, new_gallery)) - super(BookForm, self).save(**kwargs) - - -class ReadonlyBookForm(BookForm): - """Form used for not editing a Book.""" - - def __init__(self, *args, **kwargs): - ret = super(ReadonlyBookForm, self).__init__(*args, **kwargs) - for field in self.fields.values(): - field.widget.attrs.update({"disabled": "disabled"}) - return ret - - -class ChooseMasterForm(forms.Form): - """ - Form used for fixing the chunks in a book. - """ - - master = forms.ChoiceField(choices=((m, m) for m in MASTERS)) - - -class ImageForm(forms.ModelForm): - """Form used for editing an Image.""" - user = forms.ModelChoiceField(queryset= - User.objects.annotate(count=Count('chunk')). - order_by('-count', 'last_name', 'first_name'), required=False, - label=_('Assigned to')) - - class Meta: - model = Image - fields = ['title', 'slug', 'user', 'stage'] - - def __init__(self, *args, **kwargs): - super(ImageForm, self).__init__(*args, **kwargs) - self.fields['slug'].widget.attrs={'class': 'autoslug'} - self.fields['title'].widget.attrs={'class': 'autoslug-source'} - - -class ReadonlyImageForm(ImageForm): - """Form used for not editing an Image.""" - - def __init__(self, *args, **kwargs): - super(ReadonlyImageForm, self).__init__(*args, **kwargs) - for field in self.fields.values(): - field.widget.attrs.update({"disabled": "disabled"}) - - -class MarkFinalForm(forms.Form): - username = forms.CharField(initial=settings.LITERARY_DIRECTOR_USERNAME) - comment = forms.CharField(initial=u'Ostateczna akceptacja merytoryczna przez kierownika literackiego.') - books = forms.CharField(widget=forms.Textarea, help_text=u'linki do książek w redakcji, po jednym na wiersz') - - def clean_books(self): - books_value = self.cleaned_data['books'] - slugs = [line.strip().strip('/').split('/')[-1] for line in books_value.split('\n') if line.strip()] - books = Book.objects.filter(slug__in=slugs) - if len(books) != len(slugs): - raise forms.ValidationError( - 'Incorrect slug(s): %s' % ' '.join(slug for slug in slugs if not Book.objects.filter(slug=slug))) - return books - - def clean_username(self): - username = self.cleaned_data['username'] - if not User.objects.filter(username=username): - raise forms.ValidationError('Invalid username') - return username - - def save(self): - for book in self.cleaned_data['books']: - for chunk in book.chunk_set.all(): - src = chunk.head.materialize() - chunk.commit( - text=src, - author=User.objects.get(username=self.cleaned_data['username']), - description=self.cleaned_data['comment'], - tags=[Chunk.tag_model.objects.get(slug='editor-proofreading')], - publishable=True - ) - - -class PublishOptionsForm(forms.Form): - days = forms.IntegerField(label=u'po ilu dniach udostępnienić (0 = od razu)', min_value=0, initial=0) - beta = forms.BooleanField(label=u'Opublikuj na wersji testowej', required=False) diff --git a/apps/catalogue/helpers.py b/apps/catalogue/helpers.py deleted file mode 100644 index d340b461..00000000 --- a/apps/catalogue/helpers.py +++ /dev/null @@ -1,148 +0,0 @@ -from datetime import date -from functools import wraps -from os.path import join -from os import listdir, stat -from shutil import move, rmtree -from django.conf import settings -import re -import filecmp - -from django.db.models import Count - - -def active_tab(tab): - """ - View decorator, which puts tab info on a request. - """ - def wrapper(f): - @wraps(f) - def wrapped(request, *args, **kwargs): - request.catalogue_active_tab = tab - return f(request, *args, **kwargs) - return wrapped - return wrapper - - -def cached_in_field(field_name): - def decorator(f): - @property - @wraps(f) - def wrapped(self, *args, **kwargs): - value = getattr(self, field_name) - if value is None: - value = f(self, *args, **kwargs) - type(self)._default_manager.filter(pk=self.pk).update(**{field_name: value}) - return value - return wrapped - return decorator - - -def parse_isodate(isodate): - try: - return date(*[int(p) for p in isodate.split('-')]) - except (AttributeError, TypeError, ValueError): - raise ValueError("Not a date in ISO format.") - - -class GalleryMerger(object): - def __init__(self, dest_gallery, src_gallery): - self.dest = dest_gallery - self.src = src_gallery - self.dest_size = None - self.src_size = None - self.num_deleted = 0 - - @staticmethod - def path(gallery): - return join(settings.MEDIA_ROOT, settings.IMAGE_DIR, gallery) - - @staticmethod - def get_prefix(name): - m = re.match(r"^([0-9])-", name) - if m: - return int(m.groups()[0]) - return None - - @staticmethod - def set_prefix(name, prefix, always=False): - m = not always and re.match(r"^([0-9])-", name) - return "%1d-%s" % (prefix, m and name[2:] or name) - - @property - def was_merged(self): - "Check if we have gallery size recorded" - return self.dest_size is not None - - def merge(self): - if not self.dest: - return self.src - if not self.src: - return self.dest - - files = listdir(self.path(self.dest)) - files.sort() - self.dest_size = len(files) - files_other = listdir(self.path(self.src)) - files_other.sort() - self.src_size = len(files_other) - - if files and files_other: - print "compare %s with %s" % (files[-1], files_other[0]) - if filecmp.cmp( - join(self.path(self.dest), files[-1]), - join(self.path(self.src), files_other[0]), - False - ): - files_other.pop(0) - self.num_deleted = 1 - - prefixes = {} - renamed_files = {} - renamed_files_other = {} - last_pfx = -1 - - # check if all elements of my files have a prefix - files_prefixed = True - for f in files: - p = self.get_prefix(f) - if p: - if p > last_pfx: last_pfx = p - else: - files_prefixed = False - break - - # if not, add a 0 prefix to them - if not files_prefixed: - prefixes[0] = 0 - for f in files: - renamed_files[f] = self.set_prefix(f, 0, True) - - # two cases here - either all are prefixed or not. - files_other_prefixed = True - for f in files_other: - pfx = self.get_prefix(f) - if pfx is not None: - if not pfx in prefixes: - last_pfx += 1 - prefixes[pfx] = last_pfx - renamed_files_other[f] = self.set_prefix(f, prefixes[pfx]) - else: - # ops, not all files here were prefixed. - files_other_prefixed = False - break - - # just set a 1- prefix to all of them - if not files_other_prefixed: - for f in files_other: - renamed_files_other[f] = self.set_prefix(f, 1, True) - - # finally, move / rename files. - for frm, to in renamed_files.items(): - move(join(self.path(self.dest), frm), - join(self.path(self.dest), to)) - for frm, to in renamed_files_other.items(): - move(join(self.path(self.src), frm), - join(self.path(self.dest), to)) - - rmtree(join(self.path(self.src))) - return self.dest diff --git a/apps/catalogue/locale/pl/LC_MESSAGES/django.mo b/apps/catalogue/locale/pl/LC_MESSAGES/django.mo deleted file mode 100644 index 87bdfbf9..00000000 Binary files a/apps/catalogue/locale/pl/LC_MESSAGES/django.mo and /dev/null differ diff --git a/apps/catalogue/locale/pl/LC_MESSAGES/django.po b/apps/catalogue/locale/pl/LC_MESSAGES/django.po deleted file mode 100644 index 6387b5d6..00000000 --- a/apps/catalogue/locale/pl/LC_MESSAGES/django.po +++ /dev/null @@ -1,804 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: Platforma Redakcyjna\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-12-20 12:30+0100\n" -"PO-Revision-Date: 2014-03-27 13:17+0100\n" -"Last-Translator: Radek Czajka \n" -"Language-Team: Fundacja Nowoczesna Polska \n" -"Language: pl\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 1.5.4\n" - -#: forms.py:40 -msgid "Text file must be UTF-8 encoded." -msgstr "Plik powinien mieć kodowanie UTF-8." - -#: forms.py:43 -msgid "You must either enter text or upload a file" -msgstr "Proszę wpisać tekst albo wybrać plik do załadowania" - -#: forms.py:52 -msgid "ZIP file" -msgstr "Plik ZIP" - -#: forms.py:53 -msgid "Directories are documents in chunks" -msgstr "Katalogi zawierają dokumenty w częściach" - -#: forms.py:77 forms.py:178 -msgid "Assigned to" -msgstr "Przypisane do" - -#: forms.py:98 forms.py:112 -msgid "Chunk with this slug already exists" -msgstr "Część z tym slugiem już istnieje" - -#: forms.py:121 -msgid "Append to" -msgstr "Dołącz do" - -#: views.py:172 -#, python-format -msgid "Slug already used for %s" -msgstr "Slug taki sam jak dla pliku %s" - -#: views.py:174 -msgid "Slug already used in repository." -msgstr "Dokument o tym slugu już istnieje w repozytorium." - -#: views.py:180 -msgid "File should be UTF-8 encoded." -msgstr "Plik powinien mieć kodowanie UTF-8." - -#: views.py:619 models/book.py:56 -msgid "books" -msgstr "książki" - -#: views.py:621 -msgid "scan gallery" -msgstr "galeria skanów" - -#: models/book.py:28 models/chunk.py:23 models/image.py:22 -msgid "title" -msgstr "tytuł" - -#: models/book.py:29 models/chunk.py:24 models/image.py:23 -msgid "slug" -msgstr "slug" - -#: models/book.py:30 models/image.py:24 -msgid "public" -msgstr "publiczna" - -#: models/book.py:31 -msgid "scan gallery name" -msgstr "nazwa galerii skanów" - -#: models/book.py:35 -msgid "parent" -msgstr "rodzic" - -#: models/book.py:36 -msgid "parent number" -msgstr "numeracja rodzica" - -#: models/book.py:55 models/chunk.py:21 models/publish_log.py:17 -msgid "book" -msgstr "książka" - -#: models/book.py:261 -msgid "No chunks in the book." -msgstr "Książka nie ma części." - -#: models/book.py:265 -msgid "Not all chunks have publishable revisions." -msgstr "Niektóre części nie są gotowe do publikacji." - -#: models/book.py:272 models/image.py:86 -msgid "Invalid XML" -msgstr "Nieprawidłowy XML" - -#: models/book.py:274 models/image.py:88 -msgid "No Dublin Core found." -msgstr "Brak sekcji Dublin Core." - -#: models/book.py:276 models/image.py:90 -msgid "Invalid Dublin Core" -msgstr "Nieprawidłowy Dublin Core" - -#: models/book.py:279 models/image.py:94 -msgid "rdf:about is not" -msgstr "rdf:about jest różny od" - -#: models/chunk.py:22 -msgid "number" -msgstr "numer" - -#: models/chunk.py:25 -msgid "gallery start" -msgstr "początek galerii" - -#: models/chunk.py:40 -msgid "chunk" -msgstr "część" - -#: models/chunk.py:41 -msgid "chunks" -msgstr "części" - -#: models/image.py:21 models/image.py:36 models/publish_log.py:45 -msgid "image" -msgstr "obraz" - -#: models/image.py:37 -msgid "images" -msgstr "obrazy" - -#: models/image.py:79 -msgid "There is no publishable revision" -msgstr "Żadna wersja nie została oznaczona do publikacji." - -#: models/project.py:13 -msgid "name" -msgstr "nazwa" - -#: models/project.py:14 -msgid "notes" -msgstr "notatki" - -#: models/project.py:19 templates/catalogue/image_table.html:58 -#: templates/catalogue/book_list/book_list.html:65 -msgid "project" -msgstr "projekt" - -#: models/project.py:20 -msgid "projects" -msgstr "projekty" - -#: models/publish_log.py:18 models/publish_log.py:46 -msgid "time" -msgstr "czas" - -#: models/publish_log.py:19 models/publish_log.py:47 -#: templates/catalogue/wall.html:20 -msgid "user" -msgstr "użytkownik" - -#: models/publish_log.py:24 models/publish_log.py:33 -msgid "book publish record" -msgstr "zapis publikacji książki" - -#: models/publish_log.py:25 -msgid "book publish records" -msgstr "zapisy publikacji książek" - -#: models/publish_log.py:34 models/publish_log.py:48 -msgid "change" -msgstr "zmiana" - -#: models/publish_log.py:38 -msgid "chunk publish record" -msgstr "zapis publikacji części" - -#: models/publish_log.py:39 -msgid "chunk publish records" -msgstr "zapisy publikacji części" - -#: models/publish_log.py:53 -msgid "image publish record" -msgstr "zapis publikacji obrazu" - -#: models/publish_log.py:54 -msgid "image publish records" -msgstr "zapisy publikacji obrazów" - -#: templates/catalogue/active_users_list.html:5 -msgid "Active users" -msgstr "Aktywni użytkownicy" - -#: templates/catalogue/active_users_list.html:11 -msgid "Active users since" -msgstr "Użytkownicy aktywni od" - -#: templates/catalogue/activity.html:6 templates/catalogue/activity.html:12 -#: templatetags/catalogue.py:29 -msgid "Activity" -msgstr "Aktywność" - -#: templates/catalogue/base.html:10 -msgid "Platforma Redakcyjna" -msgstr "Platforma Redakcyjna" - -#: templates/catalogue/book_append_to.html:4 -#: templates/catalogue/book_append_to.html:11 -msgid "Append book" -msgstr "Dołącz książkę" - -#: templates/catalogue/book_detail.html:18 -#: templates/catalogue/book_edit.html:13 templates/catalogue/chunk_edit.html:16 -#: templates/catalogue/image_detail.html:18 -msgid "Save" -msgstr "Zapisz" - -#: templates/catalogue/book_detail.html:25 -msgid "Edit gallery" -msgstr "Edytuj galerię" - -#: templates/catalogue/book_detail.html:28 -msgid "Append to other book" -msgstr "Dołącz do innej książki" - -#: templates/catalogue/book_detail.html:34 -msgid "Chunks" -msgstr "Części" - -#: templates/catalogue/book_detail.html:49 -#: templates/catalogue/image_detail.html:36 templatetags/wall.py:108 -#: templatetags/wall.py:129 -msgid "Publication" -msgstr "Publikacja" - -#: templates/catalogue/book_detail.html:58 -#: templates/catalogue/image_detail.html:38 -msgid "Last published" -msgstr "Ostatnio opublikowano" - -#: templates/catalogue/book_detail.html:68 -msgid "Full XML" -msgstr "Pełny XML" - -#: templates/catalogue/book_detail.html:69 -msgid "HTML version" -msgstr "Wersja HTML" - -#: templates/catalogue/book_detail.html:70 -msgid "TXT version" -msgstr "Wersja TXT" - -#: templates/catalogue/book_detail.html:71 -msgid "PDF version" -msgstr "Wersja PDF" - -#: templates/catalogue/book_detail.html:72 -msgid "PDF version for mobiles" -msgstr "Wersja PDF na telefony" - -#: templates/catalogue/book_detail.html:73 -msgid "EPUB version" -msgstr "Wersja EPUB" - -#: templates/catalogue/book_detail.html:74 -msgid "MOBI version" -msgstr "Wersja MOBI" - -#: templates/catalogue/book_detail.html:88 -#: templates/catalogue/image_detail.html:57 -msgid "Publish" -msgstr "Opublikuj" - -#: templates/catalogue/book_detail.html:92 -#: templates/catalogue/image_detail.html:61 -msgid "Log in to publish." -msgstr "Zaloguj się, aby opublikować." - -#: templates/catalogue/book_detail.html:95 -#: templates/catalogue/image_detail.html:64 -msgid "This book can't be published yet, because:" -msgstr "Ta książka nie może jeszcze zostać opublikowana. Powód:" - -#: templates/catalogue/book_edit.html:5 -msgid "Edit book" -msgstr "Edytuj książkę" - -#: templates/catalogue/book_html.html:12 templates/catalogue/book_text.html:15 -msgid "Table of contents" -msgstr "Spis treści" - -#: templates/catalogue/book_html.html:13 templates/catalogue/book_text.html:17 -msgid "Edit. note" -msgstr "Nota red." - -#: templates/catalogue/book_html.html:14 -msgid "Infobox" -msgstr "Informacje" - -#: templates/catalogue/book_text.html:7 -msgid "Redakcja" -msgstr "" - -#: templates/catalogue/chunk_add.html:5 templates/catalogue/chunk_add.html:9 -#: templates/catalogue/chunk_edit.html:22 -msgid "Split chunk" -msgstr "Podziel część" - -#: templates/catalogue/chunk_add.html:14 -msgid "Insert empty chunk after" -msgstr "Wstaw pustą część po" - -#: templates/catalogue/chunk_add.html:17 -msgid "Add chunk" -msgstr "Dodaj część" - -#: templates/catalogue/chunk_edit.html:5 templates/catalogue/chunk_edit.html:9 -#: templates/catalogue/book_list/book.html:9 -#: templates/catalogue/book_list/chunk.html:7 -msgid "Chunk settings" -msgstr "Ustawienia części" - -#: templates/catalogue/chunk_edit.html:14 -msgid "Book" -msgstr "Książka" - -#: templates/catalogue/document_create_missing.html:5 -#: templates/catalogue/document_create_missing.html:9 -msgid "Create a new book" -msgstr "Utwórz nową książkę" - -#: templates/catalogue/document_create_missing.html:15 -msgid "Create book" -msgstr "Utwórz książkę" - -#: templates/catalogue/document_list.html:7 -msgid "Book list" -msgstr "Lista książek" - -#: templates/catalogue/document_upload.html:5 -msgid "Bulk document upload" -msgstr "Hurtowe dodawanie dokumentów" - -#: templates/catalogue/document_upload.html:11 -msgid "Bulk documents upload" -msgstr "Hurtowe dodawanie dokumentów" - -#: templates/catalogue/document_upload.html:14 -msgid "" -"Please submit a ZIP with UTF-8 encoded XML files. Files not ending with " -".xml will be ignored." -msgstr "" -"Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie " -"kończące się na .xml zostaną zignorowane." - -#: templates/catalogue/document_upload.html:20 -#: templates/catalogue/upload_pdf.html:16 templatetags/catalogue.py:36 -msgid "Upload" -msgstr "Załaduj" - -#: templates/catalogue/document_upload.html:27 -msgid "" -"There have been some errors. No files have been added to the repository." -msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium." - -#: templates/catalogue/document_upload.html:28 -msgid "Offending files" -msgstr "Błędne pliki" - -#: templates/catalogue/document_upload.html:36 -msgid "Correct files" -msgstr "Poprawne pliki" - -#: templates/catalogue/document_upload.html:47 -msgid "Files have been successfully uploaded to the repository." -msgstr "Pliki zostały dodane do repozytorium." - -#: templates/catalogue/document_upload.html:48 -msgid "Uploaded files" -msgstr "Dodane pliki" - -#: templates/catalogue/document_upload.html:58 -msgid "Skipped files" -msgstr "Pominięte pliki" - -#: templates/catalogue/document_upload.html:59 -msgid "Files skipped due to no .xml extension" -msgstr "Pliki pominięte z powodu braku rozszerzenia .xml." - -#: templates/catalogue/image_detail.html:26 -msgid "Editor" -msgstr "Edytor" - -#: templates/catalogue/image_detail.html:28 -msgid "Proceed to the editor." -msgstr "Przejdź do edytora." - -#: templates/catalogue/image_list.html:8 -msgid "Image list" -msgstr "Lista obrazów" - -#: templates/catalogue/image_short.html:6 -msgid "Image settings" -msgstr "Ustawienia obrazu" - -#: templates/catalogue/image_table.html:23 -#: templates/catalogue/book_list/book_list.html:28 -msgid "Search in book titles" -msgstr "Szukaj w tytułach książek" - -#: templates/catalogue/image_table.html:28 -#: templates/catalogue/book_list/book_list.html:33 -msgid "stage" -msgstr "etap" - -#: templates/catalogue/image_table.html:30 -#: templates/catalogue/image_table.html:41 -#: templates/catalogue/image_table.html:60 -#: templates/catalogue/book_list/book_list.html:35 -#: templates/catalogue/book_list/book_list.html:46 -#: templates/catalogue/book_list/book_list.html:67 -msgid "none" -msgstr "brak" - -#: templates/catalogue/image_table.html:39 -#: templates/catalogue/book_list/book_list.html:44 -msgid "editor" -msgstr "redaktor" - -#: templates/catalogue/image_table.html:50 -#: templates/catalogue/book_list/book_list.html:57 -msgid "status" -msgstr "status" - -#: templates/catalogue/image_table.html:77 -#, python-format -msgid "%(c)s image" -msgid_plural "%(c)s images" -msgstr[0] "%(c)s obraz" -msgstr[1] "%(c)s obrazy" -msgstr[2] "%(c)s obrazów" - -#: templates/catalogue/image_table.html:82 -msgid "No images found." -msgstr "Nie znaleziono obrazów." - -#: templates/catalogue/image_table.html:88 -#: templates/catalogue/book_list/book_list.html:102 -msgid "Set stage" -msgstr "Ustaw etap" - -#: templates/catalogue/image_table.html:89 -#: templates/catalogue/book_list/book_list.html:103 -msgid "Set user" -msgstr "Przypisz redaktora" - -#: templates/catalogue/image_table.html:91 -#: templates/catalogue/book_list/book_list.html:105 -msgid "Project" -msgstr "Projekt" - -#: templates/catalogue/image_table.html:92 -#: templates/catalogue/book_list/book_list.html:106 -msgid "More users" -msgstr "Więcej użytkowników" - -#: templates/catalogue/my_page.html:15 templatetags/catalogue.py:27 -msgid "My page" -msgstr "Moja strona" - -#: templates/catalogue/my_page.html:24 -msgid "Your last edited documents" -msgstr "Twoje ostatnie edycje" - -#: templates/catalogue/my_page.html:39 templates/catalogue/user_page.html:16 -msgid "Recent activity for" -msgstr "Ostatnia aktywność dla:" - -#: templates/catalogue/upload_pdf.html:5 templates/catalogue/upload_pdf.html:11 -msgid "PDF file upload" -msgstr "Ładowanie pliku PDF" - -#: templates/catalogue/user_list.html:7 templates/catalogue/user_list.html:12 -#: templatetags/catalogue.py:32 -msgid "Users" -msgstr "Użytkownicy" - -#: templates/catalogue/wall.html:30 -msgid "not logged in" -msgstr "nie zalogowany" - -#: templates/catalogue/wall.html:35 -msgid "No activity recorded." -msgstr "Nie zanotowano aktywności." - -#: templates/catalogue/book_list/book.html:8 -#: templates/catalogue/book_list/book.html:29 -msgid "Book settings" -msgstr "Ustawienia książki" - -#: templates/catalogue/book_list/book_list.html:23 -msgid "Show hidden books" -msgstr "Pokaż ukryte książki" - -#: templates/catalogue/book_list/book_list.html:91 -#, python-format -msgid "%(c)s book" -msgid_plural "%(c)s books" -msgstr[0] "%(c)s książka" -msgstr[1] "%(c)s książki" -msgstr[2] "%(c)s książek" - -#: templates/catalogue/book_list/book_list.html:96 -msgid "No books found." -msgstr "Nie znaleziono książek." - -#: templatetags/book_list.py:84 templatetags/book_list.py:152 -msgid "publishable" -msgstr "do publikacji" - -#: templatetags/book_list.py:85 templatetags/book_list.py:153 -msgid "changed" -msgstr "zmienione" - -#: templatetags/book_list.py:86 templatetags/book_list.py:154 -msgid "published" -msgstr "opublikowane" - -#: templatetags/book_list.py:87 templatetags/book_list.py:155 -msgid "unpublished" -msgstr "nie opublikowane" - -#: templatetags/book_list.py:88 templatetags/book_list.py:156 -msgid "empty" -msgstr "puste" - -#: templatetags/catalogue.py:30 -msgid "All" -msgstr "Wszystkie" - -#: templatetags/catalogue.py:31 -msgid "Images" -msgstr "Obrazy" - -#: templatetags/catalogue.py:35 -msgid "Add" -msgstr "Dodaj" - -#: templatetags/catalogue.py:38 -msgid "Covers" -msgstr "Okładki" - -#: templatetags/wall.py:49 templatetags/wall.py:78 -msgid "Related edit" -msgstr "Powiązana zmiana" - -#: templatetags/wall.py:51 templatetags/wall.py:80 -msgid "Edit" -msgstr "Zmiana" - -#: templatetags/wall.py:150 -msgid "Comment" -msgstr "Komentarz" - -#~ msgid "Comments" -#~ msgstr "Komentarze" - -#~ msgid "Mark publishable" -#~ msgstr "Oznacz do publikacji" - -#~ msgid "Mark not publishable" -#~ msgstr "Odznacz do publikacji" - -#~ msgid "Other user" -#~ msgstr "Inny użytkownik" - -#~ msgid "Admin" -#~ msgstr "Administracja" - -#~ msgid "edit" -#~ msgstr "edytuj" - -#~ msgid "add basic document structure" -#~ msgstr "dodaj podstawową strukturę dokumentu" - -#~ msgid "change master tag to" -#~ msgstr "zmień tak master na" - -#~ msgid "add begin trimming tag" -#~ msgstr "dodaj początkowy ogranicznik" - -#~ msgid "add end trimming tag" -#~ msgstr "dodaj końcowy ogranicznik" - -#~ msgid "unstructured text" -#~ msgstr "tekst bez struktury" - -#~ msgid "unknown XML" -#~ msgstr "nieznany XML" - -#~ msgid "broken document" -#~ msgstr "uszkodzony dokument" - -#~ msgid "Apply fixes" -#~ msgstr "Wykonaj zmiany" - -#~ msgid "Can mark for publishing" -#~ msgstr "Oznacza do publikacji" - -#~ msgid "Author" -#~ msgstr "Autor" - -#~ msgid "Your name" -#~ msgstr "Imię i nazwisko" - -#~ msgid "Author's email" -#~ msgstr "E-mail autora" - -#~ msgid "Your email address, so we can show a gravatar :)" -#~ msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)" - -#~ msgid "Describe changes you made." -#~ msgstr "Opisz swoje zmiany" - -#~ msgid "Completed" -#~ msgstr "Ukończono" - -#~ msgid "If you completed a life cycle stage, select it." -#~ msgstr "Jeśli został ukończony etap prac, wskaż go." - -#~ msgid "Describe the reason for reverting." -#~ msgstr "Opisz powód przywrócenia." - -#~ msgid "theme" -#~ msgstr "motyw" - -#~ msgid "themes" -#~ msgstr "motywy" - -#~ msgid "Tag added" -#~ msgstr "Dodano tag" - -#~ msgid "Revision marked" -#~ msgstr "Wersja oznaczona" - -#~ msgid "New version" -#~ msgstr "Nowa wersja" - -#~ msgid "Click to open/close gallery" -#~ msgstr "Kliknij, aby (ro)zwinąć galerię" - -#~ msgid "Help" -#~ msgstr "Pomoc" - -#~ msgid "Version" -#~ msgstr "Wersja" - -#~ msgid "Unknown" -#~ msgstr "nieznana" - -#~ msgid "Save attempt in progress" -#~ msgstr "Trwa zapisywanie" - -#~ msgid "There is a newer version of this document!" -#~ msgstr "Istnieje nowsza wersja tego dokumentu!" - -#~ msgid "Clear filter" -#~ msgstr "Wyczyść filtr" - -#~ msgid "Cancel" -#~ msgstr "Anuluj" - -#~ msgid "Revert" -#~ msgstr "Przywróć" - -#~ msgid "all" -#~ msgstr "wszystkie" - -#~ msgid "Annotations" -#~ msgstr "Przypisy" - -#~ msgid "Previous" -#~ msgstr "Poprzednie" - -#~ msgid "Next" -#~ msgstr "Następne" - -#~ msgid "Zoom in" -#~ msgstr "Powiększ" - -#~ msgid "Zoom out" -#~ msgstr "Zmniejsz" - -#~ msgid "Gallery" -#~ msgstr "Galeria" - -#~ msgid "Compare versions" -#~ msgstr "Porównaj wersje" - -#~ msgid "Revert document" -#~ msgstr "Przywróć wersję" - -#~ msgid "View version" -#~ msgstr "Zobacz wersję" - -#~ msgid "History" -#~ msgstr "Historia" - -#~ msgid "Search" -#~ msgstr "Szukaj" - -#~ msgid "Replace with" -#~ msgstr "Zamień na" - -#~ msgid "Replace" -#~ msgstr "Zamień" - -#~ msgid "Options" -#~ msgstr "Opcje" - -#~ msgid "Case sensitive" -#~ msgstr "Rozróżniaj wielkość liter" - -#~ msgid "From cursor" -#~ msgstr "Zacznij od kursora" - -#~ msgid "Search and replace" -#~ msgstr "Znajdź i zamień" - -#~ msgid "Source code" -#~ msgstr "Kod źródłowy" - -#~ msgid "Title" -#~ msgstr "Tytuł" - -#~ msgid "Document ID" -#~ msgstr "ID dokumentu" - -#~ msgid "Current version" -#~ msgstr "Aktualna wersja" - -#~ msgid "Last edited by" -#~ msgstr "Ostatnio edytowane przez" - -#~ msgid "Summary" -#~ msgstr "Podsumowanie" - -#~ msgid "Insert theme" -#~ msgstr "Wstaw motyw" - -#~ msgid "Insert annotation" -#~ msgstr "Wstaw przypis" - -#~ msgid "Visual editor" -#~ msgstr "Edytor wizualny" - -#~ msgid "Unassigned" -#~ msgstr "Nie przypisane" - -#~ msgid "First correction" -#~ msgstr "Autokorekta" - -#~ msgid "Tagging" -#~ msgstr "Tagowanie" - -#~ msgid "Initial Proofreading" -#~ msgstr "Korekta" - -#~ msgid "Annotation Proofreading" -#~ msgstr "Sprawdzenie przypisów źródła" - -#~ msgid "Modernisation" -#~ msgstr "Uwspółcześnienie" - -#~ msgid "Themes" -#~ msgstr "Motywy" - -#~ msgid "Editor's Proofreading" -#~ msgstr "Ostateczna redakcja literacka" - -#~ msgid "Technical Editor's Proofreading" -#~ msgstr "Ostateczna redakcja techniczna" - -#~ msgid "Finished stage: %s" -#~ msgstr "Ukończony etap: %s" - -#~ msgid "Refresh" -#~ msgstr "Odśwież" diff --git a/apps/catalogue/management/__init__.py b/apps/catalogue/management/__init__.py deleted file mode 100644 index bc3d6c02..00000000 --- a/apps/catalogue/management/__init__.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from collections import defaultdict -from django.db import transaction -from lxml import etree - - -class XmlUpdater(object): - """A base class for massive XML updates. - - In a subclass, override `fix_tree` and/or use `fixes_field` decorator. - Attributes: - * commit_desc: commits description - * retain_publishable: set publishable if head is (default: True) - * only_first_chunk: process only first chunks of books (default: False) - """ - commit_desc = "auto-update" - retain_publishable = True - only_first_chunk = False - - _element_fixers = defaultdict(list) - - def __init__(self): - self.counters = defaultdict(lambda: 0) - - @classmethod - def fixes_elements(cls, xpath): - """Decorator, registering a function as a fixer for given field type. - - Any decorated function will be called like - f(element, change=..., verbose=...) - providing changeset as context. - - :param xpath: element lookup, e.g. ".//{namespace-uri}tag-name" - :returns: True if anything changed - """ - def wrapper(fixer): - cls._element_fixers[xpath].append(fixer) - return fixer - return wrapper - - def fix_tree(self, tree, verbose): - """Override to provide general tree-fixing mechanism. - - :param tree: the parsed XML tree - :param verbose: verbosity level - :returns: True if anythig changed - """ - return False - - def fix_chunk(self, chunk, user, verbose=0, dry_run=False): - """Runs the update for a single chunk.""" - if verbose >= 2: - print chunk.get_absolute_url() - old_head = chunk.head - src = old_head.materialize() - try: - tree = etree.fromstring(src) - except: - if verbose: - print "%s: invalid XML" % chunk.get_absolute_url() - self.counters['Bad XML'] += 1 - return - - dirty = False - # Call the general fixing function. - if self.fix_tree(tree, verbose=verbose): - dirty = True - # Call the registered fixers. - for xpath, fixers in self._element_fixers.items(): - for elem in tree.findall(xpath): - for fixer in fixers: - if fixer(elem, change=old_head, verbose=verbose): - dirty = True - - if not dirty: - self.counters['Clean'] += 1 - return - - if not dry_run: - new_head = chunk.commit( - etree.tostring(tree, encoding=unicode), - author=user, - description=self.commit_desc - ) - if self.retain_publishable: - if old_head.publishable: - new_head.set_publishable(True) - if verbose >= 2: - print "done" - self.counters['Updated chunks'] += 1 - - def run(self, user, verbose=0, dry_run=False, books=None): - """Runs the actual update.""" - if books is None: - from catalogue.models import Book - books = Book.objects.all() - - # Start transaction management. - transaction.enter_transaction_management() - - for book in books: - self.counters['All books'] += 1 - chunks = book.chunk_set.all() - if self.only_first_chunk: - chunks = chunks[:1] - for chunk in chunks: - self.counters['All chunks'] += 1 - self.fix_chunk(chunk, user, verbose, dry_run) - - transaction.commit() - transaction.leave_transaction_management() - - def print_results(self): - """Prints the counters.""" - for item in sorted(self.counters.items()): - print "%s: %d" % item diff --git a/apps/catalogue/management/commands/__init__.py b/apps/catalogue/management/commands/__init__.py deleted file mode 100644 index e6f146f8..00000000 --- a/apps/catalogue/management/commands/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -import sys -from optparse import make_option -from django.contrib.auth.models import User -from django.core.management.base import BaseCommand -from catalogue.models import Book - - -class XmlUpdaterCommand(BaseCommand): - """Base class for creating massive XML-updating commands. - - In a subclass, provide an XmlUpdater class in the `updater' attribute. - """ - option_list = BaseCommand.option_list + ( - make_option('-q', '--quiet', action='store_false', dest='verbose', - default=True, help='Less output'), - make_option('-d', '--dry-run', action='store_true', dest='dry_run', - default=False, help="Don't actually touch anything"), - make_option('-u', '--username', dest='username', metavar='USER', - help='Assign commits to this user (required, preferably yourself).'), - ) - args = "[slug]..." - - def handle(self, *args, **options): - verbose = options.get('verbose') - dry_run = options.get('dry_run') - username = options.get('username') - - if username: - user = User.objects.get(username=username) - else: - print 'Please provide a username.' - sys.exit(1) - - books = Book.objects.filter(slug__in=args) if args else None - - updater = self.updater() - updater.run(user, verbose=verbose, dry_run=dry_run, books=books) - updater.print_results() diff --git a/apps/catalogue/management/commands/add_parent.py b/apps/catalogue/management/commands/add_parent.py deleted file mode 100644 index 2ab0510c..00000000 --- a/apps/catalogue/management/commands/add_parent.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -import sys - -from datetime import date -from lxml import etree - -from django.core.management import BaseCommand - -from catalogue.models import Book -from librarian import RDFNS, DCNS - -TEMPLATE = ''' - - -%(dc)s - - - - -''' - -DC_TEMPLATE = '%(value)s' - -DC_TAGS = ( - 'creator', - 'title', - 'relation.hasPart', - 'contributor.translator', - 'contributor.editor', - 'contributor.technical_editor', - 'contributor.funding', - 'contributor.thanks', - 'publisher', - 'subject.period', - 'subject.type', - 'subject.genre', - 'description', - 'identifier.url', - 'source', - 'source.URL', - 'rights.license', - 'rights', - 'date.pd', - 'format', - 'type', - 'date', - 'audience', - 'language', -) - -IDENTIFIER_PREFIX = 'http://wolnelektury.pl/katalog/lektura/' - - -def dc_desc_element(book): - xml = book.materialize() - tree = etree.fromstring(xml) - return tree.find(".//" + RDFNS("Description")) - - -def distinct_dc_values(tag, desc_elements): - values = set() - for desc in desc_elements: - values.update(elem.text for elem in desc.findall(DCNS(tag))) - return values - - -class Command(BaseCommand): - args = 'slug' - - def handle(self, slug, **options): - children_slugs = [line.strip() for line in sys.stdin] - children = Book.objects.filter(dc_slug__in=children_slugs) - desc_elements = [dc_desc_element(child) for child in children] - title = u'Utwory wybrane' - own_attributes = { - 'title': title, - 'relation.hasPart': [IDENTIFIER_PREFIX + child_slug for child_slug in children_slugs], - 'identifier.url': IDENTIFIER_PREFIX + slug, - 'date': date.today().isoformat(), - } - dc_tags = [] - for tag in DC_TAGS: - if tag in own_attributes: - values = own_attributes[tag] - if not isinstance(values, list): - values = [values] - else: - values = distinct_dc_values(tag, desc_elements) - for value in values: - dc_tags.append(DC_TEMPLATE % {'tag': tag, 'value': value}) - xml = TEMPLATE % {'slug': slug, 'dc': '\n'.join(dc_tags)} - Book.create( - text=xml, - creator=None, - slug=slug, - title=title, - gallery=slug) diff --git a/apps/catalogue/management/commands/assign_from_redmine.py b/apps/catalogue/management/commands/assign_from_redmine.py deleted file mode 100644 index 491fd832..00000000 --- a/apps/catalogue/management/commands/assign_from_redmine.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- - -import csv -from optparse import make_option -import re -import sys -import urllib -import urllib2 - -from django.contrib.auth.models import User -from django.core.management.base import BaseCommand -from django.core.management.color import color_style -from django.db import transaction - -from slugify import slugify -from catalogue.models import Chunk - - -REDMINE_CSV = 'http://redmine.nowoczesnapolska.org.pl/projects/wl-publikacje/issues.csv' -REDAKCJA_URL = 'http://redakcja.wolnelektury.pl/documents/' - - -class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('-r', '--redakcja', dest='redakcja', metavar='URL', - help='Base URL of Redakcja documents', - default=REDAKCJA_URL), - make_option('-q', '--quiet', action='store_false', dest='verbose', default=True, - help='Less output'), - make_option('-f', '--force', action='store_true', dest='force', default=False, - help='Force assignment overwrite'), - ) - help = 'Imports ticket assignments from Redmine.' - args = '[redmine-csv-url]' - - def handle(self, *redmine_csv, **options): - - self.style = color_style() - - redakcja = options.get('redakcja') - verbose = options.get('verbose') - force = options.get('force') - - if not redmine_csv: - if verbose: - print "Using default Redmine CSV URL:", REDMINE_CSV - redmine_csv = REDMINE_CSV - - # Start transaction management. - transaction.enter_transaction_management() - - redakcja_link = re.compile(re.escape(redakcja) + r'([-_.:?&%/a-zA-Z0-9]*)') - - all_tickets = 0 - all_chunks = 0 - done_tickets = 0 - done_chunks = 0 - empty_users = 0 - unknown_users = {} - unknown_books = [] - forced = [] - - if verbose: - print 'Downloading CSV file' - for r in csv.reader(urllib2.urlopen(redmine_csv)): - if r[0] == '#': - continue - all_tickets += 1 - - username = r[6] - if not username: - if verbose: - print "Empty user, skipping" - empty_users += 1 - continue - - first_name, last_name = unicode(username, 'utf-8').rsplit(u' ', 1) - try: - user = User.objects.get(first_name=first_name, last_name=last_name) - except User.DoesNotExist: - print self.style.ERROR('Unknown user: ' + username) - unknown_users.setdefault(username, 0) - unknown_users[username] += 1 - continue - - ticket_done = False - for fname in redakcja_link.findall(r[-1]): - fname = unicode(urllib.unquote(fname), 'utf-8', 'ignore') - if fname.endswith('.xml'): - fname = fname[:-4] - fname = fname.replace(' ', '_') - fname = slugify(fname) - - chunks = Chunk.objects.filter(book__slug=fname) - if not chunks: - print self.style.ERROR('Unknown book: ' + fname) - unknown_books.append(fname) - continue - all_chunks += chunks.count() - - for chunk in chunks: - if chunk.user: - if chunk.user == user: - continue - else: - forced.append((chunk, chunk.user, user)) - if force: - print self.style.WARNING( - '%s assigned to %s, forcing change to %s.' % - (chunk.pretty_name(), chunk.user, user)) - else: - print self.style.WARNING( - '%s assigned to %s not to %s, skipping.' % - (chunk.pretty_name(), chunk.user, user)) - continue - chunk.user = user - chunk.save() - ticket_done = True - done_chunks += 1 - - if ticket_done: - done_tickets += 1 - - - # Print results - print - print "Results:" - print "Assignments imported from %d/%d tickets to %d/%d relevalt chunks." % ( - done_tickets, all_tickets, done_chunks, all_chunks) - if empty_users: - print "%d tickets were unassigned." % empty_users - if forced: - print "%d assignments conficts (%s):" % ( - len(forced), "changed" if force else "left") - for chunk, orig, user in forced: - print " %s: \t%s \t-> %s" % ( - chunk.pretty_name(), orig.username, user.username) - if unknown_books: - print "%d unknown books:" % len(unknown_books) - for fname in unknown_books: - print " %s" % fname - if unknown_users: - print "%d unknown users:" % len(unknown_users) - for name in unknown_users: - print " %s (%d tickets)" % (name, unknown_users[name]) - print - - - transaction.commit() - transaction.leave_transaction_management() - diff --git a/apps/catalogue/management/commands/fixdc.py b/apps/catalogue/management/commands/fixdc.py deleted file mode 100644 index 3f997d0c..00000000 --- a/apps/catalogue/management/commands/fixdc.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from librarian import RDFNS, WLURI, ValidationError -from librarian.dcparser import BookInfo -from catalogue.management import XmlUpdater -from catalogue.management.commands import XmlUpdaterCommand - - -class FixDC(XmlUpdater): - commit_desc = "auto-fixing DC" - retain_publishable = True - only_first_chunk = True - - def fix_wluri(elem, change, verbose): - try: - WLURI.strict(elem.text) - except ValidationError: - correct_field = unicode(WLURI.from_slug( - WLURI(elem.text.strip()).slug)) - try: - WLURI.strict(correct_field) - except ValidationError: - # Can't make a valid WLURI out of it, leave as is. - return False - if verbose: - print "Changing %s from %s to %s" % ( - elem.tag, elem.text, correct_field - ) - elem.text = correct_field - return True - for field in BookInfo.FIELDS: - if field.validator == WLURI: - XmlUpdater.fixes_elements('.//' + field.uri)(fix_wluri) - - @XmlUpdater.fixes_elements(".//" + RDFNS("Description")) - def fix_rdfabout(elem, change, verbose): - correct_about = change.tree.book.correct_about() - attr_name = RDFNS("about") - current_about = elem.get(attr_name) - if current_about != correct_about: - if verbose: - print "Changing rdf:about from %s to %s" % ( - current_about, correct_about - ) - elem.set(attr_name, correct_about) - return True - - -class Command(XmlUpdaterCommand): - updater = FixDC - help = 'Fixes obvious errors in DC: rdf:about and WLURI format.' diff --git a/apps/catalogue/management/commands/import_wl.py b/apps/catalogue/management/commands/import_wl.py deleted file mode 100644 index 45c9e331..00000000 --- a/apps/catalogue/management/commands/import_wl.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- - -from collections import defaultdict -import json -from optparse import make_option -import urllib2 - -from django.core.management.base import BaseCommand -from django.core.management.color import color_style -from django.db import transaction -from librarian.dcparser import BookInfo -from librarian import ParseError, ValidationError - -from catalogue.models import Book - - -WL_API = 'http://www.wolnelektury.pl/api/books/' - - -class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('-q', '--quiet', action='store_false', dest='verbose', default=True, - help='Less output'), - ) - help = 'Imports XML files from WL.' - - def handle(self, *args, **options): - - self.style = color_style() - - verbose = options.get('verbose') - - # Start transaction management. - transaction.enter_transaction_management() - - if verbose: - print 'Reading currently managed files (skipping hidden ones).' - slugs = defaultdict(list) - for b in Book.objects.exclude(slug__startswith='.').all(): - if verbose: - print b.slug - text = b.materialize().encode('utf-8') - try: - info = BookInfo.from_bytes(text) - except (ParseError, ValidationError): - pass - else: - slugs[info.slug].append(b) - - book_count = 0 - commit_args = { - "author_name": 'Platforma', - "description": 'Automatycznie zaimportowane z Wolnych Lektur', - "publishable": True, - } - - if verbose: - print 'Opening books list' - for book in json.load(urllib2.urlopen(WL_API)): - book_detail = json.load(urllib2.urlopen(book['href'])) - xml_text = urllib2.urlopen(book_detail['xml']).read() - info = BookInfo.from_bytes(xml_text) - previous_books = slugs.get(info.slug) - if previous_books: - if len(previous_books) > 1: - print self.style.ERROR("There is more than one book " - "with slug %s:"), - previous_book = previous_books[0] - comm = previous_book.slug - else: - previous_book = None - comm = '*' - print book_count, info.slug , '-->', comm - Book.import_xml_text(xml_text, title=info.title[:255], - slug=info.slug[:128], previous_book=previous_book, - commit_args=commit_args) - book_count += 1 - - # Print results - print - print "Results:" - print "Imported %d books from WL:" % ( - book_count, ) - print - - - transaction.commit() - transaction.leave_transaction_management() - diff --git a/apps/catalogue/management/commands/insert_isbn.py b/apps/catalogue/management/commands/insert_isbn.py deleted file mode 100644 index 7548cb1f..00000000 --- a/apps/catalogue/management/commands/insert_isbn.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -import csv - -import sys -from django.contrib.auth.models import User -from lxml import etree -from optparse import make_option - -from collections import defaultdict -from django.core.management import BaseCommand - -from catalogue.models import Book -from librarian import RDFNS, DCNS - -CONTENT_TYPES = { - 'pdf': 'application/pdf', - 'epub': 'application/epub+zip', - 'mobi': 'application/x-mobipocket-ebook', - 'txt': 'text/plain', - 'html': 'text/html', -} - - -ISBN_TEMPLATES = ( - r'%(url)s' - r'', - r'ISBN-%(isbn)s', - r'ISBN', - r'%(content_type)s', -) - - -def url_for_format(slug, format): - if format == 'html': - return 'https://wolnelektury.pl/katalog/lektura/%s.html' % slug - else: - return 'http://wolnelektury.pl/media/book/%(format)s/%(slug)s.%(format)s' % {'slug': slug, 'format': format} - - -class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - # make_option('-q', '--quiet', action='store_false', dest='verbose', - # default=True, help='Less output'), - # make_option('-d', '--dry-run', action='store_true', dest='dry_run', - # default=False, help="Don't actually touch anything"), - make_option( - '-u', '--username', dest='username', metavar='USER', - help='Assign commits to this user (required, preferably yourself).'), - ) - args = 'csv_file' - - def handle(self, csv_file, **options): - username = options.get('username') - - if username: - user = User.objects.get(username=username) - else: - print 'Please provide a username.' - sys.exit(1) - - csvfile = open(csv_file, 'rb') - isbn_lists = defaultdict(list) - for slug, format, isbn in csv.reader(csvfile, delimiter=','): - isbn_lists[slug].append((format, isbn)) - csvfile.close() - - for slug, isbn_list in isbn_lists.iteritems(): - print 'processing %s' % slug - book = Book.objects.get(dc_slug=slug) - chunk = book.chunk_set.first() - old_head = chunk.head - src = old_head.materialize() - tree = etree.fromstring(src) - isbn_node = tree.find('.//' + DCNS("relation.hasFormat")) - if isbn_node is not None: - print '%s already contains ISBN metadata, skipping' % slug - continue - desc = tree.find(".//" + RDFNS("Description")) - for format, isbn in isbn_list: - for template in ISBN_TEMPLATES: - isbn_xml = template % { - 'format': format, - 'isbn': isbn, - 'content_type': CONTENT_TYPES[format], - 'url': url_for_format(slug, format), - } - element = etree.XML(isbn_xml) - element.tail = '\n' - desc.append(element) - new_head = chunk.commit( - etree.tostring(tree, encoding=unicode), - author=user, - description='automatyczne dodanie isbn' - ) - print 'committed %s' % slug - if old_head.publishable: - new_head.set_publishable(True) - else: - print 'Warning: %s not publishable' % slug diff --git a/apps/catalogue/management/commands/mark_final.py b/apps/catalogue/management/commands/mark_final.py deleted file mode 100644 index cdfaab93..00000000 --- a/apps/catalogue/management/commands/mark_final.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -import sys -from django.contrib.auth.models import User -from optparse import make_option - -from django.core.management import BaseCommand - -from catalogue.models import Book, Chunk - - -class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - # make_option('-q', '--quiet', action='store_false', dest='verbose', - # default=True, help='Less output'), - # make_option('-d', '--dry-run', action='store_true', dest='dry_run', - # default=False, help="Don't actually touch anything"), - make_option( - '-u', '--username', dest='username', metavar='USER', - help='Assign commits to this user (required).'), - ) - args = 'slug_file' - - def handle(self, slug_file, **options): - username = options.get('username') - - if username: - user = User.objects.get(username=username) - else: - print 'Please provide a username.' - sys.exit(1) - - slugs = [line.strip() for line in open(slug_file)] - books = Book.objects.filter(slug__in=slugs) - - for book in books: - print 'processing %s' % book.slug - for chunk in book.chunk_set.all(): - src = chunk.head.materialize() - chunk.commit( - text=src, - author=user, - description=u'Ostateczna akceptacja merytoryczna przez kierownika literackiego.', - tags=[Chunk.tag_model.objects.get(slug='editor-proofreading')], - publishable=True - ) - print 'committed %s' % book.slug diff --git a/apps/catalogue/management/commands/merge_books.py b/apps/catalogue/management/commands/merge_books.py deleted file mode 100644 index 82bd622d..00000000 --- a/apps/catalogue/management/commands/merge_books.py +++ /dev/null @@ -1,215 +0,0 @@ -# -*- coding: utf-8 -*- - -from optparse import make_option -import sys - -from django.contrib.auth.models import User -from django.core.management.base import BaseCommand -from django.core.management.color import color_style -from django.db import transaction - -from catalogue.models import Book - - -def common_prefix(texts): - common = [] - - min_len = min(len(text) for text in texts) - for i in range(min_len): - chars = list(set([text[i] for text in texts])) - if len(chars) > 1: - break - common.append(chars[0]) - return "".join(common) - - -class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('-s', '--slug', dest='new_slug', metavar='SLUG', - help='New slug of the merged book (defaults to common part of all slugs).'), - make_option('-t', '--title', dest='new_title', metavar='TITLE', - help='New title of the merged book (defaults to common part of all titles).'), - make_option('-q', '--quiet', action='store_false', dest='verbose', default=True, - help='Less output'), - make_option('-g', '--guess', action='store_true', dest='guess', default=False, - help='Try to guess what merges are needed (but do not apply them).'), - make_option('-d', '--dry-run', action='store_true', dest='dry_run', default=False, - help='Dry run: do not actually change anything.'), - make_option('-f', '--force', action='store_true', dest='force', default=False, - help='On slug conflict, hide the original book to archive.'), - ) - help = 'Merges multiple books into one.' - args = '[slug]...' - - - def print_guess(self, dry_run=True, force=False): - from collections import defaultdict - from pipes import quote - import re - - def read_slug(slug): - res = [] - res.append((re.compile(ur'__?(przedmowa)$'), -1)) - res.append((re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P\d*)$'), None)) - res.append((re.compile(ur'__?(rozdzialy__?)?(?P\d*)-'), None)) - - for r, default in res: - m = r.search(slug) - if m: - start = m.start() - try: - return int(m.group('n')), slug[:start] - except IndexError: - return default, slug[:start] - return None, slug - - def file_to_title(fname): - """ Returns a title-like version of a filename. """ - parts = (p.replace('_', ' ').title() for p in fname.split('__')) - return ' / '.join(parts) - - merges = defaultdict(list) - slugs = [] - for b in Book.objects.all(): - slugs.append(b.slug) - n, ns = read_slug(b.slug) - if n is not None: - merges[ns].append((n, b)) - - conflicting_slugs = [] - for slug in sorted(merges.keys()): - merge_list = sorted(merges[slug]) - if len(merge_list) < 2: - continue - - merge_slugs = [b.slug for i, b in merge_list] - if slug in slugs and slug not in merge_slugs: - conflicting_slugs.append(slug) - - title = file_to_title(slug) - print "./manage.py merge_books %s%s--title=%s --slug=%s \\\n %s\n" % ( - '--dry-run ' if dry_run else '', - '--force ' if force else '', - quote(title), slug, - " \\\n ".join(merge_slugs) - ) - - if conflicting_slugs: - if force: - print self.style.NOTICE('# These books will be archived:') - else: - print self.style.ERROR('# ERROR: Conflicting slugs:') - for slug in conflicting_slugs: - print '#', slug - - - def handle(self, *slugs, **options): - - self.style = color_style() - - force = options.get('force') - guess = options.get('guess') - dry_run = options.get('dry_run') - new_slug = options.get('new_slug').decode('utf-8') - new_title = options.get('new_title').decode('utf-8') - verbose = options.get('verbose') - - if guess: - if slugs: - print "Please specify either slugs, or --guess." - return - else: - self.print_guess(dry_run, force) - return - if not slugs: - print "Please specify some book slugs" - return - - # Start transaction management. - transaction.enter_transaction_management() - - books = [Book.objects.get(slug=slug) for slug in slugs] - common_slug = common_prefix(slugs) - common_title = common_prefix([b.title for b in books]) - - if not new_title: - new_title = common_title - elif common_title.startswith(new_title): - common_title = new_title - - if not new_slug: - new_slug = common_slug - elif common_slug.startswith(new_slug): - common_slug = new_slug - - if slugs[0] != new_slug and Book.objects.filter(slug=new_slug).exists(): - self.style.ERROR('Book already exists, skipping!') - - - if dry_run and verbose: - print self.style.NOTICE('DRY RUN: nothing will be changed.') - print - - if verbose: - print "New title:", self.style.NOTICE(new_title) - print "New slug:", self.style.NOTICE(new_slug) - print - - for i, book in enumerate(books): - chunk_titles = [] - chunk_slugs = [] - - book_title = book.title[len(common_title):].replace(' / ', ' ').lstrip() - book_slug = book.slug[len(common_slug):].replace('__', '_').lstrip('-_') - for j, chunk in enumerate(book): - if j: - new_chunk_title = book_title + '_%d' % j - new_chunk_slug = book_slug + '_%d' % j - else: - new_chunk_title, new_chunk_slug = book_title, book_slug - - chunk_titles.append(new_chunk_title) - chunk_slugs.append(new_chunk_slug) - - if verbose: - print "title: %s // %s -->\n %s // %s\nslug: %s / %s -->\n %s / %s" % ( - book.title, chunk.title, - new_title, new_chunk_title, - book.slug, chunk.slug, - new_slug, new_chunk_slug) - print - - if not dry_run: - try: - conflict = Book.objects.get(slug=new_slug) - except Book.DoesNotExist: - conflict = None - else: - if conflict == books[0]: - conflict = None - - if conflict: - if force: - # FIXME: there still may be a conflict - conflict.slug = '.' + conflict.slug - conflict.save() - print self.style.NOTICE('Book with slug "%s" moved to "%s".' % (new_slug, conflict.slug)) - else: - print self.style.ERROR('ERROR: Book with slug "%s" exists.' % new_slug) - return - - if i: - books[0].append(books[i], slugs=chunk_slugs, titles=chunk_titles) - else: - book.title = new_title - book.slug = new_slug - book.save() - for j, chunk in enumerate(book): - chunk.title = chunk_titles[j] - chunk.slug = chunk_slugs[j] - chunk.save() - - - transaction.commit() - transaction.leave_transaction_management() - diff --git a/apps/catalogue/management/commands/prune_audience.py b/apps/catalogue/management/commands/prune_audience.py deleted file mode 100644 index 114a26f9..00000000 --- a/apps/catalogue/management/commands/prune_audience.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# - -import sys -from django.contrib.auth.models import User -from lxml import etree -from optparse import make_option - -from django.core.management import BaseCommand - -from catalogue.models import Book -from librarian import DCNS - - -class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - # make_option('-q', '--quiet', action='store_false', dest='verbose', - # default=True, help='Less output'), - # make_option('-d', '--dry-run', action='store_true', dest='dry_run', - # default=False, help="Don't actually touch anything"), - make_option( - '-u', '--username', dest='username', metavar='USER', - help='Assign commits to this user (required, preferably yourself).'), - ) - args = 'exclude_file' - - def handle(self, exclude_file, **options): - username = options.get('username') - - if username: - user = User.objects.get(username=username) - else: - print 'Please provide a username.' - sys.exit(1) - - excluded_slugs = [line.strip() for line in open(exclude_file, 'rb') if line.strip()] - books = Book.objects.exclude(slug__in=excluded_slugs) - - for book in books: - if not book.is_published(): - continue - print 'processing %s' % book.slug - chunk = book.chunk_set.first() - old_head = chunk.head - src = old_head.materialize() - tree = etree.fromstring(src) - audience_nodes = tree.findall('.//' + DCNS("audience")) - if not audience_nodes: - print '%s has no audience, skipping' % book.slug - continue - - for node in audience_nodes: - node.getparent().remove(node) - - chunk.commit( - etree.tostring(tree, encoding=unicode), - author=user, - description='automatyczne skasowanie audience', - publishable=old_head.publishable - ) - print 'committed %s' % book.slug - if not old_head.publishable: - print 'Warning: %s not publishable, last head: %s, %s' % ( - book.slug, old_head.author.username, old_head.description[:40].replace('\n', ' ')) diff --git a/apps/catalogue/managers.py b/apps/catalogue/managers.py deleted file mode 100644 index a131ce94..00000000 --- a/apps/catalogue/managers.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.db import models - -class VisibleManager(models.Manager): - def get_queryset(self): - return super(VisibleManager, self).get_queryset().exclude(_hidden=True) diff --git a/apps/catalogue/migrations/0001_initial.py b/apps/catalogue/migrations/0001_initial.py deleted file mode 100644 index dccd9b7b..00000000 --- a/apps/catalogue/migrations/0001_initial.py +++ /dev/null @@ -1,240 +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 'Book' - db.create_table('catalogue_book', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('title', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), - ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=128, db_index=True)), - ('gallery', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), - ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['catalogue.Book'])), - ('parent_number', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)), - ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), - ('_single', self.gf('django.db.models.fields.NullBooleanField')(db_index=True, null=True, blank=True)), - ('_new_publishable', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), - ('_published', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), - )) - db.send_create_signal('catalogue', ['Book']) - - # Adding model 'Chunk' - db.create_table('catalogue_chunk', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_documents', null=True, to=orm['auth.User'])), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), - ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Book'])), - ('number', self.gf('django.db.models.fields.IntegerField')()), - ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)), - ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), - ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), - ('_hidden', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), - ('_changed', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), - ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ChunkTag'], null=True, blank=True)), - ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['catalogue.ChunkChange'], null=True, blank=True)), - )) - db.send_create_signal('catalogue', ['Chunk']) - - # Adding unique constraint on 'Chunk', fields ['book', 'number'] - db.create_unique('catalogue_chunk', ['book_id', 'number']) - - # Adding unique constraint on 'Chunk', fields ['book', 'slug'] - db.create_unique('catalogue_chunk', ['book_id', 'slug']) - - # Adding model 'ChunkTag' - db.create_table('catalogue_chunktag', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('name', self.gf('django.db.models.fields.CharField')(max_length=64)), - ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=64, unique=True, null=True, blank=True)), - ('ordering', self.gf('django.db.models.fields.IntegerField')()), - )) - db.send_create_signal('catalogue', ['ChunkTag']) - - # Adding model 'ChunkChange' - db.create_table('catalogue_chunkchange', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), - ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), - ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), - ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)), - ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['catalogue.ChunkChange'])), - ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['catalogue.ChunkChange'])), - ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)), - ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)), - ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('tree', self.gf('django.db.models.fields.related.ForeignKey')(related_name='change_set', to=orm['catalogue.Chunk'])), - ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)), - )) - db.send_create_signal('catalogue', ['ChunkChange']) - - # Adding unique constraint on 'ChunkChange', fields ['tree', 'revision'] - db.create_unique('catalogue_chunkchange', ['tree_id', 'revision']) - - # Adding M2M table for field tags on 'ChunkChange' - db.create_table('catalogue_chunkchange_tags', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('chunkchange', models.ForeignKey(orm['catalogue.chunkchange'], null=False)), - ('chunktag', models.ForeignKey(orm['catalogue.chunktag'], null=False)) - )) - db.create_unique('catalogue_chunkchange_tags', ['chunkchange_id', 'chunktag_id']) - - # Adding model 'BookPublishRecord' - db.create_table('catalogue_bookpublishrecord', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('book', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.Book'])), - ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), - )) - db.send_create_signal('catalogue', ['BookPublishRecord']) - - # Adding model 'ChunkPublishRecord' - db.create_table('catalogue_chunkpublishrecord', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('book_record', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.BookPublishRecord'])), - ('change', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.ChunkChange'])), - )) - db.send_create_signal('catalogue', ['ChunkPublishRecord']) - - - def backwards(self, orm): - - # Removing unique constraint on 'ChunkChange', fields ['tree', 'revision'] - db.delete_unique('catalogue_chunkchange', ['tree_id', 'revision']) - - # Removing unique constraint on 'Chunk', fields ['book', 'slug'] - db.delete_unique('catalogue_chunk', ['book_id', 'slug']) - - # Removing unique constraint on 'Chunk', fields ['book', 'number'] - db.delete_unique('catalogue_chunk', ['book_id', 'number']) - - # Deleting model 'Book' - db.delete_table('catalogue_book') - - # Deleting model 'Chunk' - db.delete_table('catalogue_chunk') - - # Deleting model 'ChunkTag' - db.delete_table('catalogue_chunktag') - - # Deleting model 'ChunkChange' - db.delete_table('catalogue_chunkchange') - - # Removing M2M table for field tags on 'ChunkChange' - db.delete_table('catalogue_chunkchange_tags') - - # Deleting model 'BookPublishRecord' - db.delete_table('catalogue_bookpublishrecord') - - # Deleting model 'ChunkPublishRecord' - db.delete_table('catalogue_chunkpublishrecord') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.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', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - '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['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - '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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'ordering': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0002_stages.py b/apps/catalogue/migrations/0002_stages.py deleted file mode 100644 index 71554570..00000000 --- a/apps/catalogue/migrations/0002_stages.py +++ /dev/null @@ -1,122 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import DataMigration -from django.db import models - -class Migration(DataMigration): - - def forwards(self, orm): - - from django.core.management import call_command - call_command("loaddata", "stages.json") - - - def backwards(self, orm): - "Write your backwards methods here." - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.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', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - '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['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - '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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'ordering': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0003_from_hg.py b/apps/catalogue/migrations/0003_from_hg.py deleted file mode 100644 index e542d508..00000000 --- a/apps/catalogue/migrations/0003_from_hg.py +++ /dev/null @@ -1,281 +0,0 @@ -# encoding: utf-8 -import datetime -from zlib import compress -import os -import os.path -import re -import urllib - -from django.db import models -from south.db import db -from south.v2 import DataMigration - -from django.conf import settings -from slugify import slugify - -META_REGEX = re.compile(r'\s*', re.DOTALL | re.MULTILINE) -STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE) -AUTHOR_RE = re.compile(r'\s*(.*?)\s*<(.*)>\s*') - - -def urlunquote(url): - """Unqotes URL - - # >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84') - # u'Za\u017c\xf3\u0142\u0107_g\u0119\u015bl\u0105 ja\u017a\u0144' - """ - return unicode(urllib.unquote(url), 'utf-8', 'ignore') - - -def split_name(name): - parts = name.split('__') - return parts - - -def file_to_title(fname): - """ Returns a title-like version of a filename. """ - parts = (p.replace('_', ' ').title() for p in fname.split('__')) - return ' / '.join(parts) - - -def plain_text(text): - return re.sub(META_REGEX, '', text, 1) - - -def gallery(slug, text): - result = {} - - m = re.match(META_REGEX, text) - if m: - for line in m.group(1).split('\n'): - try: - k, v = line.split(':', 1) - result[k.strip()] = v.strip() - except ValueError: - continue - - gallery = result.get('gallery', slugify(slug)) - - if gallery.startswith('/'): - gallery = os.path.basename(gallery) - - return gallery - - -def migrate_file_from_hg(orm, fname, entry): - fname = urlunquote(fname) - print fname - if fname.endswith('.xml'): - fname = fname[:-4] - title = file_to_title(fname) - fname = slugify(fname) - - # create all the needed objects - # what if it already exists? - book = orm.Book.objects.create( - title=title, - slug=fname) - chunk = orm.Chunk.objects.create( - book=book, - number=1, - slug='1') - try: - chunk.stage = orm.ChunkTag.objects.order_by('ordering')[0] - except IndexError: - chunk.stage = None - - maxrev = entry.filerev() - gallery_link = None - - # this will fail if directory exists - os.makedirs(os.path.join(settings.CATALOGUE_REPO_PATH, str(chunk.pk))) - - for rev in xrange(maxrev + 1): - fctx = entry.filectx(rev) - data = fctx.data() - gallery_link = gallery(fname, data) - data = plain_text(data) - - # get tags from description - description = fctx.description().decode("utf-8", 'replace') - tags = STAGE_TAGS_RE.findall(description) - tags = [orm.ChunkTag.objects.get(slug=slug.strip()) for slug in tags] - - if tags: - max_ordering = max(tags, key=lambda x: x.ordering).ordering - try: - chunk.stage = orm.ChunkTag.objects.filter(ordering__gt=max_ordering).order_by('ordering')[0] - except IndexError: - chunk.stage = None - - description = STAGE_TAGS_RE.sub('', description) - - author = author_name = author_email = None - author_desc = fctx.user().decode("utf-8", 'replace') - m = AUTHOR_RE.match(author_desc) - if m: - try: - author = orm['auth.User'].objects.get(username=m.group(1), email=m.group(2)) - except orm['auth.User'].DoesNotExist: - author_name = m.group(1) - author_email = m.group(2) - else: - author_name = author_desc - - head = orm.ChunkChange.objects.create( - tree=chunk, - revision=rev + 1, - created_at=datetime.datetime.fromtimestamp(fctx.date()[0]), - description=description, - author=author, - author_name=author_name, - author_email=author_email, - parent=chunk.head - ) - - path = "%d/%d" % (chunk.pk, head.pk) - abs_path = os.path.join(settings.CATALOGUE_REPO_PATH, path) - f = open(abs_path, 'wb') - f.write(compress(data)) - f.close() - head.data = path - - head.tags = tags - head.save() - - chunk.head = head - - chunk.save() - if gallery_link: - book.gallery = gallery_link - book.save() - - -class Migration(DataMigration): - - def forwards(self, orm): - try: - hg_path = settings.WIKI_REPOSITORY_PATH - except: - print 'repository not configured, skipping' - else: - from mercurial import hg, ui - - print 'migrate from', hg_path - repo = hg.repository(ui.ui(), hg_path) - tip = repo['tip'] - for fname in tip: - if fname.startswith('.'): - continue - migrate_file_from_hg(orm, fname, tip[fname]) - - - def backwards(self, orm): - "Write your backwards methods here." - pass - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.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', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - '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['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - '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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'ordering': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0004_fix_revisions.py b/apps/catalogue/migrations/0004_fix_revisions.py deleted file mode 100644 index fe5c86be..00000000 --- a/apps/catalogue/migrations/0004_fix_revisions.py +++ /dev/null @@ -1,125 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import DataMigration -from django.db import models - -class Migration(DataMigration): - - def forwards(self, orm): - "Make sure all revisions start with 1, not 0." - for zero_commit in orm.ChunkChange.objects.filter(revision=0): - for change in zero_commit.tree.change_set.all().order_by('-revision'): - change.revision=models.F('revision') + 1 - change.save() - - - def backwards(self, orm): - "Write your backwards methods here." - pass - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.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', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - '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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'ordering': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0005_auto__add_field_chunk_gallery_start.py b/apps/catalogue/migrations/0005_auto__add_field_chunk_gallery_start.py deleted file mode 100644 index 71af5f6c..00000000 --- a/apps/catalogue/migrations/0005_auto__add_field_chunk_gallery_start.py +++ /dev/null @@ -1,125 +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 'Chunk.gallery_start' - db.add_column('catalogue_chunk', 'gallery_start', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False) - - - def backwards(self, orm): - - # Deleting field 'Chunk.gallery_start' - db.delete_column('catalogue_chunk', 'gallery_start') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.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', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), - 'gallery_start': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - '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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'ordering': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0006_auto__add_field_book_public.py b/apps/catalogue/migrations/0006_auto__add_field_book_public.py deleted file mode 100644 index fd1cea56..00000000 --- a/apps/catalogue/migrations/0006_auto__add_field_book_public.py +++ /dev/null @@ -1,126 +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.public' - db.add_column('catalogue_book', 'public', self.gf('django.db.models.fields.BooleanField')(default=True, db_index=True), keep_default=False) - - - def backwards(self, orm): - - # Deleting field 'Book.public' - db.delete_column('catalogue_book', 'public') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.Book']"}), - 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), - 'gallery_start': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - '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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'ordering': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0007_auto__add_field_book_dc_slug.py b/apps/catalogue/migrations/0007_auto__add_field_book_dc_slug.py deleted file mode 100644 index 5ae20ea3..00000000 --- a/apps/catalogue/migrations/0007_auto__add_field_book_dc_slug.py +++ /dev/null @@ -1,127 +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.dc_slug' - db.add_column('catalogue_book', 'dc_slug', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True), keep_default=False) - - - def backwards(self, orm): - - # Deleting field 'Book.dc_slug' - db.delete_column('catalogue_book', 'dc_slug') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'dc_slug': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': '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['catalogue.Book']"}), - 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), - 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - '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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'ordering': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0008_auto.py b/apps/catalogue/migrations/0008_auto.py deleted file mode 100644 index 5276b27b..00000000 --- a/apps/catalogue/migrations/0008_auto.py +++ /dev/null @@ -1,127 +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 index on 'Book', fields ['dc_slug'] - db.create_index('catalogue_book', ['dc_slug']) - - - def backwards(self, orm): - - # Removing index on 'Book', fields ['dc_slug'] - db.delete_index('catalogue_book', ['dc_slug']) - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': '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['catalogue.Book']"}), - 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), - 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - '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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'ordering': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0009_auto__add_field_book__on_track.py b/apps/catalogue/migrations/0009_auto__add_field_book__on_track.py deleted file mode 100644 index f0509c42..00000000 --- a/apps/catalogue/migrations/0009_auto__add_field_book__on_track.py +++ /dev/null @@ -1,128 +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._on_track' - db.add_column('catalogue_book', '_on_track', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True), keep_default=False) - - - def backwards(self, orm): - - # Deleting field 'Book._on_track' - db.delete_column('catalogue_book', '_on_track') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': '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['catalogue.Book']"}), - 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), - 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - '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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'ordering': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0010_auto__add_field_book_dc_cover_image.py b/apps/catalogue/migrations/0010_auto__add_field_book_dc_cover_image.py deleted file mode 100644 index aebbed95..00000000 --- a/apps/catalogue/migrations/0010_auto__add_field_book_dc_cover_image.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - depends_on = ( - ("cover", "0001_initial"), - ) - - def forwards(self, orm): - # Adding field 'Book.dc_cover_image' - db.add_column('catalogue_book', 'dc_cover_image', - self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cover.Image'], null=True, on_delete=models.SET_NULL, blank=True), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'Book.dc_cover_image' - db.delete_column('catalogue_book', 'dc_cover_image_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', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), - 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': '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['catalogue.Book']"}), - 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), - 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - '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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), - 'ordering': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - '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'}) - }, - 'cover.image': { - 'Meta': {'object_name': 'Image'}, - 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'download_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}), - 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), - 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) - } - } - - complete_apps = ['catalogue'] \ No newline at end of file diff --git a/apps/catalogue/migrations/0011_auto__add_project__add_field_book_project.py b/apps/catalogue/migrations/0011_auto__add_project__add_field_book_project.py deleted file mode 100644 index 6f30cb4f..00000000 --- a/apps/catalogue/migrations/0011_auto__add_project__add_field_book_project.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: 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 'Project' - db.create_table(u'catalogue_project', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), - ('notes', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), - )) - db.send_create_signal('catalogue', ['Project']) - - # Adding field 'Book.project' - db.add_column(u'catalogue_book', 'project', - self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Project'], null=True), - keep_default=False) - - - def backwards(self, orm): - # Deleting model 'Project' - db.delete_table(u'catalogue_project') - - # Deleting field 'Book.project' - db.delete_column(u'catalogue_book', 'project_id') - - - models = { - u'auth.group': { - 'Meta': {'object_name': 'Group'}, - u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - u'auth.permission': { - 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - u'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': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), - 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - u'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['catalogue.Book']"}), - 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True'}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': u"orm['auth.User']"}), - 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), - u'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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - u'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', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.project': { - 'Meta': {'ordering': "['name']", 'object_name': 'Project'}, - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), - 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) - }, - u'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'}), - u'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'}) - }, - u'cover.image': { - 'Meta': {'object_name': 'Image'}, - 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'download_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}), - 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), - 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) - } - } - - complete_apps = ['catalogue'] \ No newline at end of file diff --git a/apps/catalogue/migrations/0012_auto__add_imagepublishrecord__add_imagechange__add_unique_imagechange_.py b/apps/catalogue/migrations/0012_auto__add_imagepublishrecord__add_imagechange__add_unique_imagechange_.py deleted file mode 100644 index 599e103e..00000000 --- a/apps/catalogue/migrations/0012_auto__add_imagepublishrecord__add_imagechange__add_unique_imagechange_.py +++ /dev/null @@ -1,270 +0,0 @@ -# -*- coding: 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 'ImagePublishRecord' - db.create_table(u'catalogue_imagepublishrecord', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('image', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.Image'])), - ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), - ('change', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.ImageChange'])), - )) - db.send_create_signal('catalogue', ['ImagePublishRecord']) - - # Adding model 'ImageChange' - db.create_table(u'catalogue_imagechange', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), - ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), - ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), - ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)), - ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['catalogue.ImageChange'])), - ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['catalogue.ImageChange'])), - ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)), - ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)), - ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('tree', self.gf('django.db.models.fields.related.ForeignKey')(related_name='change_set', to=orm['catalogue.Image'])), - ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)), - )) - db.send_create_signal('catalogue', ['ImageChange']) - - # Adding M2M table for field tags on 'ImageChange' - db.create_table(u'catalogue_imagechange_tags', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('imagechange', models.ForeignKey(orm['catalogue.imagechange'], null=False)), - ('imagetag', models.ForeignKey(orm['catalogue.imagetag'], null=False)) - )) - db.create_unique(u'catalogue_imagechange_tags', ['imagechange_id', 'imagetag_id']) - - # Adding unique constraint on 'ImageChange', fields ['tree', 'revision'] - db.create_unique(u'catalogue_imagechange', ['tree_id', 'revision']) - - # Adding model 'ImageTag' - db.create_table(u'catalogue_imagetag', ( - (u'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')(max_length=64, unique=True, null=True, blank=True)), - ('ordering', self.gf('django.db.models.fields.IntegerField')()), - )) - db.send_create_signal('catalogue', ['ImageTag']) - - # Adding model 'Image' - db.create_table(u'catalogue_image', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), - ('image', self.gf('django.db.models.fields.files.FileField')(max_length=100)), - ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), - ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=50)), - ('public', self.gf('django.db.models.fields.BooleanField')(default=True, db_index=True)), - ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), - ('_new_publishable', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), - ('_published', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), - ('_changed', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), - ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ImageTag'], null=True, blank=True)), - ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['catalogue.ImageChange'], null=True, blank=True)), - ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_image', null=True, to=orm['auth.User'])), - )) - db.send_create_signal('catalogue', ['Image']) - - - def backwards(self, orm): - # Removing unique constraint on 'ImageChange', fields ['tree', 'revision'] - db.delete_unique(u'catalogue_imagechange', ['tree_id', 'revision']) - - # Deleting model 'ImagePublishRecord' - db.delete_table(u'catalogue_imagepublishrecord') - - # Deleting model 'ImageChange' - db.delete_table(u'catalogue_imagechange') - - # Removing M2M table for field tags on 'ImageChange' - db.delete_table('catalogue_imagechange_tags') - - # Deleting model 'ImageTag' - db.delete_table(u'catalogue_imagetag') - - # Deleting model 'Image' - db.delete_table(u'catalogue_image') - - - models = { - u'auth.group': { - 'Meta': {'object_name': 'Group'}, - u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - u'auth.permission': { - 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - u'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': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), - 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - u'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['catalogue.Book']"}), - 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True', 'blank': 'True'}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': u"orm['auth.User']"}), - 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), - u'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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - u'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', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.image': { - 'Meta': {'ordering': "['title']", 'object_name': 'Image'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_image'", 'null': 'True', 'to': u"orm['auth.User']"}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ImageChange']", 'null': 'True', 'blank': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'image': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ImageTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.imagechange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ImageChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), - u'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['catalogue.ImageChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ImageChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ImageTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Image']"}) - }, - 'catalogue.imagepublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'ImagePublishRecord'}, - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ImageChange']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Image']"}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) - }, - 'catalogue.imagetag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ImageTag'}, - u'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', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.project': { - 'Meta': {'ordering': "['name']", 'object_name': 'Project'}, - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), - 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) - }, - u'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'}), - u'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'}) - }, - u'cover.image': { - 'Meta': {'object_name': 'Image'}, - 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True', 'blank': 'True'}), - 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), - 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) - } - } - - complete_apps = ['catalogue'] \ No newline at end of file diff --git a/apps/catalogue/migrations/0013_auto__add_field_image_project.py b/apps/catalogue/migrations/0013_auto__add_field_image_project.py deleted file mode 100644 index 6ae3564f..00000000 --- a/apps/catalogue/migrations/0013_auto__add_field_image_project.py +++ /dev/null @@ -1,196 +0,0 @@ -# -*- coding: 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 'Image.project' - db.add_column(u'catalogue_image', 'project', - self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Project'], null=True, blank=True), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'Image.project' - db.delete_column(u'catalogue_image', 'project_id') - - - models = { - u'auth.group': { - 'Meta': {'object_name': 'Group'}, - u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - u'auth.permission': { - 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - u'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': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), - 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - u'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['catalogue.Book']"}), - 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), - 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True', 'blank': 'True'}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) - }, - 'catalogue.bookpublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) - }, - 'catalogue.chunk': { - 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': u"orm['auth.User']"}), - 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'number': ('django.db.models.fields.IntegerField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.chunkchange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), - u'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['catalogue.ChunkChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) - }, - 'catalogue.chunkpublishrecord': { - 'Meta': {'object_name': 'ChunkPublishRecord'}, - 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'catalogue.chunktag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, - u'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', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.image': { - 'Meta': {'ordering': "['title']", 'object_name': 'Image'}, - '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_image'", 'null': 'True', 'to': u"orm['auth.User']"}), - 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ImageChange']", 'null': 'True', 'blank': 'True'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'image': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True', 'blank': 'True'}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), - 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ImageTag']", 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.imagechange': { - 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ImageChange'}, - 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), - 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), - u'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['catalogue.ImageChange']"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ImageChange']"}), - 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ImageTag']"}), - 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Image']"}) - }, - 'catalogue.imagepublishrecord': { - 'Meta': {'ordering': "['-timestamp']", 'object_name': 'ImagePublishRecord'}, - 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ImageChange']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Image']"}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) - }, - 'catalogue.imagetag': { - 'Meta': {'ordering': "['ordering']", 'object_name': 'ImageTag'}, - u'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', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'catalogue.project': { - 'Meta': {'ordering': "['name']", 'object_name': 'Project'}, - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), - 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) - }, - u'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'}), - u'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'}) - }, - u'cover.image': { - 'Meta': {'object_name': 'Image'}, - 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True', 'blank': 'True'}), - 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), - 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) - } - } - - complete_apps = ['catalogue'] \ No newline at end of file diff --git a/apps/catalogue/migrations/__init__.py b/apps/catalogue/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/catalogue/models/__init__.py b/apps/catalogue/models/__init__.py deleted file mode 100755 index d0015c79..00000000 --- a/apps/catalogue/models/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from catalogue.models.project import Project -from catalogue.models.chunk import Chunk -from catalogue.models.image import Image -from catalogue.models.publish_log import (BookPublishRecord, - ChunkPublishRecord, ImagePublishRecord) -from catalogue.models.book import Book -from catalogue.models.listeners import * - -from django.contrib.auth.models import User as AuthUser - -class User(AuthUser): - class Meta: - proxy = True - - def __unicode__(self): - return "%s %s" % (self.first_name, self.last_name) diff --git a/apps/catalogue/models/book.py b/apps/catalogue/models/book.py deleted file mode 100755 index 1fcd05ac..00000000 --- a/apps/catalogue/models/book.py +++ /dev/null @@ -1,452 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.contrib.sites.models import Site -from django.db import models, transaction -from django.template.loader import render_to_string -from django.utils.translation import ugettext_lazy as _ -from django.conf import settings -from slugify import slugify - - -import apiclient -from catalogue.helpers import cached_in_field, GalleryMerger -from catalogue.models import BookPublishRecord, ChunkPublishRecord, Project -from catalogue.signals import post_publish -from catalogue.tasks import refresh_instance, book_content_updated -from catalogue.xml_tools import compile_text, split_xml -from cover.models import Image -import os -import shutil -import re - -class Book(models.Model): - """ A document edited on the wiki """ - - title = models.CharField(_('title'), max_length=255, db_index=True) - slug = models.SlugField(_('slug'), max_length=128, unique=True, db_index=True) - public = models.BooleanField(_('public'), default=True, db_index=True) - gallery = models.CharField(_('scan gallery name'), max_length=255, blank=True) - project = models.ForeignKey(Project, null=True, blank=True) - - #wl_slug = models.CharField(_('title'), max_length=255, null=True, db_index=True, editable=False) - parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children", editable=False) - parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True, editable=False) - - # Cache - _short_html = models.TextField(null=True, blank=True, editable=False) - _single = models.NullBooleanField(editable=False, db_index=True) - _new_publishable = models.NullBooleanField(editable=False) - _published = models.NullBooleanField(editable=False) - _on_track = models.IntegerField(null=True, blank=True, db_index=True, editable=False) - dc_cover_image = models.ForeignKey(Image, blank=True, null=True, - db_index=True, on_delete=models.SET_NULL, editable=False) - dc_slug = models.CharField(max_length=128, null=True, blank=True, - editable=False, db_index=True) - - class NoTextError(BaseException): - pass - - class Meta: - app_label = 'catalogue' - ordering = ['title', 'slug'] - verbose_name = _('book') - verbose_name_plural = _('books') - - - # Representing - # ============ - - def __iter__(self): - return iter(self.chunk_set.all()) - - def __getitem__(self, chunk): - return self.chunk_set.all()[chunk] - - def __len__(self): - return self.chunk_set.count() - - def __nonzero__(self): - """ - Necessary so that __len__ isn't used for bool evaluation. - """ - return True - - def __unicode__(self): - return self.title - - @models.permalink - def get_absolute_url(self): - return ("catalogue_book", [self.slug]) - - def correct_about(self): - return "http://%s%s" % ( - Site.objects.get_current().domain, - self.get_absolute_url() - ) - - def gallery_path(self): - return os.path.join(settings.MEDIA_ROOT, settings.IMAGE_DIR, self.gallery) - - def gallery_url(self): - return '%s%s%s/' % (settings.MEDIA_URL, settings.IMAGE_DIR, self.gallery) - - # Creating & manipulating - # ======================= - - def accessible(self, request): - return self.public or request.user.is_authenticated() - - @classmethod - @transaction.atomic - def create(cls, creator, text, *args, **kwargs): - b = cls.objects.create(*args, **kwargs) - b.chunk_set.all().update(creator=creator) - b[0].commit(text, author=creator) - return b - - def add(self, *args, **kwargs): - """Add a new chunk at the end.""" - return self.chunk_set.reverse()[0].split(*args, **kwargs) - - @classmethod - @transaction.atomic - def import_xml_text(cls, text=u'', previous_book=None, - commit_args=None, **kwargs): - """Imports a book from XML, splitting it into chunks as necessary.""" - texts = split_xml(text) - if previous_book: - instance = previous_book - else: - instance = cls(**kwargs) - instance.save() - - # if there are more parts, set the rest to empty strings - book_len = len(instance) - for i in range(book_len - len(texts)): - texts.append((u'pusta część %d' % (i + 1), u'')) - - i = 0 - for i, (title, text) in enumerate(texts): - if not title: - title = u'część %d' % (i + 1) - - slug = slugify(title) - - if i < book_len: - chunk = instance[i] - chunk.slug = slug[:50] - chunk.title = title[:255] - chunk.save() - else: - chunk = instance.add(slug, title) - - chunk.commit(text, **commit_args) - - return instance - - def make_chunk_slug(self, proposed): - """ - Finds a chunk slug not yet used in the book. - """ - slugs = set(c.slug for c in self) - i = 1 - new_slug = proposed[:50] - while new_slug in slugs: - new_slug = "%s_%d" % (proposed[:45], i) - i += 1 - return new_slug - - @transaction.atomic - def append(self, other, slugs=None, titles=None): - """Add all chunks of another book to self.""" - assert self != other - - number = self[len(self) - 1].number + 1 - len_other = len(other) - single = len_other == 1 - - if slugs is not None: - assert len(slugs) == len_other - if titles is not None: - assert len(titles) == len_other - if slugs is None: - slugs = [slugify(t) for t in titles] - - for i, chunk in enumerate(other): - # move chunk to new book - chunk.book = self - chunk.number = number - - if titles is None: - # try some title guessing - if other.title.startswith(self.title): - other_title_part = other.title[len(self.title):].lstrip(' /') - else: - other_title_part = other.title - - if single: - # special treatment for appending one-parters: - # just use the guessed title and original book slug - chunk.title = other_title_part - if other.slug.startswith(self.slug): - chunk.slug = other.slug[len(self.slug):].lstrip('-_') - else: - chunk.slug = other.slug - else: - chunk.title = ("%s, %s" % (other_title_part, chunk.title))[:255] - else: - chunk.slug = slugs[i] - chunk.title = titles[i] - - chunk.slug = self.make_chunk_slug(chunk.slug) - chunk.save() - number += 1 - assert not other.chunk_set.exists() - - gm = GalleryMerger(self.gallery, other.gallery) - self.gallery = gm.merge() - - # and move the gallery starts - if gm.was_merged: - for chunk in self[len(self) - len_other:]: - old_start = chunk.gallery_start or 1 - chunk.gallery_start = old_start + gm.dest_size - gm.num_deleted - chunk.save() - - other.delete() - - - @transaction.atomic - def prepend_history(self, other): - """Prepend history from all the other book's chunks to own.""" - assert self != other - - for i in range(len(self), len(other)): - title = u"pusta część %d" % i - chunk = self.add(slugify(title), title) - chunk.commit('') - - for i in range(len(other)): - self[i].prepend_history(other[0]) - - assert not other.chunk_set.exists() - other.delete() - - def split(self): - """Splits all the chunks into separate books.""" - self.title - for chunk in self: - book = Book.objects.create(title=chunk.title, slug=chunk.slug, - public=self.public, gallery=self.gallery) - book[0].delete() - chunk.book = book - chunk.number = 1 - chunk.save() - assert not self.chunk_set.exists() - self.delete() - - # State & cache - # ============= - - def last_published(self): - try: - return self.publish_log.all()[0].timestamp - except IndexError: - return None - - def assert_publishable(self): - assert self.chunk_set.exists(), _('No chunks in the book.') - try: - changes = self.get_current_changes(publishable=True) - except self.NoTextError: - raise AssertionError(_('Not all chunks have publishable revisions.')) - - from librarian import NoDublinCore, ParseError, ValidationError - - try: - bi = self.wldocument(changes=changes, strict=True).book_info - except ParseError, e: - raise AssertionError(_('Invalid XML') + ': ' + unicode(e)) - except NoDublinCore: - raise AssertionError(_('No Dublin Core found.')) - except ValidationError, e: - raise AssertionError(_('Invalid Dublin Core') + ': ' + unicode(e)) - - valid_about = self.correct_about() - assert bi.about == valid_about, _("rdf:about is not") + " " + valid_about - - def publishable_error(self): - try: - return self.assert_publishable() - except AssertionError, e: - return e - else: - return None - - def hidden(self): - return self.slug.startswith('.') - - def is_new_publishable(self): - """Checks if book is ready for publishing. - - Returns True if there is a publishable version newer than the one - already published. - - """ - new_publishable = False - if not self.chunk_set.exists(): - return False - for chunk in self: - change = chunk.publishable() - if not change: - return False - if not new_publishable and not change.publish_log.exists(): - new_publishable = True - return new_publishable - new_publishable = cached_in_field('_new_publishable')(is_new_publishable) - - def is_published(self): - return self.publish_log.exists() - published = cached_in_field('_published')(is_published) - - def get_on_track(self): - if self.published: - return -1 - stages = [ch.stage.ordering if ch.stage is not None else 0 - for ch in self] - if not len(stages): - return 0 - return min(stages) - on_track = cached_in_field('_on_track')(get_on_track) - - def is_single(self): - return len(self) == 1 - single = cached_in_field('_single')(is_single) - - @cached_in_field('_short_html') - def short_html(self): - return render_to_string('catalogue/book_list/book.html', {'book': self}) - - def book_info(self, publishable=True): - try: - book_xml = self.materialize(publishable=publishable) - except self.NoTextError: - pass - else: - from librarian.dcparser import BookInfo - from librarian import NoDublinCore, ParseError, ValidationError - try: - return BookInfo.from_bytes(book_xml.encode('utf-8')) - except (self.NoTextError, ParseError, NoDublinCore, ValidationError): - return None - - def refresh_dc_cache(self): - update = { - 'dc_slug': None, - 'dc_cover_image': None, - } - - info = self.book_info() - if info is not None: - update['dc_slug'] = info.url.slug - if info.cover_source: - try: - image = Image.objects.get(pk=int(info.cover_source.rstrip('/').rsplit('/', 1)[-1])) - except: - pass - else: - if info.cover_source == image.get_full_url(): - update['dc_cover_image'] = image - Book.objects.filter(pk=self.pk).update(**update) - - def touch(self): - # this should only really be done when text or publishable status changes - book_content_updated.delay(self) - - update = { - "_new_publishable": self.is_new_publishable(), - "_published": self.is_published(), - "_single": self.is_single(), - "_on_track": self.get_on_track(), - "_short_html": None, - } - Book.objects.filter(pk=self.pk).update(**update) - refresh_instance(self) - - def refresh(self): - """This should be done offline.""" - self.short_html - self.single - self.new_publishable - self.published - - # Materializing & publishing - # ========================== - - def get_current_changes(self, publishable=True): - """ - Returns a list containing one Change for every Chunk in the Book. - Takes the most recent revision (publishable, if set). - Throws an error, if a proper revision is unavailable for a Chunk. - """ - if publishable: - changes = [chunk.publishable() for chunk in self] - else: - changes = [chunk.head for chunk in self if chunk.head is not None] - if None in changes: - raise self.NoTextError('Some chunks have no available text.') - return changes - - def materialize(self, publishable=False, changes=None): - """ - Get full text of the document compiled from chunks. - Takes the current versions of all texts - or versions most recently tagged for publishing, - or a specified iterable changes. - """ - if changes is None: - changes = self.get_current_changes(publishable) - return compile_text(change.materialize() for change in changes) - - def wldocument(self, publishable=True, changes=None, - parse_dublincore=True, strict=False): - from catalogue.ebook_utils import RedakcjaDocProvider - from librarian.parser import WLDocument - - return WLDocument.from_bytes( - self.materialize(publishable=publishable, changes=changes).encode('utf-8'), - provider=RedakcjaDocProvider(publishable=publishable), - parse_dublincore=parse_dublincore, - strict=strict) - - def publish(self, user, fake=False, host=None, days=0, beta=False): - """ - Publishes a book on behalf of a (local) user. - """ - self.assert_publishable() - changes = self.get_current_changes(publishable=True) - if not fake: - book_xml = self.materialize(changes=changes) - data = {"book_xml": book_xml, "days": days} - if host: - data['gallery_url'] = host + self.gallery_url() - apiclient.api_call(user, "books/", data, beta=beta) - if not beta: - # record the publish - br = BookPublishRecord.objects.create(book=self, user=user) - for c in changes: - ChunkPublishRecord.objects.create(book_record=br, change=c) - if not self.public and days == 0: - self.public = True - self.save() - if self.public and days > 0: - self.public = False - self.save() - post_publish.send(sender=br) - - def latex_dir(self): - doc = self.wldocument() - return doc.latex_dir(cover=True, ilustr_path=self.gallery_path()) diff --git a/apps/catalogue/models/chunk.py b/apps/catalogue/models/chunk.py deleted file mode 100755 index fc3a9eae..00000000 --- a/apps/catalogue/models/chunk.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.conf import settings -from django.db import models -from django.db.utils import IntegrityError -from django.template.loader import render_to_string -from django.utils.translation import ugettext_lazy as _ -from catalogue.helpers import cached_in_field -from catalogue.managers import VisibleManager -from catalogue.tasks import refresh_instance -from dvcs import models as dvcs_models - - -class Chunk(dvcs_models.Document): - """ An editable chunk of text. Every Book text is divided into chunks. """ - REPO_PATH = settings.CATALOGUE_REPO_PATH - - book = models.ForeignKey('Book', editable=False, verbose_name=_('book')) - number = models.IntegerField(_('number')) - title = models.CharField(_('title'), max_length=255, blank=True) - slug = models.SlugField(_('slug')) - gallery_start = models.IntegerField(_('gallery start'), null=True, blank=True, default=1) - - # cache - _short_html = models.TextField(null=True, blank=True, editable=False) - _hidden = models.NullBooleanField(editable=False) - _changed = models.NullBooleanField(editable=False) - - # managers - objects = models.Manager() - visible_objects = VisibleManager() - - class Meta: - app_label = 'catalogue' - unique_together = [['book', 'number'], ['book', 'slug']] - ordering = ['number'] - verbose_name = _('chunk') - verbose_name_plural = _('chunks') - permissions = [('can_pubmark', 'Can mark for publishing')] - - # Representing - # ============ - - def __unicode__(self): - return "%d:%d: %s" % (self.book_id, self.number, self.title) - - @models.permalink - def get_absolute_url(self): - return "wiki_editor", [self.book.slug, self.slug] - - def pretty_name(self, book_length=None): - title = self.book.title - if self.title: - title += ", %s" % self.title - if book_length > 1: - title += " (%d/%d)" % (self.number, book_length) - return title - - # Creating and manipulation - # ========================= - - def split(self, slug, title='', **kwargs): - """ Create an empty chunk after this one """ - self.book.chunk_set.filter(number__gt=self.number).update( - number=models.F('number')+1) - new_chunk = None - while not new_chunk: - new_slug = self.book.make_chunk_slug(slug) - try: - new_chunk = self.book.chunk_set.create( - number=self.number+1, - slug=new_slug[:50], title=title[:255], **kwargs) - except IntegrityError: - pass - return new_chunk - - @classmethod - def get(cls, book_slug, chunk_slug=None): - if chunk_slug is None: - return cls.objects.get(book__slug=book_slug, number=1) - else: - return cls.objects.get(book__slug=book_slug, slug=chunk_slug) - - # State & cache - # ============= - - def new_publishable(self): - change = self.publishable() - if not change: - return False - return not change.publish_log.exists() - - def is_changed(self): - if self.head is None: - return False - return not self.head.publishable - changed = cached_in_field('_changed')(is_changed) - - def is_hidden(self): - return self.book.hidden() - hidden = cached_in_field('_hidden')(is_hidden) - - @cached_in_field('_short_html') - def short_html(self): - return render_to_string( - 'catalogue/book_list/chunk.html', {'chunk': self}) - - def touch(self): - update = { - "_changed": self.is_changed(), - "_hidden": self.is_hidden(), - "_short_html": None, - } - Chunk.objects.filter(pk=self.pk).update(**update) - refresh_instance(self) - - def refresh(self): - """This should be done offline.""" - self.changed - self.hidden - self.short_html diff --git a/apps/catalogue/models/image.py b/apps/catalogue/models/image.py deleted file mode 100755 index 646dd0ae..00000000 --- a/apps/catalogue/models/image.py +++ /dev/null @@ -1,172 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.conf import settings -from django.contrib.sites.models import Site -from django.db import models -from django.template.loader import render_to_string -from django.utils.translation import ugettext_lazy as _ -from catalogue.helpers import cached_in_field -from catalogue.models import Project -from catalogue.tasks import refresh_instance -from dvcs import models as dvcs_models - - -class Image(dvcs_models.Document): - """ An editable chunk of text. Every Book text is divided into chunks. """ - REPO_PATH = settings.CATALOGUE_IMAGE_REPO_PATH - - image = models.FileField(_('image'), upload_to='catalogue/images') - title = models.CharField(_('title'), max_length=255, blank=True) - slug = models.SlugField(_('slug'), unique=True) - public = models.BooleanField(_('public'), default=True, db_index=True) - project = models.ForeignKey(Project, null=True, blank=True) - - # cache - _short_html = models.TextField(null=True, blank=True, editable=False) - _new_publishable = models.NullBooleanField(editable=False) - _published = models.NullBooleanField(editable=False) - _changed = models.NullBooleanField(editable=False) - - class Meta: - app_label = 'catalogue' - ordering = ['title'] - verbose_name = _('image') - verbose_name_plural = _('images') - permissions = [('can_pubmark_image', 'Can mark images for publishing')] - - # Representing - # ============ - - def __unicode__(self): - return self.title - - @models.permalink - def get_absolute_url(self): - return ("catalogue_image", [self.slug]) - - def correct_about(self): - return ["http://%s%s" % ( - Site.objects.get_current().domain, - self.get_absolute_url() - ), - "http://%s%s" % ( - 'obrazy.redakcja.wolnelektury.pl', - self.get_absolute_url() - )] - - # State & cache - # ============= - - def last_published(self): - try: - return self.publish_log.all()[0].timestamp - except IndexError: - return None - - def assert_publishable(self): - from librarian.picture import WLPicture - from librarian import NoDublinCore, ParseError, ValidationError - - class SelfImageStore(object): - def path(self_, slug, mime_type): - """Returns own file object. Ignores slug ad mime_type.""" - return open(self.image.path) - - publishable = self.publishable() - assert publishable, _("There is no publishable revision") - picture_xml = publishable.materialize() - - try: - picture = WLPicture.from_bytes( - picture_xml.encode('utf-8'), - image_store=SelfImageStore) - except ParseError, e: - raise AssertionError(_('Invalid XML') + ': ' + str(e)) - except NoDublinCore: - raise AssertionError(_('No Dublin Core found.')) - except ValidationError, e: - raise AssertionError(_('Invalid Dublin Core') + ': ' + str(e)) - - valid_about = self.correct_about() - assert picture.picture_info.about in valid_about, \ - _("rdf:about is not") + " " + valid_about[0] - - def publishable_error(self): - try: - return self.assert_publishable() - except AssertionError, e: - return e - else: - return None - - def accessible(self, request): - return self.public or request.user.is_authenticated() - - def is_new_publishable(self): - change = self.publishable() - if not change: - return False - return not change.publish_log.exists() - new_publishable = cached_in_field('_new_publishable')(is_new_publishable) - - def is_published(self): - return self.publish_log.exists() - published = cached_in_field('_published')(is_published) - - def is_changed(self): - if self.head is None: - return False - return not self.head.publishable - changed = cached_in_field('_changed')(is_changed) - - @cached_in_field('_short_html') - def short_html(self): - return render_to_string( - 'catalogue/image_short.html', {'image': self}) - - def refresh(self): - """This should be done offline.""" - self.short_html - self.single - self.new_publishable - self.published - - def touch(self): - update = { - "_changed": self.is_changed(), - "_short_html": None, - "_new_publishable": self.is_new_publishable(), - "_published": self.is_published(), - } - Image.objects.filter(pk=self.pk).update(**update) - refresh_instance(self) - - def refresh(self): - """This should be done offline.""" - self.changed - self.short_html - - - # Publishing - # ========== - - def publish(self, user): - """Publishes the picture on behalf of a (local) user.""" - from base64 import b64encode - import apiclient - from catalogue.signals import post_publish - - self.assert_publishable() - change = self.publishable() - picture_xml = change.materialize() - picture_data = open(self.image.path).read() - apiclient.api_call(user, "pictures/", { - "picture_xml": picture_xml, - "picture_image_data": b64encode(picture_data), - }) - # record the publish - log = self.publish_log.create(user=user, change=change) - post_publish.send(sender=log) diff --git a/apps/catalogue/models/listeners.py b/apps/catalogue/models/listeners.py deleted file mode 100755 index 1cfac276..00000000 --- a/apps/catalogue/models/listeners.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.contrib.auth.models import User -from django.db import models -from catalogue.models import (Book, Chunk, Image, BookPublishRecord, - ImagePublishRecord) -from catalogue.signals import post_publish -from dvcs.signals import post_publishable - - -def book_changed(sender, instance, created, **kwargs): - instance.touch() - for c in instance: - c.touch() -models.signals.post_save.connect(book_changed, sender=Book) - - -def chunk_changed(sender, instance, created, **kwargs): - instance.book.touch() - instance.touch() -models.signals.post_save.connect(chunk_changed, sender=Chunk) - - -def image_changed(sender, instance, created, **kwargs): - instance.touch() -models.signals.post_save.connect(image_changed, sender=Image) - - -def user_changed(sender, instance, *args, **kwargs): - if 'last_login' in (kwargs.get('update_fields') or {}): - # Quick hack - this change seems to result from logging user in so just ignore it. - return - books = set() - for c in instance.chunk_set.all(): - books.add(c.book) - c.touch() - for b in books: - b.touch() -models.signals.post_save.connect(user_changed, sender=User) - - -def publish_listener(sender, *args, **kwargs): - if isinstance(sender, BookPublishRecord): - sender.book.touch() - for c in sender.book: - c.touch() - elif isinstance(sender, ImagePublishRecord): - sender.image.touch() -post_publish.connect(publish_listener) - - -def chunk_publishable_listener(sender, *args, **kwargs): - sender.tree.touch() - if isinstance(sender.tree, Chunk): - sender.tree.book.touch() -post_publishable.connect(chunk_publishable_listener) - -def publishable_listener(sender, *args, **kwargs): - sender.tree.touch() -post_publishable.connect(publishable_listener, sender=Image) - - -def listener_create(sender, instance, created, **kwargs): - if created: - instance.chunk_set.create(number=1, slug='1') -models.signals.post_save.connect(listener_create, sender=Book) - diff --git a/apps/catalogue/models/project.py b/apps/catalogue/models/project.py deleted file mode 100755 index eb951021..00000000 --- a/apps/catalogue/models/project.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.db import models -from django.utils.translation import ugettext_lazy as _ - - -class Project(models.Model): - """ A project, tracked for funding purposes. """ - - name = models.CharField(_('name'), max_length=255, unique=True) - notes = models.TextField(_('notes'), blank=True, null=True) - - class Meta: - app_label = 'catalogue' - ordering = ['name'] - verbose_name = _('project') - verbose_name_plural = _('projects') - - def __unicode__(self): - return self.name diff --git a/apps/catalogue/models/publish_log.py b/apps/catalogue/models/publish_log.py deleted file mode 100755 index 7a8e2f9e..00000000 --- a/apps/catalogue/models/publish_log.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.contrib.auth.models import User -from django.db import models -from django.utils.translation import ugettext_lazy as _ -from catalogue.models import Chunk, Image - - -class BookPublishRecord(models.Model): - """ - A record left after publishing a Book. - """ - - book = models.ForeignKey('Book', verbose_name=_('book'), related_name='publish_log') - timestamp = models.DateTimeField(_('time'), auto_now_add=True) - user = models.ForeignKey(User, verbose_name=_('user')) - - class Meta: - app_label = 'catalogue' - ordering = ['-timestamp'] - verbose_name = _('book publish record') - verbose_name_plural = _('book publish records') - - -class ChunkPublishRecord(models.Model): - """ - BookPublishRecord details for each Chunk. - """ - - book_record = models.ForeignKey(BookPublishRecord, verbose_name=_('book publish record')) - change = models.ForeignKey(Chunk.change_model, related_name='publish_log', verbose_name=_('change')) - - class Meta: - app_label = 'catalogue' - verbose_name = _('chunk publish record') - verbose_name_plural = _('chunk publish records') - - -class ImagePublishRecord(models.Model): - """A record left after publishing an Image.""" - - image = models.ForeignKey(Image, verbose_name=_('image'), related_name='publish_log') - timestamp = models.DateTimeField(_('time'), auto_now_add=True) - user = models.ForeignKey(User, verbose_name=_('user')) - change = models.ForeignKey(Image.change_model, related_name='publish_log', verbose_name=_('change')) - - class Meta: - app_label = 'catalogue' - ordering = ['-timestamp'] - verbose_name = _('image publish record') - verbose_name_plural = _('image publish records') diff --git a/apps/catalogue/signals.py b/apps/catalogue/signals.py deleted file mode 100644 index 62ca5145..00000000 --- a/apps/catalogue/signals.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.dispatch import Signal - -post_publish = Signal() diff --git a/apps/catalogue/tasks.py b/apps/catalogue/tasks.py deleted file mode 100644 index 9507c412..00000000 --- a/apps/catalogue/tasks.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -from celery.task import task -from django.utils import translation - - -@task(ignore_result=True) -def _refresh_by_pk(cls, pk, language=None): - prev_language = translation.get_language() - if language: - translation.activate(language) - try: - cls._default_manager.get(pk=pk).refresh() - except cls.DoesNotExist: - pass - finally: - translation.activate(prev_language) - - -def refresh_instance(instance): - _refresh_by_pk.delay(type(instance), instance.pk, translation.get_language()) - - -@task(ignore_result=True) -def book_content_updated(book): - book.refresh_dc_cache() diff --git a/apps/catalogue/templates/catalogue/active_users_list.html b/apps/catalogue/templates/catalogue/active_users_list.html deleted file mode 100755 index f711b605..00000000 --- a/apps/catalogue/templates/catalogue/active_users_list.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} - - -{% block titleextra %}{% trans "Active users" %}{% endblock %} - - -{% block content %} - -

- {% trans "Active users since" %} {{ since }} -

- -
    -{% for email, names, count in users %} -
  • {% for name in names %}{{ name }}, {% endfor %}{{ email }} ({{ count }})
  • -{% endfor %} -
- -{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/activity.html b/apps/catalogue/templates/catalogue/activity.html deleted file mode 100755 index 3bb8afb2..00000000 --- a/apps/catalogue/templates/catalogue/activity.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} -{% load wall %} - - -{% block titleextra %}{% trans "Activity" %}{% endblock %} - - -{% block content %} - -

< - {% trans "Activity" %}: {{ day }} - {% if next_day %} - > - {% endif %} -

- - {% day_wall day %} -{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/base.html b/apps/catalogue/templates/catalogue/base.html deleted file mode 100644 index 8577f104..00000000 --- a/apps/catalogue/templates/catalogue/base.html +++ /dev/null @@ -1,54 +0,0 @@ -{% load pipeline i18n %} -{% load catalogue %} - - - - - - {% stylesheet 'catalogue' %} - {% block title %}{% block titleextra %}{% endblock %} :: - {% trans "Platforma Redakcyjna" %}{% endblock title %} - {% block add_css %}{% endblock %} - - - -
- - - - - -
- {% main_tabs %} -
- - - {% include "registration/head_login.html" %} - - -
-
- -
- -{% block content %} -
- {% block leftcolumn %} - {% endblock leftcolumn %} -
-
- {% block rightcolumn %} - {% endblock rightcolumn %} -
-{% endblock content %} - -
- - - -{% javascript 'catalogue' %} -{% block add_js %}{% endblock %} -{% block extrabody %} -{% endblock %} - - diff --git a/apps/catalogue/templates/catalogue/book_append_to.html b/apps/catalogue/templates/catalogue/book_append_to.html deleted file mode 100755 index c1ecc290..00000000 --- a/apps/catalogue/templates/catalogue/book_append_to.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} - -{% block titleextra %}{% trans "Append book" %}{% endblock %} - -{% block leftcolumn %} -
- {% csrf_token %} - {{ form.as_p }} - -

-
-{% endblock leftcolumn %} - -{% block rightcolumn %} -{% endblock rightcolumn %} diff --git a/apps/catalogue/templates/catalogue/book_detail.html b/apps/catalogue/templates/catalogue/book_detail.html deleted file mode 100755 index 4712edf3..00000000 --- a/apps/catalogue/templates/catalogue/book_detail.html +++ /dev/null @@ -1,103 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load book_list i18n %} - - -{% block titleextra %}{{ book.title }}{% endblock %} - - -{% block content %} - - -

{{ book.title }}

- - -{% if editable %}
{% csrf_token %}{% endif %} - - {{ form.as_table }} - {% if editable %} - - {% endif %} -
-{% if editable %}
{% endif %} - -{% if editable %} - {% if book.gallery %} -

{% trans "Edit gallery" %}

- {% endif %} - -

{% trans "Append to other book" %}

-{% endif %} - - -
- -

{% trans "Chunks" %}

- - - {% for chunk in book %} - {{ chunk.short_html|safe }} - {% endfor %} -
- -
- - - -
- - -

{% trans "Publication" %}

- -
- -{% if book.dc_cover_image %} - {{ book.dc_cover_image }} -{% endif %} -
- -

{% trans "Last published" %}: - {% if book.last_published %} - {{ book.last_published }} - {% else %} - — - {% endif %} -

- -{% if publishable %} -

- {% trans "Full XML" %}
- {% trans "HTML version" %}
- {% trans "TXT version" %}
- {% trans "PDF version" %}
- {% trans "PDF version for mobiles" %}
- {% trans "EPUB version" %}
- {% trans "MOBI version" %}
-

- - {% if user.is_authenticated %} - -
{% csrf_token %} - {{ publish_options_form.as_p }} - - - -
- {% else %} - {% trans "Log in to publish." %} - {% endif %} -{% else %} -

{% trans "This book can't be published yet, because:" %}

-
  • {{ publishable_error }}
-{% endif %} - -
-
- - -{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/book_edit.html b/apps/catalogue/templates/catalogue/book_edit.html deleted file mode 100755 index 43fe0ea9..00000000 --- a/apps/catalogue/templates/catalogue/book_edit.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} - - -{% block titleextra %}{% trans "Edit book" %}{% endblock %} - - -{% block leftcolumn %} -
- {% csrf_token %} - {{ form.as_p }} - -

-
-{% endblock leftcolumn %} - -{% block rightcolumn %} -{% endblock rightcolumn %} diff --git a/apps/catalogue/templates/catalogue/book_html.html b/apps/catalogue/templates/catalogue/book_html.html deleted file mode 100755 index 518811ee..00000000 --- a/apps/catalogue/templates/catalogue/book_html.html +++ /dev/null @@ -1,29 +0,0 @@ -{% load i18n %} - - - - - {{ book.title }} - - - -
- {#% book_info book %#} -
- - - {{ html|safe }} - - - diff --git a/apps/catalogue/templates/catalogue/book_list/book.html b/apps/catalogue/templates/catalogue/book_list/book.html deleted file mode 100755 index f6a0fcd2..00000000 --- a/apps/catalogue/templates/catalogue/book_list/book.html +++ /dev/null @@ -1,40 +0,0 @@ -{% load i18n %} -{% load username from common_tags %} - -{% if book.single %} - {% with book.0 as chunk %} - - - [B] - [c] - - {{ book.title }} - {% if chunk.stage %} - {{ chunk.stage }} - {% else %}– - {% endif %} - {% if chunk.user %}{{ chunk.user|username }}{% endif %} - - {% if chunk.published %}P{% endif %} - {% if book.new_publishable %}p{% endif %} - {% if chunk.changed %}+{% endif %} - - {{ book.project.name }} - - {% endwith %} -{% else %} - - - [B] - - {{ book.title }} - - - - {% if book.published %}P{% endif %} - {% if book.new_publishable %}p{% endif %} - - {{ book.project.name }} - -{% endif %} diff --git a/apps/catalogue/templates/catalogue/book_list/book_list.html b/apps/catalogue/templates/catalogue/book_list/book_list.html deleted file mode 100755 index e238827b..00000000 --- a/apps/catalogue/templates/catalogue/book_list/book_list.html +++ /dev/null @@ -1,114 +0,0 @@ -{% load i18n %} -{% load pagination_tags %} -{% load username from common_tags %} - - -
- - -{% if not viewed_user %} - -{% endif %} - - - -
- - - - - - - - - - - {% if not viewed_user %} - - {% else %} - - {% endif %} - - - - - - - - {% with cnt=books|length %} - {% autopaginate books 100 %} - - {% for item in books %} - {% with item.book as book %} - {{ book.short_html|safe }} - {% if not book.single %} - {% for chunk in item.chunks %} - {{ chunk.short_html|safe }} - {% endfor %} - {% endif %} - {% endwith %} - {% endfor %} - - - {% endwith %} -
- - -
- -
-
- {% paginate %} - {% blocktrans count c=cnt %}{{c}} book{% plural %}{{c}} books{% endblocktrans %}
-{% if not books %} -

{% trans "No books found." %}

-{% endif %} - - - - diff --git a/apps/catalogue/templates/catalogue/book_list/chunk.html b/apps/catalogue/templates/catalogue/book_list/chunk.html deleted file mode 100755 index 0d218954..00000000 --- a/apps/catalogue/templates/catalogue/book_list/chunk.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load i18n %} -{% load username from common_tags %} - - - - - [c] - - {{ chunk.number }}. - {{ chunk.title }} - {% if chunk.stage %} - {{ chunk.stage }} - {% else %} - – - {% endif %} - {% if chunk.user %} - - {{ chunk.user|username }} - {% else %} - - {% endif %} - - - {% if chunk.new_publishable %}p{% endif %} - {% if chunk.changed %}+{% endif %} - - diff --git a/apps/catalogue/templates/catalogue/book_text.html b/apps/catalogue/templates/catalogue/book_text.html deleted file mode 100644 index 2e484483..00000000 --- a/apps/catalogue/templates/catalogue/book_text.html +++ /dev/null @@ -1,28 +0,0 @@ -{% load i18n pipeline %} - - - - - {% trans "Redakcja" %} :: {{ book.title }} - {% stylesheet 'book' %} - - {% javascript 'book' %} - - - - - {{ html|safe }} - - diff --git a/apps/catalogue/templates/catalogue/chunk_add.html b/apps/catalogue/templates/catalogue/chunk_add.html deleted file mode 100755 index f813b6f3..00000000 --- a/apps/catalogue/templates/catalogue/chunk_add.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} - - -{% block titleextra %}{% trans "Split chunk" %}{% endblock %} - - -{% block content %} -

{% trans "Split chunk" %}

- -
- {% csrf_token %} - - - - {{ form.as_table }} - -
{% trans "Insert empty chunk after" %}:{{ chunk.pretty_name }}
-
-{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/chunk_edit.html b/apps/catalogue/templates/catalogue/chunk_edit.html deleted file mode 100755 index 20062265..00000000 --- a/apps/catalogue/templates/catalogue/chunk_edit.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} - - -{% block titleextra %}{% trans "Chunk settings" %}{% endblock %} - - -{% block content %} -

{% trans "Chunk settings" %}

- -
- {% csrf_token %} - - - {{ form.as_table}} - -
{% trans "Book" %}:{{ chunk.book }} ({{ chunk.number }}/{{ chunk.book|length }})
- -
- - -

{% trans "Split chunk" %}

- -{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/document_create_missing.html b/apps/catalogue/templates/catalogue/document_create_missing.html deleted file mode 100644 index aa2ce06c..00000000 --- a/apps/catalogue/templates/catalogue/document_create_missing.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} - - -{% block titleextra %}{% trans "Create a new book" %}{% endblock %} - - -{% block content %} -

{% trans "Create a new book" %}

- -
- {% csrf_token %} - - {{ form.as_table}} - -
-
-{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/document_list.html b/apps/catalogue/templates/catalogue/document_list.html deleted file mode 100644 index fe3598e1..00000000 --- a/apps/catalogue/templates/catalogue/document_list.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "catalogue/base.html" %} - -{% load i18n %} -{% load catalogue book_list %} -{% load pipeline %} - -{% block titleextra %}{% trans "Book list" %}{% endblock %} - - -{% block add_js %} -{% javascript 'book_list' %} -{% endblock %} - -{% block add_css %} -{% stylesheet 'book_list' %} -{% endblock %} - -{% block content %} - {% book_list %} -{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/document_upload.html b/apps/catalogue/templates/catalogue/document_upload.html deleted file mode 100644 index 009d1540..00000000 --- a/apps/catalogue/templates/catalogue/document_upload.html +++ /dev/null @@ -1,72 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} - - -{% block titleextra %}{% trans "Bulk document upload" %}{% endblock %} - - -{% block leftcolumn %} - - -

{% trans "Bulk documents upload" %}

- -

-{% trans "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with .xml will be ignored." %} -

- -
-{% csrf_token %} -{{ form.as_p }} -

-
- -
- -{% if error_list %} - -

{% trans "There have been some errors. No files have been added to the repository." %} -

{% trans "Offending files" %}

-
    - {% for filename, title, error in error_list %} -
  • {{ title }} ({{ filename }}): {{ error }}
  • - {% endfor %} -
- - {% if ok_list %} -

{% trans "Correct files" %}

-
    - {% for filename, slug, title in ok_list %} -
  • {{ title }} ({{ filename }})
  • - {% endfor %} -
- {% endif %} - -{% else %} - - {% if ok_list %} -

{% trans "Files have been successfully uploaded to the repository." %}

-

{% trans "Uploaded files" %}

-
    - {% for filename, slug, title in ok_list %} -
  • {{ title }} ({{ filename }})
  • - {% endfor %} -
- {% endif %} -{% endif %} - -{% if skipped_list %} -

{% trans "Skipped files" %}

-

{% trans "Files skipped due to no .xml extension" %}

-
    - {% for filename in skipped_list %} -
  • {{ filename }}
  • - {% endfor %} -
-{% endif %} - - -{% endblock leftcolumn %} - - -{% block rightcolumn %} -{% endblock rightcolumn %} diff --git a/apps/catalogue/templates/catalogue/image_detail.html b/apps/catalogue/templates/catalogue/image_detail.html deleted file mode 100755 index 8ad2a63f..00000000 --- a/apps/catalogue/templates/catalogue/image_detail.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load book_list i18n %} - - -{% block titleextra %}{{ object.title }}{% endblock %} - - -{% block content %} - - -

{{ object.title }}

- - -{% if editable %}
{% csrf_token %}{% endif %} - - {{ form.as_table }} - {% if editable %} - - {% endif %} -
-{% if editable %}
{% endif %} - - - -
-

{% trans "Editor" %}

- -

{% trans "Proceed to the editor." %}

-
- - - -
- - -

{% trans "Publication" %}

- -

{% trans "Last published" %}: - {% if object.last_published %} - {{ object.last_published }} - {% else %} - — - {% endif %} -

- -{% if publishable %} - {% if user.is_authenticated %} - -
{% csrf_token %} - - - -
- {% else %} - {% trans "Log in to publish." %} - {% endif %} -{% else %} -

{% trans "This book can't be published yet, because:" %}

-
  • {{ publishable_error }}
-{% endif %} - -
- - -{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/image_list.html b/apps/catalogue/templates/catalogue/image_list.html deleted file mode 100755 index 4ce1668a..00000000 --- a/apps/catalogue/templates/catalogue/image_list.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "catalogue/base.html" %} - -{% load i18n %} -{% load catalogue book_list %} -{% load pipeline %} - - -{% block titleextra %}{% trans "Image list" %}{% endblock %} - - -{% block add_js %} -{% javascript 'book_list' %} -{% endblock %} - -{% block add_css %} -{% stylesheet 'book_list' %} -{% endblock %} - - -{% block content %} - {% image_list %} -{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/image_short.html b/apps/catalogue/templates/catalogue/image_short.html deleted file mode 100755 index c7ff77b7..00000000 --- a/apps/catalogue/templates/catalogue/image_short.html +++ /dev/null @@ -1,21 +0,0 @@ -{% load i18n %} -{% load username from common_tags %} - - - - [B] - - {{ image.title }} - {% if image.stage %} - {{ image.stage }} - {% else %}– - {% endif %} - {% if image.user %}{{ image.user|username }}{% endif %} - - {% if image.published %}P{% endif %} - {% if image.new_publishable %}p{% endif %} - {% if image.changed %}+{% endif %} - - {{ image.project.name }} - diff --git a/apps/catalogue/templates/catalogue/image_table.html b/apps/catalogue/templates/catalogue/image_table.html deleted file mode 100755 index e6caedda..00000000 --- a/apps/catalogue/templates/catalogue/image_table.html +++ /dev/null @@ -1,100 +0,0 @@ -{% load i18n %} -{% load pagination_tags %} -{% load username from common_tags %} - - - -
- - -{% if not viewed_user %} - -{% endif %} - - -
- - - - - - - - - {% if not viewed_user %} - - {% endif %} - - - - - - - - {% with cnt=objects|length %} - {% autopaginate objects 100 %} - - {% for item in objects %} - {{ item.short_html|safe }} - {% endfor %} - - - {% endwith %} -
-
- -
-
- {% paginate %} - {% blocktrans count c=cnt %}{{c}} image{% plural %}{{c}} images{% endblocktrans %}
-{% if not objects %} -

{% trans "No images found." %}

-{% endif %} - - - - diff --git a/apps/catalogue/templates/catalogue/main_tabs.html b/apps/catalogue/templates/catalogue/main_tabs.html deleted file mode 100755 index 82321cc4..00000000 --- a/apps/catalogue/templates/catalogue/main_tabs.html +++ /dev/null @@ -1,3 +0,0 @@ -{% for tab in tabs %} - {{ tab.caption }} -{% endfor %} diff --git a/apps/catalogue/templates/catalogue/mark_final.html b/apps/catalogue/templates/catalogue/mark_final.html deleted file mode 100644 index 9ed740c0..00000000 --- a/apps/catalogue/templates/catalogue/mark_final.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "catalogue/base.html" %} - -{% block titleextra %}Oznacz książki{% endblock %} - - -{% block leftcolumn %} - -

Oznacz książki

- -
- {% csrf_token %} - {{ form.as_p }} - -
- -{% endblock leftcolumn %} \ No newline at end of file diff --git a/apps/catalogue/templates/catalogue/mark_final_completed.html b/apps/catalogue/templates/catalogue/mark_final_completed.html deleted file mode 100644 index 1b37c836..00000000 --- a/apps/catalogue/templates/catalogue/mark_final_completed.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "catalogue/base.html" %} - -{% block titleextra %}Oznaczono książki{% endblock %} - - -{% block leftcolumn %} - -

Oznaczono książki

- -

Książki zostały oznaczone.

- -{% endblock leftcolumn %} \ No newline at end of file diff --git a/apps/catalogue/templates/catalogue/my_page.html b/apps/catalogue/templates/catalogue/my_page.html deleted file mode 100755 index d3347503..00000000 --- a/apps/catalogue/templates/catalogue/my_page.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "catalogue/base.html" %} - -{% load i18n %} -{% load catalogue book_list wall %} -{% load pipeline %} - -{% block add_js %} -{% javascript 'book_list' %} -{% endblock %} - -{% block add_css %} -{% stylesheet 'book_list' %} -{% endblock %} - -{% block titleextra %}{% trans "My page" %}{% endblock %} - - -{% block leftcolumn %} - {% book_list request.user %} -{% endblock leftcolumn %} - -{% block rightcolumn %} -
-

{% trans "Your last edited documents" %}

-
    - {% for edit_url, item in last_books %} -
  1. {{ item.title }}
    ({{ item.time|date:"H:i:s, d/m/Y" }})
  2. - {% endfor %} -
-
- -

{% trans "Recent activity for" %} {{ request.user|nice_name }}

- {% wall request.user 10 %} -{% endblock rightcolumn %} diff --git a/apps/catalogue/templates/catalogue/upload_pdf.html b/apps/catalogue/templates/catalogue/upload_pdf.html deleted file mode 100755 index 265b84ad..00000000 --- a/apps/catalogue/templates/catalogue/upload_pdf.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} - - -{% block titleextra %}{% trans "PDF file upload" %}{% endblock %} - - -{% block content %} - - -

{% trans "PDF file upload" %}

- -
-{% csrf_token %} -{{ form.as_p }} -

-
- - -{% endblock content %} diff --git a/apps/catalogue/templates/catalogue/user_list.html b/apps/catalogue/templates/catalogue/user_list.html deleted file mode 100755 index 460a38b9..00000000 --- a/apps/catalogue/templates/catalogue/user_list.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "catalogue/base.html" %} - -{% load i18n %} -{% load username from common_tags %} - - -{% block titleextra %}{% trans "Users" %}{% endblock %} - - -{% block leftcolumn %} - -

{% trans "Users" %}

- - - -{% endblock leftcolumn %} diff --git a/apps/catalogue/templates/catalogue/user_page.html b/apps/catalogue/templates/catalogue/user_page.html deleted file mode 100755 index 4be4ca3e..00000000 --- a/apps/catalogue/templates/catalogue/user_page.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "catalogue/base.html" %} - -{% load i18n %} -{% load catalogue book_list wall %} - - -{% block titleextra %}{{ viewed_user|nice_name }}{% endblock %} - - -{% block leftcolumn %} -

{{ viewed_user|nice_name }}

- {% book_list viewed_user %} -{% endblock leftcolumn %} - -{% block rightcolumn %} -

{% trans "Recent activity for" %} {{ viewed_user|nice_name }}

- {% wall viewed_user 10 %} -{% endblock rightcolumn %} diff --git a/apps/catalogue/templates/catalogue/wall.html b/apps/catalogue/templates/catalogue/wall.html deleted file mode 100755 index a107dfa8..00000000 --- a/apps/catalogue/templates/catalogue/wall.html +++ /dev/null @@ -1,37 +0,0 @@ -{% load i18n %} -{% load gravatar %} -{% load email %} -{% load username from common_tags %} - -
    -{% for item in wall %} -
  • -
    - {% if item.get_email %} - Avatar -
    - {% endif %} -
    - -
    {{ item.timestamp }}
    -

    {{ item.header }}

    - {{ item.title }} -
    {% trans "user" %}: - {% if item.user %} - - {{ item.user|username }} - <{{ item.user.email|email_link }}> - {% else %} - {{ item.user_name }} - {% if item.email %} - <{{ item.email|email_link }}> - {% endif %} - ({% trans "not logged in" %}) - {% endif %} -
    {{ item.summary|linebreaksbr }} -
  • -{% empty %} -
  • {% trans "No activity recorded." %}
  • -{% endfor %} -
diff --git a/apps/catalogue/templatetags/__init__.py b/apps/catalogue/templatetags/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/catalogue/templatetags/book_list.py b/apps/catalogue/templatetags/book_list.py deleted file mode 100755 index 9ac996b8..00000000 --- a/apps/catalogue/templatetags/book_list.py +++ /dev/null @@ -1,209 +0,0 @@ -from __future__ import absolute_import - -from re import split -from django.db.models import Q, Count -from django import template -from django.utils.translation import ugettext_lazy as _ -from django.contrib.auth.models import User -from catalogue.models import Chunk, Image, Project - -register = template.Library() - - -class ChunksList(object): - def __init__(self, chunk_qs): - #self.chunk_qs = chunk_qs#.annotate( - #book_length=Count('book__chunk')).select_related( - #'book')#, 'stage__name', - #'user') - self.chunk_qs = chunk_qs.select_related('book__hidden') - - self.book_qs = chunk_qs.values('book_id') - - def __getitem__(self, key): - if isinstance(key, slice): - return self.get_slice(key) - elif isinstance(key, int): - return self.get_slice(slice(key, key+1))[0] - else: - raise TypeError('Unsupported list index. Must be a slice or an int.') - - def __len__(self): - return self.book_qs.count() - - def get_slice(self, slice_): - book_ids = [x['book_id'] for x in self.book_qs[slice_]] - chunk_qs = self.chunk_qs.filter(book__in=book_ids) - - chunks_list = [] - book = None - for chunk in chunk_qs: - if chunk.book != book: - book = chunk.book - chunks_list.append(ChoiceChunks(book, [chunk])) - else: - chunks_list[-1].chunks.append(chunk) - return chunks_list - - -class ChoiceChunks(object): - """ - Associates the given chunks iterable for a book. - """ - - chunks = None - - def __init__(self, book, chunks): - self.book = book - self.chunks = chunks - - -def foreign_filter(qs, value, filter_field, model, model_field='slug', unset='-'): - if value == unset: - return qs.filter(**{filter_field: None}) - if not value: - return qs - try: - obj = model._default_manager.get(**{model_field: value}) - except model.DoesNotExist: - return qs.none() - else: - return qs.filter(**{filter_field: obj}) - - -def search_filter(qs, value, filter_fields): - if not value: - return qs - q = Q(**{"%s__icontains" % filter_fields[0]: value}) - for field in filter_fields[1:]: - q |= Q(**{"%s__icontains" % field: value}) - return qs.filter(q) - - -_states = [ - ('publishable', _('publishable'), Q(book___new_publishable=True)), - ('changed', _('changed'), Q(_changed=True)), - ('published', _('published'), Q(book___published=True)), - ('unpublished', _('unpublished'), Q(book___published=False)), - ('empty', _('empty'), Q(head=None)), - ] -_states_options = [s[:2] for s in _states] -_states_dict = dict([(s[0], s[2]) for s in _states]) - - -def document_list_filter(request, **kwargs): - - def arg_or_GET(field): - return kwargs.get(field, request.GET.get(field)) - - if arg_or_GET('all'): - chunks = Chunk.objects.all() - else: - chunks = Chunk.visible_objects.all() - - chunks = chunks.order_by('book__title', 'book', 'number') - - if not request.user.is_authenticated(): - chunks = chunks.filter(book__public=True) - - state = arg_or_GET('status') - if state in _states_dict: - chunks = chunks.filter(_states_dict[state]) - - chunks = foreign_filter(chunks, arg_or_GET('user'), 'user', User, 'username') - chunks = foreign_filter(chunks, arg_or_GET('stage'), 'stage', Chunk.tag_model, 'slug') - chunks = search_filter(chunks, arg_or_GET('title'), ['book__title', 'title']) - chunks = foreign_filter(chunks, arg_or_GET('project'), 'book__project', Project, 'pk') - return chunks - - -@register.inclusion_tag('catalogue/book_list/book_list.html', takes_context=True) -def book_list(context, user=None): - request = context['request'] - - if user: - filters = {"user": user} - new_context = {"viewed_user": user} - else: - filters = {} - new_context = { - "users": User.objects.annotate( - count=Count('chunk')).filter(count__gt=0).order_by( - '-count', 'last_name', 'first_name'), - "other_users": User.objects.annotate( - count=Count('chunk')).filter(count=0).order_by( - 'last_name', 'first_name'), - } - - new_context.update({ - "filters": True, - "request": request, - "books": ChunksList(document_list_filter(request, **filters)), - "stages": Chunk.tag_model.objects.all(), - "states": _states_options, - "projects": Project.objects.all(), - }) - - return new_context - - - -_image_states = [ - ('publishable', _('publishable'), Q(_new_publishable=True)), - ('changed', _('changed'), Q(_changed=True)), - ('published', _('published'), Q(_published=True)), - ('unpublished', _('unpublished'), Q(_published=False)), - ('empty', _('empty'), Q(head=None)), - ] -_image_states_options = [s[:2] for s in _image_states] -_image_states_dict = dict([(s[0], s[2]) for s in _image_states]) - -def image_list_filter(request, **kwargs): - - def arg_or_GET(field): - return kwargs.get(field, request.GET.get(field)) - - images = Image.objects.all() - - if not request.user.is_authenticated(): - images = images.filter(public=True) - - state = arg_or_GET('status') - if state in _image_states_dict: - images = images.filter(_image_states_dict[state]) - - images = foreign_filter(images, arg_or_GET('user'), 'user', User, 'username') - images = foreign_filter(images, arg_or_GET('stage'), 'stage', Image.tag_model, 'slug') - images = search_filter(images, arg_or_GET('title'), ['title', 'title']) - images = foreign_filter(images, arg_or_GET('project'), 'project', Project, 'pk') - return images - - -@register.inclusion_tag('catalogue/image_table.html', takes_context=True) -def image_list(context, user=None): - request = context['request'] - - if user: - filters = {"user": user} - new_context = {"viewed_user": user} - else: - filters = {} - new_context = { - "users": User.objects.annotate( - count=Count('image')).filter(count__gt=0).order_by( - '-count', 'last_name', 'first_name'), - "other_users": User.objects.annotate( - count=Count('image')).filter(count=0).order_by( - 'last_name', 'first_name'), - } - - new_context.update({ - "filters": True, - "request": request, - "objects": image_list_filter(request, **filters), - "stages": Image.tag_model.objects.all(), - "states": _image_states_options, - "projects": Project.objects.all(), - }) - - return new_context diff --git a/apps/catalogue/templatetags/catalogue.py b/apps/catalogue/templatetags/catalogue.py deleted file mode 100644 index 07c5cf9d..00000000 --- a/apps/catalogue/templatetags/catalogue.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import absolute_import - -from django.core.urlresolvers import reverse -from django import template -from django.utils.translation import ugettext as _ - -register = template.Library() - - -class Tab(object): - slug = None - caption = None - url = None - - def __init__(self, slug, caption, url): - self.slug = slug - self.caption = caption - self.url = url - - -@register.inclusion_tag("catalogue/main_tabs.html", takes_context=True) -def main_tabs(context): - active = getattr(context['request'], 'catalogue_active_tab', None) - - tabs = [] - user = context['user'] - tabs.append(Tab('my', _('My page'), reverse("catalogue_user"))) - - tabs.append(Tab('activity', _('Activity'), reverse("catalogue_activity"))) - tabs.append(Tab('all', _('All'), reverse("catalogue_document_list"))) - tabs.append(Tab('images', _('Images'), reverse("catalogue_image_list"))) - tabs.append(Tab('users', _('Users'), reverse("catalogue_users"))) - - if user.has_perm('catalogue.add_book'): - tabs.append(Tab('create', _('Add'), reverse("catalogue_create_missing"))) - tabs.append(Tab('upload', _('Upload'), reverse("catalogue_upload"))) - - tabs.append(Tab('cover', _('Covers'), reverse("cover_image_list"))) - - return {"tabs": tabs, "active_tab": active} - - -@register.filter -def nice_name(user): - return user.get_full_name() or user.username - diff --git a/apps/catalogue/templatetags/common_tags.py b/apps/catalogue/templatetags/common_tags.py deleted file mode 100755 index 7f5d0e9f..00000000 --- a/apps/catalogue/templatetags/common_tags.py +++ /dev/null @@ -1,6 +0,0 @@ -from django import template -register = template.Library() - -@register.filter -def username(user): - return ("%s %s" % (user.first_name, user.last_name)).lstrip() or user.username diff --git a/apps/catalogue/templatetags/set_get_parameter.py b/apps/catalogue/templatetags/set_get_parameter.py deleted file mode 100755 index b3d44d73..00000000 --- a/apps/catalogue/templatetags/set_get_parameter.py +++ /dev/null @@ -1,46 +0,0 @@ -from re import split - -from django import template - -register = template.Library() - - -""" -In template: - {% set_get_paramater param1='const_value',param2=,param3=variable %} -results with changes to query string: - param1 is set to `const_value' string - param2 is unset, if exists, - param3 is set to the value of variable in context - -Using 'django.core.context_processors.request' is required. - -""" - - -class SetGetParameter(template.Node): - def __init__(self, values): - self.values = values - - def render(self, context): - request = template.Variable('request').resolve(context) - params = request.GET.copy() - for key, value in self.values.items(): - if value == '': - if key in params: - del(params[key]) - else: - params[key] = template.Variable(value).resolve(context) - return '?%s' % params.urlencode() - - -@register.tag -def set_get_parameter(parser, token): - parts = split(r'\s+', token.contents, 2) - - values = {} - for pair in parts[1].split(','): - s = pair.split('=') - values[s[0]] = s[1] - - return SetGetParameter(values) diff --git a/apps/catalogue/templatetags/wall.py b/apps/catalogue/templatetags/wall.py deleted file mode 100755 index d000421a..00000000 --- a/apps/catalogue/templatetags/wall.py +++ /dev/null @@ -1,210 +0,0 @@ -from __future__ import absolute_import - -from datetime import timedelta -from django.db.models import Q -from django.core.urlresolvers import reverse -from django.contrib.comments.models import Comment -from django import template -from django.utils.translation import ugettext as _ - -from catalogue.models import Chunk, BookPublishRecord, Image, ImagePublishRecord - -register = template.Library() - - -class WallItem(object): - title = '' - summary = '' - url = '' - timestamp = '' - user = None - user_name = '' - 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(user=None, max_len=None, day=None): - qs = Chunk.change_model.objects.order_by('-created_at') - qs = qs.select_related('author', 'tree', 'tree__book__title') - if user is not None: - qs = qs.filter(Q(author=user) | Q(tree__user=user)) - if max_len is not None: - qs = qs[:max_len] - if day is not None: - next_day = day + timedelta(1) - qs = qs.filter(created_at__gte=day, created_at__lt=next_day) - for item in qs: - tag = 'stage' if item.tags.count() else 'change' - chunk = item.tree - w = WallItem(tag) - if user and item.author != user: - w.header = _('Related edit') - else: - w.header = _('Edit') - 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.user_name = item.author_name - w.email = item.author_email - yield w - - -def image_changes_wall(user=None, max_len=None, day=None): - qs = Image.change_model.objects.order_by('-created_at') - qs = qs.select_related('author', 'tree', 'tree__title') - if user is not None: - qs = qs.filter(Q(author=user) | Q(tree__user=user)) - if max_len is not None: - qs = qs[:max_len] - if day is not None: - next_day = day + timedelta(1) - qs = qs.filter(created_at__gte=day, created_at__lt=next_day) - for item in qs: - tag = 'stage' if item.tags.count() else 'change' - image = item.tree - w = WallItem(tag) - if user and item.author != user: - w.header = _('Related edit') - else: - w.header = _('Edit') - w.title = image.title - w.summary = item.description - w.url = reverse('wiki_img_editor', - args=[image.slug]) + '?diff=%d' % item.revision - w.timestamp = item.created_at - w.user = item.author - w.user_name = item.author_name - w.email = item.author_email - yield w - - - -# TODO: marked for publishing - - -def published_wall(user=None, max_len=None, day=None): - qs = BookPublishRecord.objects.select_related('book__title') - if user: - # TODO: published my book - qs = qs.filter(Q(user=user)) - if max_len is not None: - qs = qs[:max_len] - if day is not None: - next_day = day + timedelta(1) - qs = qs.filter(timestamp__gte=day, timestamp__lt=next_day) - for item in qs: - w = WallItem('publish') - w.header = _('Publication') - w.title = item.book.title - w.timestamp = item.timestamp - w.url = item.book.get_absolute_url() - w.user = item.user - w.email = item.user.email - yield w - - -def image_published_wall(user=None, max_len=None, day=None): - qs = ImagePublishRecord.objects.select_related('image__title') - if user: - # TODO: published my book - qs = qs.filter(Q(user=user)) - if max_len is not None: - qs = qs[:max_len] - if day is not None: - next_day = day + timedelta(1) - qs = qs.filter(timestamp__gte=day, timestamp__lt=next_day) - for item in qs: - w = WallItem('publish') - w.header = _('Publication') - w.title = item.image.title - w.timestamp = item.timestamp - w.url = item.image.get_absolute_url() - w.user = item.user - w.email = item.user.email - yield w - - -def comments_wall(user=None, max_len=None, day=None): - qs = Comment.objects.filter(is_public=True).select_related().order_by('-submit_date') - if user: - # TODO: comments concerning my books - qs = qs.filter(Q(user=user)) - if max_len is not None: - qs = qs[:max_len] - if day is not None: - next_day = day + timedelta(1) - qs = qs.filter(submit_date__gte=day, submit_date__lt=next_day) - for item in qs: - w = WallItem('comment') - w.header = _('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 - ui = item.userinfo - w.email = item.email - w.user_name = item.name - yield w - - -def big_wall(walls, max_len=None): - """ - Takes some WallItem iterators and zips them into one big wall. - Input iterators must already be sorted by timestamp. - """ - subwalls = [] - for w in walls: - try: - subwalls.append([next(w), w]) - except StopIteration: - pass - - if max_len is None: - max_len = -1 - 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("catalogue/wall.html", takes_context=True) -def wall(context, user=None, max_len=100): - return { - "request": context['request'], - "STATIC_URL": context['STATIC_URL'], - "wall": big_wall([ - changes_wall(user, max_len), - published_wall(user, max_len), - image_changes_wall(user, max_len), - image_published_wall(user, max_len), - comments_wall(user, max_len), - ], max_len)} - -@register.inclusion_tag("catalogue/wall.html", takes_context=True) -def day_wall(context, day): - return { - "request": context['request'], - "STATIC_URL": context['STATIC_URL'], - "wall": big_wall([ - changes_wall(day=day), - published_wall(day=day), - image_changes_wall(day=day), - image_published_wall(day=day), - comments_wall(day=day), - ])} diff --git a/apps/catalogue/test_utils.py b/apps/catalogue/test_utils.py deleted file mode 100644 index 2b085450..00000000 --- a/apps/catalogue/test_utils.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -"""Testing utilities.""" - -from os.path import abspath, dirname, join - - -def get_fixture(path): - f_path = join(dirname(abspath(__file__)), 'tests/files', path) - with open(f_path) as f: - return unicode(f.read(), 'utf-8') diff --git a/apps/catalogue/tests/__init__.py b/apps/catalogue/tests/__init__.py deleted file mode 100644 index 533a6c53..00000000 --- a/apps/catalogue/tests/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from catalogue.tests.book import * -from catalogue.tests.gallery import * -from catalogue.tests.publish import * -from catalogue.tests.xml_updater import * diff --git a/apps/catalogue/tests/book.py b/apps/catalogue/tests/book.py deleted file mode 100644 index df6f3b4f..00000000 --- a/apps/catalogue/tests/book.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -"""Tests for manipulating books in the catalogue.""" - -from django.test import TestCase -from django.contrib.auth.models import User -from catalogue.models import Book - - -class ManipulationTests(TestCase): - - def setUp(self): - self.user = User.objects.create(username='tester') - self.book1 = Book.create(self.user, 'book 1', slug='book1') - self.book2 = Book.create(self.user, 'book 2', slug='book2') - - def test_append(self): - self.book1.append(self.book2) - self.assertEqual(Book.objects.all().count(), 1) - self.assertEqual(len(self.book1), 2) - - def test_append_to_self(self): - with self.assertRaises(AssertionError): - self.book1.append(Book.objects.get(pk=self.book1.pk)) - self.assertEqual(Book.objects.all().count(), 2) - self.assertEqual(len(self.book1), 1) - - def test_prepend_history(self): - self.book1.prepend_history(self.book2) - self.assertEqual(Book.objects.all().count(), 1) - self.assertEqual(len(self.book1), 1) - self.assertEqual(self.book1.materialize(), 'book 1') - - def test_prepend_history_to_self(self): - with self.assertRaises(AssertionError): - self.book1.prepend_history(self.book1) - self.assertEqual(Book.objects.all().count(), 2) - self.assertEqual(self.book1.materialize(), 'book 1') - self.assertEqual(self.book2.materialize(), 'book 2') - - def test_split_book(self): - self.book1.chunk_set.create(number=2, title='Second chunk', - slug='book3') - self.book1[1].commit('I survived!') - self.assertEqual(len(self.book1), 2) - self.book1.split() - self.assertEqual(set([b.slug for b in Book.objects.all()]), - set(['book2', '1', 'book3'])) - self.assertEqual( - Book.objects.get(slug='book3').materialize(), - 'I survived!') diff --git a/apps/catalogue/tests/files/chunk1.xml b/apps/catalogue/tests/files/chunk1.xml deleted file mode 100755 index 6a75580a..00000000 --- a/apps/catalogue/tests/files/chunk1.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - -Mickiewicz, Adam -Do M*** -Fundacja Nowoczesna Polska -Romantyzm -Liryka -Wiersz - -http://wolnelektury.pl/katalog/lektura/sonety-odeskie-do-m -http://www.polona.pl/Content/2222 -Mickiewicz, Adam (1798-1855), Poezje, tom 1 (Wiersze młodzieńcze - Ballady i romanse - Wiersze do r. 1824), Krakowska Spółdzielnia Wydawnicza, wyd. 2 zwiększone, Kraków, 1922 - -Domena publiczna - Adam Mickiewicz zm. 1855 -1926 -xml -text -text -2007-09-06 -pol - - - - -Adam Mickiewicz -Sonety odeskie -Do M*** - -Wiérsz napisany w roku 1822 - - -Precz z moich oczu!... posłucham od razu,/ -Precz z mego serca!... i serce posłucha,/ -Precz z méj pamięci!... Nie! tego rozkazu/ -Moja i twoja pamięć nie posłucha. - - - - diff --git a/apps/catalogue/tests/files/chunk2.xml b/apps/catalogue/tests/files/chunk2.xml deleted file mode 100755 index 63a243e1..00000000 --- a/apps/catalogue/tests/files/chunk2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - -Jak cień tém dłuższy, gdy padnie z daleka,/ -Tém szerzéj koło żałobne roztoczy,/ -Tak moja postać, im daléj ucieka,/ -Tém grubszym kirem twą pamięć pomroczy. - - - - diff --git a/apps/catalogue/tests/files/expected.xml b/apps/catalogue/tests/files/expected.xml deleted file mode 100755 index ff225a03..00000000 --- a/apps/catalogue/tests/files/expected.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - -Mickiewicz, Adam -Do M*** -Fundacja Nowoczesna Polska -Romantyzm -Liryka -Wiersz - -http://wolnelektury.pl/katalog/lektura/sonety-odeskie-do-m -http://www.polona.pl/Content/2222 -Mickiewicz, Adam (1798-1855), Poezje, tom 1 (Wiersze młodzieńcze - Ballady i romanse - Wiersze do r. 1824), Krakowska Spółdzielnia Wydawnicza, wyd. 2 zwiększone, Kraków, 1922 - -Domena publiczna - Adam Mickiewicz zm. 1855 -1926 -xml -text -text -2007-09-06 -pol - - - - -Adam Mickiewicz -Sonety odeskie -Do M*** - -Wiérsz napisany w roku 1822 - - -Precz z moich oczu!... posłucham od razu,/ -Precz z mego serca!... i serce posłucha,/ -Precz z méj pamięci!... Nie! tego rozkazu/ -Moja i twoja pamięć nie posłucha. - - - -Jak cień tém dłuższy, gdy padnie z daleka,/ -Tém szerzéj koło żałobne roztoczy,/ -Tak moja postać, im daléj ucieka,/ -Tém grubszym kirem twą pamięć pomroczy. - - - - diff --git a/apps/catalogue/tests/gallery.py b/apps/catalogue/tests/gallery.py deleted file mode 100644 index 4b8ea3f4..00000000 --- a/apps/catalogue/tests/gallery.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -"""Tests for galleries of scans.""" - -from os.path import join, basename, exists -from os import makedirs, listdir -from django.test import TestCase -from django.contrib.auth.models import User -from catalogue.models import Book -from tempfile import mkdtemp -from django.conf import settings - - -class GalleryAppendTests(TestCase): - def setUp(self): - self.user = User.objects.create(username='tester') - self.book1 = Book.create(self.user, 'book 1', slug='book1') - self.book1.chunk_set.create(number=2, title='Second chunk', - slug='book1-2') - c=self.book1[1] - c.gallery_start=3 - - self.scandir = join(settings.MEDIA_ROOT, settings.IMAGE_DIR) - if not exists(self.scandir): - makedirs(self.scandir) - - def make_gallery(self, book, files): - d = mkdtemp('gallery', dir=self.scandir) - for named, cont in files.items(): - f = open(join(d, named), 'w') - f.write(cont) - f.close() - book.gallery = basename(d) - - - def test_both_indexed(self): - self.book2 = Book.create(self.user, 'book 2', slug='book2') - self.book2.chunk_set.create(number=2, title='Second chunk of second book', - slug='book2-2') - - c = self.book2[1] - c.gallery_start = 3 - c.save() - - print "gallery starts:",self.book2[0].gallery_start, self.book2[1].gallery_start - - self.make_gallery(self.book1, { - '1-0001_1l' : 'aa', - '1-0001_2r' : 'bb', - '1-0002_1l' : 'cc', - '1-0002_2r' : 'dd', - }) - - self.make_gallery(self.book2, { - '1-0001_1l' : 'dd', # the same, should not be moved - '1-0001_2r' : 'ff', - '2-0002_1l' : 'gg', - '2-0002_2r' : 'hh', - }) - - self.book1.append(self.book2) - - files = listdir(join(self.scandir, self.book1.gallery)) - files.sort() - print files - self.assertEqual(files, [ - '1-0001_1l', - '1-0001_2r', - '1-0002_1l', - '1-0002_2r', - # '2-0001_1l', - '2-0001_2r', - '3-0002_1l', - '3-0002_2r', - ]) - - self.assertEqual((4, 6), (self.book1[2].gallery_start, self.book1[3].gallery_start)) - - - def test_none_indexed(self): - self.book2 = Book.create(self.user, 'book 2', slug='book2') - self.make_gallery(self.book1, { - '0001_1l' : 'aa', - '0001_2r' : 'bb', - '0002_1l' : 'cc', - '0002_2r' : 'dd', - }) - - self.make_gallery(self.book2, { - '0001_1l' : 'ee', - '0001_2r' : 'ff', - '0002_1l' : 'gg', - '0002_2r' : 'hh', - }) - - self.book1.append(self.book2) - - files = listdir(join(self.scandir, self.book1.gallery)) - files.sort() - print files - self.assertEqual(files, [ - '0-0001_1l', - '0-0001_2r', - '0-0002_1l', - '0-0002_2r', - '1-0001_1l', - '1-0001_2r', - '1-0002_1l', - '1-0002_2r', - ]) - - - def test_none_indexed(self): - import nose.tools - self.book2 = Book.create(self.user, 'book 2', slug='book2') - self.make_gallery(self.book1, { - '1-0001_1l' : 'aa', - '1-0001_2r' : 'bb', - '1002_1l' : 'cc', - '1002_2r' : 'dd', - }) - - self.make_gallery(self.book2, { - '0001_1l' : 'ee', - '0001_2r' : 'ff', - '0002_1l' : 'gg', - '0002_2r' : 'hh', - }) - - self.book1.append(self.book2) - - files = listdir(join(self.scandir, self.book1.gallery)) - files.sort() - print files - self.assertEqual(files, [ - '0-1-0001_1l', - '0-1-0001_2r', - '0-1002_1l', - '0-1002_2r', - '1-0001_1l', - '1-0001_2r', - '1-0002_1l', - '1-0002_2r', - ]) - diff --git a/apps/catalogue/tests/publish.py b/apps/catalogue/tests/publish.py deleted file mode 100644 index 93e02daa..00000000 --- a/apps/catalogue/tests/publish.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -"""Tests for the publishing process.""" - -from catalogue.test_utils import get_fixture - -from mock import patch -from django.test import TestCase -from django.contrib.auth.models import User -from catalogue.models import Book - - -class PublishTests(TestCase): - def setUp(self): - self.user = User.objects.create(username='tester') - self.text1 = get_fixture('chunk1.xml') - self.book = Book.create(self.user, self.text1, slug='test-book') - - @patch('apiclient.api_call') - def test_unpublishable(self, api_call): - with self.assertRaises(AssertionError): - self.book.publish(self.user) - - @patch('apiclient.api_call') - def test_publish(self, api_call): - self.book[0].head.set_publishable(True) - self.book.publish(self.user) - api_call.assert_called_with(self.user, 'books/', {"book_xml": self.text1, "days": 0}, beta=False) - - @patch('apiclient.api_call') - def test_publish_multiple(self, api_call): - self.book[0].head.set_publishable(True) - self.book[0].split(slug='part-2') - self.book[1].commit(get_fixture('chunk2.xml')) - self.book[1].head.set_publishable(True) - self.book.publish(self.user) - api_call.assert_called_with(self.user, 'books/', {"book_xml": get_fixture('expected.xml'), "days": 0}, beta=False) diff --git a/apps/catalogue/tests/xml_updater.py b/apps/catalogue/tests/xml_updater.py deleted file mode 100644 index 9fb5a4a0..00000000 --- a/apps/catalogue/tests/xml_updater.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -"""XmlUpdater tests.""" - -from catalogue.test_utils import get_fixture -from django.test import TestCase -from django.contrib.auth.models import User -from catalogue.models import Book -from catalogue.management import XmlUpdater -from librarian import DCNS - - -class XmlUpdaterTests(TestCase): - class SimpleUpdater(XmlUpdater): - @XmlUpdater.fixes_elements('.//' + DCNS('title')) - def fix_title(element, **kwargs): - element.text = element.text + " fixed" - return True - - def setUp(self): - self.user = User.objects.create(username='tester') - text = get_fixture('chunk1.xml') - Book.create(self.user, text, slug='test-book') - self.title = "Do M***" - - def test_xml_updater(self): - self.SimpleUpdater().run(self.user) - self.assertEqual( - Book.objects.get(slug='test-book').wldocument( - publishable=False).book_info.title, - self.title + " fixed" - ) diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py deleted file mode 100644 index e81c0a36..00000000 --- a/apps/catalogue/urls.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -from django.conf.urls import patterns, url -from django.contrib.auth.decorators import permission_required -from django.views.generic import RedirectView -from catalogue.feeds import PublishTrackFeed -from catalogue.views import GalleryView - - -urlpatterns = patterns('catalogue.views', - url(r'^$', RedirectView.as_view(url='catalogue/')), - - url(r'^images/$', 'image_list', name='catalogue_image_list'), - url(r'^image/(?P[^/]+)/$', 'image', name="catalogue_image"), - url(r'^image/(?P[^/]+)/publish$', 'publish_image', - name="catalogue_publish_image"), - - url(r'^catalogue/$', 'document_list', name='catalogue_document_list'), - url(r'^user/$', 'my', name='catalogue_user'), - url(r'^user/(?P[^/]+)/$', 'user', name='catalogue_user'), - url(r'^users/$', 'users', name='catalogue_users'), - url(r'^activity/$', 'activity', name='catalogue_activity'), - url(r'^activity/(?P\d{4}-\d{2}-\d{2})/$', - 'activity', name='catalogue_activity'), - - url(r'^upload/$', - 'upload', name='catalogue_upload'), - - url(r'^create/(?P[^/]*)/', - 'create_missing', name='catalogue_create_missing'), - url(r'^create/', - 'create_missing', name='catalogue_create_missing'), - - url(r'^book/(?P[^/]+)/publish$', 'publish', name="catalogue_publish"), - - url(r'^book/(?P[^/]+)/$', 'book', name="catalogue_book"), - url(r'^book/(?P[^/]+)/gallery/$', - permission_required('catalogue.change_book')(GalleryView.as_view()), - name="catalogue_book_gallery"), - url(r'^book/(?P[^/]+)/xml$', 'book_xml', name="catalogue_book_xml"), - url(r'^book/dc/(?P[^/]+)/xml$', 'book_xml_dc', name="catalogue_book_xml_dc"), - url(r'^book/(?P[^/]+)/txt$', 'book_txt', name="catalogue_book_txt"), - url(r'^book/(?P[^/]+)/html$', 'book_html', name="catalogue_book_html"), - url(r'^book/(?P[^/]+)/epub$', 'book_epub', name="catalogue_book_epub"), - url(r'^book/(?P[^/]+)/mobi$', 'book_mobi', name="catalogue_book_mobi"), - url(r'^book/(?P[^/]+)/pdf$', 'book_pdf', name="catalogue_book_pdf"), - url(r'^book/(?P[^/]+)/pdf-mobile$', 'book_pdf', kwargs={'mobile': True}, name="catalogue_book_pdf_mobile"), - - url(r'^chunk_add/(?P[^/]+)/(?P[^/]+)/$', - 'chunk_add', name="catalogue_chunk_add"), - url(r'^chunk_edit/(?P[^/]+)/(?P[^/]+)/$', - 'chunk_edit', name="catalogue_chunk_edit"), - url(r'^book_append/(?P[^/]+)/$', - 'book_append', name="catalogue_book_append"), - url(r'^chunk_mass_edit', - 'chunk_mass_edit', name='catalogue_chunk_mass_edit'), - url(r'^image_mass_edit', - 'image_mass_edit', name='catalogue_image_mass_edit'), - - url(r'^track/(?P[^/]*)/$', PublishTrackFeed()), - url(r'^active/$', 'active_users_list', name='active_users_list'), - - url(r'^mark-final/$', 'mark_final', name='mark_final'), - url(r'^mark-final-completed/$', 'mark_final_completed', name='mark_final_completed'), -) diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py deleted file mode 100644 index f6208056..00000000 --- a/apps/catalogue/views.py +++ /dev/null @@ -1,672 +0,0 @@ -# -*- coding: utf-8 -*- -from collections import defaultdict -from datetime import datetime, date, timedelta -import logging -import os -from StringIO import StringIO -from urllib import unquote -from urlparse import urlsplit, urlunsplit - -from django.conf import settings -from django.contrib import auth -from django.contrib.auth.models import User -from django.contrib.auth.decorators import login_required, permission_required -from django.core.urlresolvers import reverse -from django.db.models import Count, Q -from django.db import transaction -from django import http -from django.http import Http404, HttpResponse, HttpResponseForbidden -from django.http.response import HttpResponseRedirect -from django.shortcuts import get_object_or_404, render -from django.utils.encoding import iri_to_uri -from django.utils.http import urlquote_plus -from django.utils.translation import ugettext_lazy as _ -from django.views.decorators.http import require_POST -from django_cas.decorators import user_passes_test - -from apiclient import NotAuthorizedError -from catalogue import forms -from catalogue import helpers -from catalogue.helpers import active_tab -from catalogue.models import (Book, Chunk, Image, BookPublishRecord, - ChunkPublishRecord, ImagePublishRecord, Project) -from fileupload.views import UploadView - -# -# Quick hack around caching problems, TODO: use ETags -# -from django.views.decorators.cache import never_cache - -logger = logging.getLogger("fnp.catalogue") - - -@active_tab('all') -@never_cache -def document_list(request): - return render(request, 'catalogue/document_list.html') - - -@active_tab('images') -@never_cache -def image_list(request, user=None): - return render(request, 'catalogue/image_list.html') - - -@never_cache -def user(request, username): - user = get_object_or_404(User, username=username) - return render(request, 'catalogue/user_page.html', {"viewed_user": user}) - - -@login_required -@active_tab('my') -@never_cache -def my(request): - last_books = sorted(request.session.get("wiki_last_books", {}).items(), - key=lambda x: x[1]['time'], reverse=True) - for k, v in last_books: - v['time'] = datetime.fromtimestamp(v['time']) - return render(request, 'catalogue/my_page.html', { - 'last_books': last_books, - "logout_to": '/', - }) - - -@active_tab('users') -def users(request): - return render(request, 'catalogue/user_list.html', { - 'users': User.objects.all().annotate(count=Count('chunk')).order_by( - '-count', 'last_name', 'first_name'), - }) - - -@active_tab('activity') -def activity(request, isodate=None): - today = date.today() - try: - day = helpers.parse_isodate(isodate) - except ValueError: - day = today - - if day > today: - raise Http404 - if day != today: - next_day = day + timedelta(1) - prev_day = day - timedelta(1) - - return render(request, 'catalogue/activity.html', locals()) - - -@never_cache -def logout_then_redirect(request): - auth.logout(request) - return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?=')) - - -@permission_required('catalogue.add_book') -@active_tab('create') -def create_missing(request, slug=None): - if slug is None: - slug = '' - slug = slug.replace(' ', '-') - - if request.method == "POST": - form = forms.DocumentCreateForm(request.POST, request.FILES) - if form.is_valid(): - - if request.user.is_authenticated(): - creator = request.user - else: - creator = None - book = Book.create( - text=form.cleaned_data['text'], - creator=creator, - slug=form.cleaned_data['slug'], - title=form.cleaned_data['title'], - gallery=form.cleaned_data['gallery'], - ) - - return http.HttpResponseRedirect(reverse("catalogue_book", args=[book.slug])) - else: - form = forms.DocumentCreateForm(initial={ - "slug": slug, - "title": slug.replace('-', ' ').title(), - "gallery": slug, - }) - - return render(request, "catalogue/document_create_missing.html", { - "slug": slug, - "form": form, - - "logout_to": '/', - }) - - -@permission_required('catalogue.add_book') -@active_tab('upload') -def upload(request): - if request.method == "POST": - form = forms.DocumentsUploadForm(request.POST, request.FILES) - if form.is_valid(): - from slugify import slugify - - if request.user.is_authenticated(): - creator = request.user - else: - creator = None - - zip = form.cleaned_data['zip'] - skipped_list = [] - ok_list = [] - error_list = [] - slugs = {} - existing = [book.slug for book in Book.objects.all()] - for filename in zip.namelist(): - if filename[-1] == '/': - continue - title = os.path.basename(filename)[:-4] - slug = slugify(title) - if not (slug and filename.endswith('.xml')): - skipped_list.append(filename) - elif slug in slugs: - error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug]))) - elif slug in existing: - error_list.append((filename, slug, _('Slug already used in repository.'))) - else: - try: - zip.read(filename).decode('utf-8') # test read - ok_list.append((filename, slug, title)) - except UnicodeDecodeError: - error_list.append((filename, title, _('File should be UTF-8 encoded.'))) - slugs[slug] = filename - - if not error_list: - for filename, slug, title in ok_list: - book = Book.create( - text=zip.read(filename).decode('utf-8'), - creator=creator, - slug=slug, - title=title, - ) - - return render(request, "catalogue/document_upload.html", { - "form": form, - "ok_list": ok_list, - "skipped_list": skipped_list, - "error_list": error_list, - - "logout_to": '/', - }) - else: - form = forms.DocumentsUploadForm() - - return render(request, "catalogue/document_upload.html", { - "form": form, - - "logout_to": '/', - }) - - -def serve_xml(request, book, slug): - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - xml = book.materialize(publishable=True) - response = http.HttpResponse(xml, content_type='application/xml') - response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug - return response - - -@never_cache -def book_xml(request, slug): - book = get_object_or_404(Book, slug=slug) - return serve_xml(request, book, slug) - - -@never_cache -def book_xml_dc(request, slug): - book = get_object_or_404(Book, dc_slug=slug) - return serve_xml(request, book, slug) - - -@never_cache -def book_txt(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - doc = book.wldocument() - text = doc.as_text().get_bytes() - response = http.HttpResponse(text, content_type='text/plain') - response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug - return response - - -@never_cache -def book_html(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - doc = book.wldocument(parse_dublincore=False) - html = doc.as_html(options={'gallery': "'%s'" % book.gallery_url()}) - - html = html.get_bytes() if html is not None else '' - # response = http.HttpResponse(html, content_type='text/html') - # return response - # book_themes = {} - # for fragment in book.fragments.all().iterator(): - # for theme in fragment.tags.filter(category='theme').iterator(): - # book_themes.setdefault(theme, []).append(fragment) - - # book_themes = book_themes.items() - # book_themes.sort(key=lambda s: s[0].sort_key) - return render(request, 'catalogue/book_text.html', locals()) - - -@never_cache -def book_pdf(request, slug, mobile=False): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - # TODO: move to celery - doc = book.wldocument() - # TODO: error handling - customizations = ['26pt', 'nothemes', 'nomargins', 'notoc'] if mobile else None - pdf_file = doc.as_pdf(cover=True, ilustr_path=book.gallery_path(), customizations=customizations) - from catalogue.ebook_utils import serve_file - return serve_file(pdf_file.get_filename(), - book.slug + '.pdf', 'application/pdf') - - -@never_cache -def book_epub(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - # TODO: move to celery - doc = book.wldocument() - # TODO: error handling - epub = doc.as_epub(ilustr_path=book.gallery_path()).get_bytes() - response = HttpResponse(content_type='application/epub+zip') - response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.epub' - response.write(epub) - return response - - -@never_cache -def book_mobi(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - # TODO: move to celery - doc = book.wldocument() - # TODO: error handling - mobi = doc.as_mobi(ilustr_path=book.gallery_path()).get_bytes() - response = HttpResponse(content_type='application/x-mobipocket-ebook') - response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.mobi' - response.write(mobi) - return response - - -@never_cache -def revision(request, slug, chunk=None): - try: - doc = Chunk.get(slug, chunk) - except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist): - raise Http404 - if not doc.book.accessible(request): - return HttpResponseForbidden("Not authorized.") - return http.HttpResponse(str(doc.revision())) - - -def book(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - if request.user.has_perm('catalogue.change_book'): - if request.method == "POST": - form = forms.BookForm(request.POST, instance=book) - if form.is_valid(): - form.save() - return http.HttpResponseRedirect(book.get_absolute_url()) - else: - form = forms.BookForm(instance=book) - publish_options_form = forms.PublishOptionsForm() - editable = True - else: - form = forms.ReadonlyBookForm(instance=book) - publish_options_form = forms.PublishOptionsForm() - editable = False - - publish_error = book.publishable_error() - publishable = publish_error is None - - return render(request, "catalogue/book_detail.html", { - "book": book, - "publishable": publishable, - "publishable_error": publish_error, - "form": form, - "publish_options_form": publish_options_form, - "editable": editable, - }) - - -def image(request, slug): - image = get_object_or_404(Image, slug=slug) - if not image.accessible(request): - return HttpResponseForbidden("Not authorized.") - - if request.user.has_perm('catalogue.change_image'): - if request.method == "POST": - form = forms.ImageForm(request.POST, instance=image) - if form.is_valid(): - form.save() - return http.HttpResponseRedirect(image.get_absolute_url()) - else: - form = forms.ImageForm(instance=image) - editable = True - else: - form = forms.ReadonlyImageForm(instance=image) - editable = False - - publish_error = image.publishable_error() - publishable = publish_error is None - - return render(request, "catalogue/image_detail.html", { - "object": image, - "publishable": publishable, - "publishable_error": publish_error, - "form": form, - "editable": editable, - }) - - -@permission_required('catalogue.add_chunk') -def chunk_add(request, slug, chunk): - try: - doc = Chunk.get(slug, chunk) - except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist): - raise Http404 - if not doc.book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - if request.method == "POST": - form = forms.ChunkAddForm(request.POST, instance=doc) - if form.is_valid(): - if request.user.is_authenticated(): - creator = request.user - else: - creator = None - doc.split(creator=creator, - slug=form.cleaned_data['slug'], - title=form.cleaned_data['title'], - gallery_start=form.cleaned_data['gallery_start'], - user=form.cleaned_data['user'], - stage=form.cleaned_data['stage'] - ) - - return http.HttpResponseRedirect(doc.book.get_absolute_url()) - else: - form = forms.ChunkAddForm(initial={ - "slug": str(doc.number + 1), - "title": "cz. %d" % (doc.number + 1, ), - }) - - return render(request, "catalogue/chunk_add.html", { - "chunk": doc, - "form": form, - }) - - -@login_required -def chunk_edit(request, slug, chunk): - try: - doc = Chunk.get(slug, chunk) - except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist): - raise Http404 - if not doc.book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - if request.method == "POST": - form = forms.ChunkForm(request.POST, instance=doc) - if form.is_valid(): - form.save() - go_next = request.GET.get('next', None) - if go_next: - go_next = urlquote_plus(unquote(iri_to_uri(go_next)), safe='/?=&') - else: - go_next = doc.book.get_absolute_url() - return http.HttpResponseRedirect(go_next) - else: - form = forms.ChunkForm(instance=doc) - - referer = request.META.get('HTTP_REFERER') - if referer: - parts = urlsplit(referer) - parts = ['', ''] + list(parts[2:]) - go_next = urlquote_plus(urlunsplit(parts)) - else: - go_next = '' - - return render(request, "catalogue/chunk_edit.html", { - "chunk": doc, - "form": form, - "go_next": go_next, - }) - - -@transaction.atomic -@login_required -@require_POST -def chunk_mass_edit(request): - ids = map(int, filter(lambda i: i.strip()!='', request.POST.get('ids').split(','))) - chunks = map(lambda i: Chunk.objects.get(id=i), ids) - - stage = request.POST.get('stage') - if stage: - try: - stage = Chunk.tag_model.objects.get(slug=stage) - except Chunk.DoesNotExist, e: - stage = None - - for c in chunks: c.stage = stage - - username = request.POST.get('user') - logger.info("username: %s" % username) - logger.info(request.POST) - if username: - try: - user = User.objects.get(username=username) - except User.DoesNotExist, e: - user = None - - for c in chunks: c.user = user - - project_id = request.POST.get('project') - if project_id: - try: - project = Project.objects.get(pk=int(project_id)) - except (Project.DoesNotExist, ValueError), e: - project = None - for c in chunks: - book = c.book - book.project = project - book.save() - - for c in chunks: c.save() - - return HttpResponse("", content_type="text/plain") - - -@transaction.atomic -@login_required -@require_POST -def image_mass_edit(request): - ids = map(int, filter(lambda i: i.strip()!='', request.POST.get('ids').split(','))) - images = map(lambda i: Image.objects.get(id=i), ids) - - stage = request.POST.get('stage') - if stage: - try: - stage = Image.tag_model.objects.get(slug=stage) - except Image.DoesNotExist, e: - stage = None - - for c in images: c.stage = stage - - username = request.POST.get('user') - logger.info("username: %s" % username) - logger.info(request.POST) - if username: - try: - user = User.objects.get(username=username) - except User.DoesNotExist, e: - user = None - - for c in images: c.user = user - - project_id = request.POST.get('project') - if project_id: - try: - project = Project.objects.get(pk=int(project_id)) - except (Project.DoesNotExist, ValueError), e: - project = None - for c in images: - c.project = project - - for c in images: c.save() - - return HttpResponse("", content_type="text/plain") - - -@permission_required('catalogue.change_book') -def book_append(request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - if request.method == "POST": - form = forms.BookAppendForm(book, request.POST) - if form.is_valid(): - append_to = form.cleaned_data['append_to'] - append_to.append(book) - return http.HttpResponseRedirect(append_to.get_absolute_url()) - else: - form = forms.BookAppendForm(book) - return render(request, "catalogue/book_append_to.html", { - "book": book, - "form": form, - - "logout_to": '/', - }) - - -@require_POST -@login_required -def publish(request, slug): - form = forms.PublishOptionsForm(request.POST) - if form.is_valid(): - days = form.cleaned_data['days'] - beta = form.cleaned_data['beta'] - else: - days = 0 - beta = False - book = get_object_or_404(Book, slug=slug) - if not book.accessible(request): - return HttpResponseForbidden("Not authorized.") - - try: - protocol = 'https://' if request.is_secure() else 'http://' - book.publish(request.user, host=protocol + request.get_host(), days=days, beta=beta) - except NotAuthorizedError: - return http.HttpResponseRedirect(reverse('apiclient_oauth' if not beta else 'apiclient_beta_oauth')) - except BaseException, e: - return http.HttpResponse(repr(e)) - else: - return http.HttpResponseRedirect(book.get_absolute_url()) - - -@require_POST -@login_required -def publish_image(request, slug): - image = get_object_or_404(Image, slug=slug) - if not image.accessible(request): - return HttpResponseForbidden("Not authorized.") - - try: - image.publish(request.user) - except NotAuthorizedError: - return http.HttpResponseRedirect(reverse('apiclient_oauth')) - except BaseException, e: - return http.HttpResponse(e) - else: - return http.HttpResponseRedirect(image.get_absolute_url()) - - -class GalleryView(UploadView): - def get_object(self, request, slug): - book = get_object_or_404(Book, slug=slug) - if not book.gallery: - raise Http404 - return book - - def breadcrumbs(self): - return [ - (_('books'), reverse('catalogue_document_list')), - (self.object.title, self.object.get_absolute_url()), - (_('scan gallery'),), - ] - - def get_directory(self): - return "%s%s/" % (settings.IMAGE_DIR, self.object.gallery) - - -def active_users_list(request): - since = date(date.today().year, 1, 1) - by_user = defaultdict(lambda: 0) - by_email = defaultdict(lambda: 0) - names_by_email = defaultdict(set) - for change_model in (Chunk.change_model, Image.change_model): - for c in change_model.objects.filter( - created_at__gte=since).order_by( - 'author', 'author_email', 'author_name').values( - 'author', 'author_name', 'author_email').annotate( - c=Count('author'), ce=Count('author_email')).distinct(): - if c['author']: - by_user[c['author']] += c['c'] - else: - by_email[c['author_email']] += c['ce'] - if c['author_name'].strip(): - names_by_email[c['author_email']].add(c['author_name']) - for user in User.objects.filter(pk__in=by_user): - by_email[user.email] += by_user[user.pk] - names_by_email[user.email].add("%s %s" % (user.first_name, user.last_name)) - - active_users = [] - for email, count in by_email.items(): - active_users.append((email, names_by_email[email], count)) - active_users.sort(key=lambda x: -x[2]) - return render(request, 'catalogue/active_users_list.html', { - 'users': active_users, - 'since': since, - }) - - -@user_passes_test(lambda u: u.is_superuser) -def mark_final(request): - if request.method == 'POST': - form = forms.MarkFinalForm(data=request.POST) - if form.is_valid(): - form.save() - return HttpResponseRedirect(reverse('mark_final_completed')) - else: - form = forms.MarkFinalForm() - return render(request, 'catalogue/mark_final.html', {'form': form}) - - -def mark_final_completed(request): - return render(request, 'catalogue/mark_final_completed.html') diff --git a/apps/catalogue/xml_tools.py b/apps/catalogue/xml_tools.py deleted file mode 100644 index 242714b6..00000000 --- a/apps/catalogue/xml_tools.py +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: utf-8 -*- -from copy import deepcopy -import re - -from lxml import etree -from catalogue.constants import TRIM_BEGIN, TRIM_END, MASTERS - -RE_TRIM_BEGIN = re.compile("^$" % TRIM_BEGIN, re.M) -RE_TRIM_END = re.compile("^$" % TRIM_END, re.M) - - -class ParseError(BaseException): - pass - - -def _trim(text, trim_begin=True, trim_end=True): - """ - Cut off everything before RE_TRIM_BEGIN and after RE_TRIM_END, so - that eg. one big XML file can be compiled from many small XML files. - """ - if trim_begin: - text = RE_TRIM_BEGIN.split(text, maxsplit=1)[-1] - if trim_end: - text = RE_TRIM_END.split(text, maxsplit=1)[0] - return text - - -def compile_text(parts): - """ - Compiles full text from an iterable of parts, - trimming where applicable. - """ - texts = [] - trim_begin = False - text = '' - for next_text in parts: - if not next_text: - continue - if text: - # trim the end, because there's more non-empty text - # don't trim beginning, if `text' is the first non-empty part - texts.append(_trim(text, trim_begin=trim_begin)) - trim_begin = True - text = next_text - # don't trim the end, because there's no more text coming after `text' - # only trim beginning if it's not still the first non-empty - texts.append(_trim(text, trim_begin=trim_begin, trim_end=False)) - return "".join(texts) - - -def add_trim_begin(text): - trim_tag = etree.Comment(TRIM_BEGIN) - e = etree.fromstring(text) - for master in e[::-1]: - if master.tag in MASTERS: - break - if master.tag not in MASTERS: - raise ParseError('No master tag found!') - - master.insert(0, trim_tag) - trim_tag.tail = '\n\n\n' + (master.text or '') - master.text = '\n' - return unicode(etree.tostring(e, encoding="utf-8"), 'utf-8') - - -def add_trim_end(text): - trim_tag = etree.Comment(TRIM_END) - e = etree.fromstring(text) - for master in e[::-1]: - if master.tag in MASTERS: - break - if master.tag not in MASTERS: - raise ParseError('No master tag found!') - - master.append(trim_tag) - trim_tag.tail = '\n' - prev = trim_tag.getprevious() - if prev is not None: - prev.tail = (prev.tail or '') + '\n\n\n' - else: - master.text = (master.text or '') + '\n\n\n' - return unicode(etree.tostring(e, encoding="utf-8"), 'utf-8') - - -def split_xml(text): - """Splits text into chapters. - - All this stuff really must go somewhere else. - - """ - src = etree.fromstring(text) - chunks = [] - - splitter = u'naglowek_rozdzial' - parts = src.findall('.//naglowek_rozdzial') - while parts: - # copy the document - copied = deepcopy(src) - - element = parts[-1] - - # find the chapter's title - name_elem = deepcopy(element) - for tag in 'extra', 'motyw', 'pa', 'pe', 'pr', 'pt', 'uwaga': - for a in name_elem.findall('.//' + tag): - a.text='' - del a[:] - name = etree.tostring(name_elem, method='text', encoding='utf-8').strip() - - # in the original, remove everything from the start of the last chapter - parent = element.getparent() - del parent[parent.index(element):] - element, parent = parent, parent.getparent() - while parent is not None: - del parent[parent.index(element) + 1:] - element, parent = parent, parent.getparent() - - # in the copy, remove everything before the last chapter - element = copied.findall('.//naglowek_rozdzial')[-1] - parent = element.getparent() - while parent is not None: - parent.text = None - while parent[0] is not element: - del parent[0] - element, parent = parent, parent.getparent() - chunks[:0] = [[name, - unicode(etree.tostring(copied, encoding='utf-8'), 'utf-8') - ]] - - parts = src.findall('.//naglowek_rozdzial') - - chunks[:0] = [[u'początek', - unicode(etree.tostring(src, encoding='utf-8'), 'utf-8') - ]] - - for ch in chunks[1:]: - ch[1] = add_trim_begin(ch[1]) - for ch in chunks[:-1]: - ch[1] = add_trim_end(ch[1]) - - return chunks diff --git a/apps/cover/__init__.py b/apps/cover/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/cover/forms.py b/apps/cover/forms.py deleted file mode 100755 index 513bdefb..00000000 --- a/apps/cover/forms.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from StringIO import StringIO - -from django import forms -from django.conf import settings -from django.utils.translation import ugettext_lazy as _, ugettext -from cover.models import Image -from django.utils.text import mark_safe -from PIL import Image as PILImage - -from cover.utils import get_flickr_data, FlickrError, URLOpener - - -class ImageAddForm(forms.ModelForm): - class Meta: - model = Image - - def __init__(self, *args, **kwargs): - super(ImageAddForm, self).__init__(*args, **kwargs) - self.fields['file'].required = False - - def clean_download_url(self): - cl = self.cleaned_data['download_url'] or None - if cl is not None: - try: - img = Image.objects.get(download_url=cl) - except Image.DoesNotExist: - pass - else: - raise forms.ValidationError(mark_safe( - ugettext('Image already in repository.') - % {'url': img.get_absolute_url()})) - return cl - - def clean_source_url(self): - source_url = self.cleaned_data['source_url'] or None - if source_url is not None: - same_source = Image.objects.filter(source_url=source_url) - if same_source: - raise forms.ValidationError(mark_safe( - ugettext('Image already in repository' - % same_source.first().get_absolute_url()))) - return source_url - - def clean(self): - cleaned_data = super(ImageAddForm, self).clean() - download_url = cleaned_data.get('download_url', None) - uploaded_file = cleaned_data.get('file', None) - if not download_url and not uploaded_file: - raise forms.ValidationError(ugettext('No image specified')) - if download_url: - image_data = URLOpener().open(download_url).read() - width, height = PILImage.open(StringIO(image_data)).size - else: - width, height = PILImage.open(uploaded_file.file).size - min_width, min_height = settings.MIN_COVER_SIZE - if width < min_width or height < min_height: - raise forms.ValidationError(ugettext('Image too small: %sx%s, minimal dimensions %sx%s') % - (width, height, min_width, min_height)) - return cleaned_data - - -class ImageEditForm(forms.ModelForm): - """Form used for editing a Book.""" - class Meta: - model = Image - exclude = ['download_url'] - - def clean(self): - cleaned_data = super(ImageEditForm, self).clean() - uploaded_file = cleaned_data.get('file', None) - width, height = PILImage.open(uploaded_file.file).size - min_width, min_height = settings.MIN_COVER_SIZE - if width < min_width or height < min_height: - raise forms.ValidationError(ugettext('Image too small: %sx%s, minimal dimensions %sx%s') % - (width, height, min_width, min_height)) - - -class ReadonlyImageEditForm(ImageEditForm): - """Form used for not editing an Image.""" - - def __init__(self, *args, **kwargs): - super(ReadonlyImageEditForm, self).__init__(*args, **kwargs) - for field in self.fields.values(): - field.widget.attrs.update({"readonly": True}) - - def save(self, *args, **kwargs): - raise AssertionError("ReadonlyImageEditForm should not be saved.") - - -class FlickrForm(forms.Form): - source_url = forms.URLField(label=_('Flickr URL')) - - def clean_source_url(self): - url = self.cleaned_data['source_url'] - try: - flickr_data = get_flickr_data(url) - except FlickrError as e: - raise forms.ValidationError(e) - for field_name in ('license_url', 'license_name', 'author', 'title', 'download_url'): - self.cleaned_data[field_name] = flickr_data[field_name] - return flickr_data['source_url'] diff --git a/apps/cover/locale/pl/LC_MESSAGES/django.mo b/apps/cover/locale/pl/LC_MESSAGES/django.mo deleted file mode 100644 index 1c297fe0..00000000 Binary files a/apps/cover/locale/pl/LC_MESSAGES/django.mo and /dev/null differ diff --git a/apps/cover/locale/pl/LC_MESSAGES/django.po b/apps/cover/locale/pl/LC_MESSAGES/django.po deleted file mode 100644 index 67547cec..00000000 --- a/apps/cover/locale/pl/LC_MESSAGES/django.po +++ /dev/null @@ -1,100 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-02-25 13:29+0100\n" -"PO-Revision-Date: 2014-02-25 13:30+0100\n" -"Last-Translator: Radek Czajka \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 1.5.4\n" - -#: forms.py:29 -#, python-format -msgid "Image already in repository." -msgstr "Obraz jest już w repozytorium." - -#: forms.py:60 -msgid "Flickr URL" -msgstr "URL z Flickra" - -#: models.py:19 -msgid "title" -msgstr "tytuł" - -#: models.py:20 -msgid "author" -msgstr "autor" - -#: models.py:21 -msgid "license name" -msgstr "nazwa licencji" - -#: models.py:22 -msgid "license URL" -msgstr "URL licencji" - -#: models.py:23 -msgid "source URL" -msgstr "URL źródła" - -#: models.py:24 -msgid "image download URL" -msgstr "URL pliku do pobrania" - -#: models.py:25 -msgid "file" -msgstr "plik" - -#: models.py:28 -msgid "cover image" -msgstr "obrazek na okładkę" - -#: models.py:29 -msgid "cover images" -msgstr "obrazki na okładki" - -#: templates/cover/add_image.html:33 templates/cover/add_image.html.py:62 -msgid "Add image" -msgstr "Dodaj obrazek" - -#: templates/cover/add_image.html:40 -msgid "Load from Flickr" -msgstr "Pobierz z Flickra" - -#: templates/cover/image_detail.html:7 -msgid "Cover image" -msgstr "Obrazek na okładkę" - -#: templates/cover/image_detail.html:23 -msgid "source" -msgstr "źródło" - -#: templates/cover/image_detail.html:35 -msgid "Change" -msgstr "Zmień" - -#: templates/cover/image_detail.html:41 -msgid "Used in:" -msgstr "Użyte w:" - -#: templates/cover/image_detail.html:49 -msgid "None" -msgstr "Brak" - -#: templates/cover/image_list.html:7 -msgid "Cover images" -msgstr "Obrazki na okładki" - -#: templates/cover/image_list.html:10 -msgid "Add new" -msgstr "Dodaj nowy" diff --git a/apps/cover/management/__init__.py b/apps/cover/management/__init__.py deleted file mode 100644 index d3841244..00000000 --- a/apps/cover/management/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# diff --git a/apps/cover/management/commands/__init__.py b/apps/cover/management/commands/__init__.py deleted file mode 100644 index d3841244..00000000 --- a/apps/cover/management/commands/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# diff --git a/apps/cover/management/commands/refresh_covers.py b/apps/cover/management/commands/refresh_covers.py deleted file mode 100644 index cc0ef31c..00000000 --- a/apps/cover/management/commands/refresh_covers.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -import urllib2 as urllib -from optparse import make_option - -from django.core.files.base import ContentFile -from django.core.management import BaseCommand - -from cover.models import Image -from cover.utils import get_flickr_data, URLOpener, FlickrError - - -class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('--from', dest='from_id', type=int, default=1), - ) - - def handle(self, *args, **options): - from_id = options.get('from_id', 1) - images = Image.objects.filter(id__gte=from_id).exclude(book=None).order_by('id') - images = images.filter(source_url__contains='flickr.com').exclude(download_url__endswith='_o.jpg') - for image in images: - print image.id - try: - flickr_data = get_flickr_data(image.source_url) - print flickr_data - except FlickrError as e: - print 'Flickr analysis failed: %s' % e - else: - flickr_url = flickr_data['download_url'] - if flickr_url != image.download_url: - same_url = Image.objects.filter(download_url=flickr_url) - if same_url: - print 'Download url already present in image %s' % same_url.get().id - continue - try: - t = URLOpener().open(flickr_url).read() - except urllib.URLError: - print 'Broken download url' - except IOError: - print 'Connection failed' - else: - image.download_url = flickr_url - image.file.save(image.file.name, ContentFile(t)) - image.save() diff --git a/apps/cover/migrations/0001_initial.py b/apps/cover/migrations/0001_initial.py deleted file mode 100644 index f31c405f..00000000 --- a/apps/cover/migrations/0001_initial.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: 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 'Image' - db.create_table('cover_image', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('title', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('author', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('license_name', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('license_url', self.gf('django.db.models.fields.URLField')(max_length=255, blank=True)), - ('source_url', self.gf('django.db.models.fields.URLField')(max_length=200)), - ('download_url', self.gf('django.db.models.fields.URLField')(unique=True, max_length=200)), - ('file', self.gf('django.db.models.fields.files.ImageField')(max_length=100)), - )) - db.send_create_signal('cover', ['Image']) - - - def backwards(self, orm): - # Deleting model 'Image' - db.delete_table('cover_image') - - - models = { - 'cover.image': { - 'Meta': {'object_name': 'Image'}, - 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'download_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}), - 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), - 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) - } - } - - complete_apps = ['cover'] \ No newline at end of file diff --git a/apps/cover/migrations/0002_auto__chg_field_image_download_url.py b/apps/cover/migrations/0002_auto__chg_field_image_download_url.py deleted file mode 100644 index 8a64c39d..00000000 --- a/apps/cover/migrations/0002_auto__chg_field_image_download_url.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: 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): - - # Changing field 'Image.download_url' - db.alter_column(u'cover_image', 'download_url', self.gf('django.db.models.fields.URLField')(max_length=200, unique=True, null=True)) - - def backwards(self, orm): - - # User chose to not deal with backwards NULL issues for 'Image.download_url' - raise RuntimeError("Cannot reverse this migration. 'Image.download_url' and its values cannot be restored.") - - models = { - u'cover.image': { - 'Meta': {'object_name': 'Image'}, - 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True'}), - 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), - 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) - } - } - - complete_apps = ['cover'] \ No newline at end of file diff --git a/apps/cover/migrations/0003_auto__chg_field_image_source_url.py b/apps/cover/migrations/0003_auto__chg_field_image_source_url.py deleted file mode 100644 index 98951e35..00000000 --- a/apps/cover/migrations/0003_auto__chg_field_image_source_url.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: 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): - - # Changing field 'Image.source_url' - db.alter_column(u'cover_image', 'source_url', self.gf('django.db.models.fields.URLField')(max_length=200, null=True)) - - def backwards(self, orm): - - # User chose to not deal with backwards NULL issues for 'Image.source_url' - raise RuntimeError("Cannot reverse this migration. 'Image.source_url' and its values cannot be restored.") - - models = { - u'cover.image': { - 'Meta': {'object_name': 'Image'}, - 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True'}), - 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), - 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) - } - } - - complete_apps = ['cover'] \ No newline at end of file diff --git a/apps/cover/migrations/__init__.py b/apps/cover/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/cover/models.py b/apps/cover/models.py deleted file mode 100644 index d83dad39..00000000 --- a/apps/cover/models.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.core.files.base import ContentFile -from django.core.files.storage import FileSystemStorage -from django.db import models -from django.db.models.signals import post_save -from django.dispatch import receiver -from django.utils.translation import ugettext_lazy as _ -from django.contrib.sites.models import Site -from cover.utils import URLOpener - - -class OverwriteStorage(FileSystemStorage): - - def get_available_name(self, name, max_length=None): - self.delete(name) - return name - - -class Image(models.Model): - title = models.CharField(max_length=255, verbose_name=_('title')) - author = models.CharField(max_length=255, verbose_name=_('author')) - license_name = models.CharField(max_length=255, verbose_name=_('license name')) - license_url = models.URLField(max_length=255, blank=True, verbose_name=_('license URL')) - source_url = models.URLField(verbose_name=_('source URL'), null=True, blank=True) - download_url = models.URLField(unique=True, verbose_name=_('image download URL'), null=True, blank=True) - file = models.ImageField( - upload_to='cover/image', storage=OverwriteStorage(), editable=True, verbose_name=_('file')) - - class Meta: - verbose_name = _('cover image') - verbose_name_plural = _('cover images') - - def __unicode__(self): - return u"%s - %s" % (self.author, self.title) - - @models.permalink - def get_absolute_url(self): - return 'cover_image', [self.id] - - def get_full_url(self): - return "http://%s%s" % (Site.objects.get_current().domain, self.get_absolute_url()) - - -@receiver(post_save, sender=Image) -def download_image(sender, instance, **kwargs): - if instance.pk and not instance.file: - t = URLOpener().open(instance.download_url).read() - instance.file.save("%d.jpg" % instance.pk, ContentFile(t)) diff --git a/apps/cover/templates/cover/add_image.html b/apps/cover/templates/cover/add_image.html deleted file mode 100755 index 293c1001..00000000 --- a/apps/cover/templates/cover/add_image.html +++ /dev/null @@ -1,69 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} - -{% block titleextra %}{% trans "Add image" %}{% endblock %} - -{% block add_js %} - {{block.super}} - -{% endblock %} - -{% block content %} -

{% trans "Add image" %}

- - -
{% csrf_token %} - - - {{ ff.as_table }} - -
-
- -
{% csrf_token %} -{{ form.non_field_errors }} - - {% for field in form %} - {% if field.name != 'download_url' and field.name != 'file' %} - - - - - {% endif %} - {% endfor %} - - - - - - - - -
{{field.errors}} {{field.label}}{{field}}
{{ form.download_url.errors }} {{form.download_url.label}}{{form.download_url}}{{ form.file.errors }} Lub {{form.file.label}}{{form.file}}
-
- - -{% endblock %} diff --git a/apps/cover/templates/cover/image_detail.html b/apps/cover/templates/cover/image_detail.html deleted file mode 100755 index db9b176f..00000000 --- a/apps/cover/templates/cover/image_detail.html +++ /dev/null @@ -1,59 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} -{% load thumbnail %} -{% load build_absolute_uri from fnp_common %} - -{% block titleextra %}{% trans "Cover image" %}{% endblock %} - -{% block content %} -

{% trans "Cover image" %}

- -
- - - -
{{ object.title }} by {{ object.author }}, - {% if object.license_url %}{% endif %} - {{ object.license_name }} - {% if object.license_url %}{% endif %} - -
{% trans "source" %}: {{ object.download_url }} -
- - -{% if editable %} -
- {% csrf_token %} -{% endif %} - - {{ form.as_table }} - {% if editable %} - - {% endif %} -
-{% if editable %}
{% endif %} - - -

{% trans "Used in:" %}

-{% if object.book_set %} -
    - {% for book in object.book_set.all %} -
  • {{ book }}
  • - {% endfor %} -
-{% else %} -

{% trans "None" %}

-{% endif %} - - - -{% endblock %} diff --git a/apps/cover/templates/cover/image_list.html b/apps/cover/templates/cover/image_list.html deleted file mode 100755 index 50443edd..00000000 --- a/apps/cover/templates/cover/image_list.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} -{% load thumbnail pagination_tags %} - -{% block titleextra %}{% trans "Cover images" %}{% endblock %} - -{% block content %} -

{% trans "Cover images" %}

- -{% if can_add %} - {% trans "Add new" %} -{% endif %} - -
    -{% autopaginate object_list 100 %} -{% for image in object_list %} - - - -
    - {{ image }}
    -{% endfor %} -{% paginate %} -
- -{% endblock %} diff --git a/apps/cover/tests.py b/apps/cover/tests.py deleted file mode 100644 index be8d0033..00000000 --- a/apps/cover/tests.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from nose.tools import * -from django.test import TestCase -from cover.forms import FlickrForm - - -class FlickrTests(TestCase): - def test_flickr(self): - form = FlickrForm({"source_url": "https://www.flickr.com/photos/rczajka/6941928577/in/photostream"}) - self.assertTrue(form.is_valid()) - self.assertEqual(form.cleaned_data['source_url'], "https://www.flickr.com/photos/rczajka/6941928577/") - self.assertEqual(form.cleaned_data['author'], "Radek Czajka@Flickr") - self.assertEqual(form.cleaned_data['title'], u"Pirate Stańczyk") - self.assertEqual(form.cleaned_data['license_name'], "CC BY 2.0") - self.assertEqual(form.cleaned_data['license_url'], "https://creativecommons.org/licenses/by/2.0/") - self.assertTrue('.staticflickr.com' in form.cleaned_data['download_url']) diff --git a/apps/cover/urls.py b/apps/cover/urls.py deleted file mode 100644 index 1146f62d..00000000 --- a/apps/cover/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.conf.urls import patterns, url - - -urlpatterns = patterns('cover.views', - url(r'^preview/$', 'preview_from_xml', name='cover_preview'), - url(r'^preview/(?P[^/]+)/$', 'preview', name='cover_preview'), - url(r'^preview/(?P[^/]+)/(?P[^/]+)/$', - 'preview', name='cover_preview'), - url(r'^preview/(?P[^/]+)/(?P[^/]+)/(?P\d+)/$', - 'preview', name='cover_preview'), - - url(r'^image/$', 'image_list', name='cover_image_list'), - url(r'^image/(?P\d+)/?$', 'image', name='cover_image'), - url(r'^image/(?P\d+)/file/', 'image_file', name='cover_file'), - url(r'^add_image/$', 'add_image', name='cover_add_image'), -) diff --git a/apps/cover/utils.py b/apps/cover/utils.py deleted file mode 100755 index 51aee190..00000000 --- a/apps/cover/utils.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -import json -import re -from urllib import FancyURLopener - -from django.contrib.sites.models import Site - - -class URLOpener(FancyURLopener): - @property - def version(self): - return 'FNP Redakcja (http://%s)' % Site.objects.get_current() - - -class FlickrError(Exception): - pass - - -def get_flickr_data(url): - m = re.match(r'(https?://)?(www\.|secure\.)?flickr\.com/photos/(?P[^/]+)/(?P\d+)/?', url) - if not m: - raise FlickrError("It doesn't look like Flickr URL.") - author_slug, img_id = m.group('author'), m.group('img') - base_url = "https://www.flickr.com/photos/%s/%s/" % (author_slug, img_id) - try: - html = URLOpener().open(url).read().decode('utf-8') - except IOError: - raise FlickrError('Error reading page') - match = re.search(r']* rel="license ', html) - if not match: - raise FlickrError('License not found.') - else: - license_url = match.group(1) - re_license = re.compile(r'https?://creativecommons.org/licenses/([^/]*)/([^/]*)/.*') - m = re_license.match(license_url) - if not m: - re_pd = re.compile(r'https?://creativecommons.org/publicdomain/([^/]*)/([^/]*)/.*') - m = re_pd.match(license_url) - if not m: - raise FlickrError('License does not look like CC: %s' % license_url) - if m.group(1).lower() == 'zero': - license_name = 'Public domain (CC0 %s)' % m.group(2) - else: - license_name = 'Public domain' - else: - license_name = 'CC %s %s' % (m.group(1).upper(), m.group(2)) - m = re.search(r']* class="owner-name [^>]*>([^<]*)<', html) - if m: - author = "%s@Flickr" % m.group(1) - else: - raise FlickrError('Error reading author name.') - m = re.search(r']*>(.*?)', html, re.S) - if not m: - raise FlickrError('Error reading image title.') - title = m.group(1).strip() - m = re.search(r'modelExport: (\{.*\})', html) - try: - assert m - download_url = 'https:' + json.loads(m.group(1))['main']['photo-models'][0]['sizes']['o']['url'] - except (AssertionError, ValueError, IndexError, KeyError): - raise FlickrError('Error reading image URL.') - return { - 'source_url': base_url, - 'license_url': license_url, - 'license_name': license_name, - 'author': author, - 'title': title, - 'download_url': download_url, - } diff --git a/apps/cover/views.py b/apps/cover/views.py deleted file mode 100644 index 3f2c46fa..00000000 --- a/apps/cover/views.py +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -import os.path -from django.conf import settings -from django.contrib.auth.decorators import permission_required -from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.shortcuts import get_object_or_404, render -from django.views.decorators.csrf import csrf_exempt -from django.views.decorators.http import require_POST -from catalogue.helpers import active_tab -from catalogue.models import Chunk -from cover.models import Image -from cover import forms - -PREVIEW_SIZE = (216, 300) - - -def preview(request, book, chunk=None, rev=None): - """Creates a cover image. - - If chunk and rev number are given, use version from given revision. - If rev is not given, use publishable version. - """ - from PIL import Image - from librarian.cover import make_cover - from librarian.dcparser import BookInfo - - chunk = Chunk.get(book, chunk) - if rev is not None: - try: - revision = chunk.at_revision(rev) - except Chunk.change_model.DoesNotExist: - raise Http404 - else: - revision = chunk.publishable() - if revision is None: - raise Http404 - xml = revision.materialize().encode('utf-8') - - try: - info = BookInfo.from_bytes(xml) - except: - return HttpResponseRedirect(os.path.join(settings.STATIC_URL, "img/sample_cover.png")) - cover = make_cover(info) - response = HttpResponse(content_type=cover.mime_type()) - img = cover.image().resize(PREVIEW_SIZE, Image.ANTIALIAS) - img.save(response, cover.format) - return response - - -@csrf_exempt -@require_POST -def preview_from_xml(request): - from hashlib import sha1 - from PIL import Image - from os import makedirs - from lxml import etree - from librarian.cover import make_cover - from librarian.dcparser import BookInfo - - xml = request.POST['xml'] - try: - info = BookInfo.from_bytes(xml.encode('utf-8')) - except: - return HttpResponse(os.path.join(settings.STATIC_URL, "img/sample_cover.png")) - coverid = sha1(etree.tostring(info.to_etree())).hexdigest() - cover = make_cover(info) - - cover_dir = 'cover/preview' - try: - makedirs(os.path.join(settings.MEDIA_ROOT, cover_dir)) - except OSError: - pass - fname = os.path.join(cover_dir, "%s.%s" % (coverid, cover.ext())) - img = cover.image().resize(PREVIEW_SIZE, Image.ANTIALIAS) - img.save(os.path.join(settings.MEDIA_ROOT, fname)) - return HttpResponse(os.path.join(settings.MEDIA_URL, fname)) - - -@active_tab('cover') -def image(request, pk): - img = get_object_or_404(Image, pk=pk) - - if request.user.has_perm('cover.change_image'): - if request.method == "POST": - form = forms.ImageEditForm(request.POST, request.FILES, instance=img) - if form.is_valid(): - form.save() - return HttpResponseRedirect(img.get_absolute_url()) - else: - form = forms.ImageEditForm(instance=img) - editable = True - else: - form = forms.ReadonlyImageEditForm(instance=img) - editable = False - - return render(request, "cover/image_detail.html", { - "object": Image.objects.get(id=img.id), - "form": form, - "editable": editable, - }) - - -def image_file(request, pk): - img = get_object_or_404(Image, pk=pk) - return HttpResponseRedirect(img.file.url) - - -@active_tab('cover') -def image_list(request): - return render(request, "cover/image_list.html", { - 'object_list': Image.objects.all(), - 'can_add': request.user.has_perm('cover.add_image'), - }) - - -@permission_required('cover.add_image') -@active_tab('cover') -def add_image(request): - form = ff = None - if request.method == 'POST': - if request.POST.get('form_id') == 'flickr': - ff = forms.FlickrForm(request.POST) - if ff.is_valid(): - form = forms.ImageAddForm(ff.cleaned_data) - else: - form = forms.ImageAddForm(request.POST, request.FILES) - if form.is_valid(): - obj = form.save() - return HttpResponseRedirect(obj.get_absolute_url()) - if form is None: - form = forms.ImageAddForm() - if ff is None: - ff = forms.FlickrForm() - return render(request, 'cover/add_image.html', { - 'form': form, - 'ff': ff, - }) diff --git a/apps/dvcs/__init__.py b/apps/dvcs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/dvcs/locale/pl/LC_MESSAGES/django.mo b/apps/dvcs/locale/pl/LC_MESSAGES/django.mo deleted file mode 100644 index dfd85c25..00000000 Binary files a/apps/dvcs/locale/pl/LC_MESSAGES/django.mo and /dev/null differ diff --git a/apps/dvcs/locale/pl/LC_MESSAGES/django.po b/apps/dvcs/locale/pl/LC_MESSAGES/django.po deleted file mode 100644 index c0365d56..00000000 --- a/apps/dvcs/locale/pl/LC_MESSAGES/django.po +++ /dev/null @@ -1,124 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-12-14 15:25+0100\n" -"PO-Revision-Date: 2011-12-14 15:27+0100\n" -"Last-Translator: Radek Czajka \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" - -#: models.py:19 -msgid "name" -msgstr "nazwa" - -#: models.py:20 -msgid "slug" -msgstr "slug" - -#: models.py:22 -msgid "ordering" -msgstr "kolejność" - -#: models.py:70 -msgid "author" -msgstr "autor" - -#: models.py:71 -msgid "author name" -msgstr "imię i nazwisko autora" - -#: models.py:73 -#: models.py:77 -msgid "Used if author is not set." -msgstr "Używane, gdy nie jest ustawiony autor." - -#: models.py:75 -msgid "author email" -msgstr "e-mail autora" - -#: models.py:79 -msgid "revision" -msgstr "rewizja" - -#: models.py:83 -msgid "parent" -msgstr "rodzic" - -#: models.py:88 -msgid "merge parent" -msgstr "drugi rodzic" - -#: models.py:91 -msgid "description" -msgstr "opis" - -#: models.py:94 -msgid "publishable" -msgstr "do publikacji" - -#: models.py:176 -msgid "tag" -msgstr "tag" - -#: models.py:176 -#: models.py:178 -#: models.py:194 -#: models.py:196 -msgid "for:" -msgstr "dla:" - -#: models.py:178 -#: models.py:202 -msgid "tags" -msgstr "tagi" - -#: models.py:194 -msgid "change" -msgstr "zmiana" - -#: models.py:196 -msgid "changes" -msgstr "zmiany" - -#: models.py:201 -msgid "document" -msgstr "dokument" - -#: models.py:203 -msgid "data" -msgstr "dane" - -#: models.py:217 -msgid "stage" -msgstr "etap" - -#: models.py:225 -msgid "head" -msgstr "głowica" - -#: models.py:226 -msgid "This document's current head." -msgstr "Aktualna wersja dokumentu." - -#: models.py:230 -msgid "creator" -msgstr "utworzył" - -#: models.py:245 -msgid "user" -msgstr "użytkownik" - -#: models.py:245 -msgid "Work assignment." -msgstr "Przypisanie pracy użytkownikowi." - diff --git a/apps/dvcs/models.py b/apps/dvcs/models.py deleted file mode 100644 index 24bdeb3a..00000000 --- a/apps/dvcs/models.py +++ /dev/null @@ -1,332 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from datetime import datetime -import os.path - -from django.contrib.auth.models import User -from django.core.files.base import ContentFile -from django.db import models, transaction -from django.db.models.base import ModelBase -from django.utils.translation import string_concat, ugettext_lazy as _ -from mercurial import simplemerge - -from django.conf import settings -from dvcs.signals import post_commit, post_publishable -from dvcs.storage import GzipFileSystemStorage - - -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: - abstract = True - 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 = {} - - def get_next(self): - """ - Returns the next tag - stage to work on. - Returns None for the last stage. - """ - try: - return type(self).objects.filter(ordering__gt=self.ordering)[0] - except IndexError: - return None - -models.signals.pre_save.connect(Tag.listener_changed, sender=Tag) - - -def data_upload_to(instance, filename): - return "%d/%d" % (instance.tree.pk, instance.pk) - - -class Change(models.Model): - """ - Single document change related to previous change. The "parent" - argument points to the version against which this change has been - recorded. Initial text will have a null parent. - - Data file contains a gzipped text of the document. - """ - author = models.ForeignKey(User, null=True, blank=True, verbose_name=_('author')) - author_name = models.CharField( - _('author name'), max_length=128, null=True, blank=True, help_text=_("Used if author is not set.")) - author_email = models.CharField( - _('author email'), max_length=128, null=True, blank=True, help_text=_("Used if author is not set.")) - revision = models.IntegerField(_('revision'), db_index=True) - - parent = models.ForeignKey( - 'self', null=True, blank=True, default=None, verbose_name=_('parent'), related_name="children") - - merge_parent = models.ForeignKey( - 'self', null=True, blank=True, default=None, verbose_name=_('merge parent'), related_name="merge_children") - - description = models.TextField(_('description'), blank=True, default='') - created_at = models.DateTimeField(editable=False, db_index=True, default=datetime.now) - publishable = models.BooleanField(_('publishable'), default=False) - - class Meta: - abstract = True - ordering = ('created_at',) - unique_together = ['tree', 'revision'] - - def __unicode__(self): - return u"Id: %r, Tree %r, Parent %r, Data: %s" % (self.id, self.tree_id, self.parent_id, self.data) - - def author_str(self): - if self.author: - return "%s %s <%s>" % ( - self.author.first_name, - self.author.last_name, - self.author.email) - else: - return "%s <%s>" % ( - self.author_name, - self.author_email - ) - - def save(self, *args, **kwargs): - """ - take the next available revision number if none yet - """ - if self.revision is None: - tree_rev = self.tree.revision() - if tree_rev is None: - self.revision = 1 - else: - self.revision = tree_rev + 1 - return super(Change, self).save(*args, **kwargs) - - def materialize(self): - f = self.data.storage.open(self.data) - text = f.read() - f.close() - return unicode(text, 'utf-8') - - def merge_with(self, other, author=None, - author_name=None, author_email=None, - description=u"Automatic merge."): - """Performs an automatic merge after straying commits.""" - assert self.tree_id == other.tree_id # same tree - if other.parent_id == self.pk: - # immediate child - fast forward - return other - - local = self.materialize().encode('utf-8') - base = other.parent.materialize().encode('utf-8') - remote = other.materialize().encode('utf-8') - - merge = simplemerge.Merge3Text(base, local, remote) - result = ''.join(merge.merge_lines()) - merge_node = self.children.create( - merge_parent=other, tree=self.tree, - author=author, - author_name=author_name, - author_email=author_email, - description=description) - merge_node.data.save('', ContentFile(result)) - return merge_node - - def revert(self, **kwargs): - """ commit this version of a doc as new head """ - self.tree.commit(text=self.materialize(), **kwargs) - - def set_publishable(self, publishable): - self.publishable = publishable - self.save() - post_publishable.send(sender=self, publishable=publishable) - - -def create_tag_model(model): - name = model.__name__ + 'Tag' - - class Meta(Tag.Meta): - app_label = model._meta.app_label - verbose_name = string_concat( - _("tag"), " ", _("for:"), " ", model._meta.verbose_name) - verbose_name_plural = string_concat( - _("tags"), " ", _("for:"), " ", model._meta.verbose_name) - - attrs = { - '__module__': model.__module__, - 'Meta': Meta, - } - return type(name, (Tag,), attrs) - - -def create_change_model(model): - name = model.__name__ + 'Change' - repo = GzipFileSystemStorage(location=model.REPO_PATH) - - class Meta(Change.Meta): - app_label = model._meta.app_label - verbose_name = string_concat( - _("change"), " ", _("for:"), " ", model._meta.verbose_name) - verbose_name_plural = string_concat( - _("changes"), " ", _("for:"), " ", model._meta.verbose_name) - - attrs = { - '__module__': model.__module__, - 'tree': models.ForeignKey(model, related_name='change_set', verbose_name=_('document')), - 'tags': models.ManyToManyField(model.tag_model, verbose_name=_('tags'), related_name='change_set'), - 'data': models.FileField(_('data'), upload_to=data_upload_to, storage=repo), - 'Meta': Meta, - } - return type(name, (Change,), attrs) - - -class DocumentMeta(ModelBase): - """Metaclass for Document models.""" - def __new__(cls, name, bases, attrs): - - model = super(DocumentMeta, cls).__new__(cls, name, bases, attrs) - if not model._meta.abstract: - # create a real Tag object and `stage' fk - model.tag_model = create_tag_model(model) - models.ForeignKey(model.tag_model, verbose_name=_('stage'), - null=True, blank=True).contribute_to_class(model, 'stage') - - # create real Change model and `head' fk - model.change_model = create_change_model(model) - - models.ForeignKey( - model.change_model, null=True, blank=True, default=None, - verbose_name=_('head'), help_text=_("This document's current head."), - editable=False).contribute_to_class(model, 'head') - - models.ForeignKey( - User, null=True, blank=True, editable=False, - verbose_name=_('creator'), related_name="created_%s" % name.lower() - ).contribute_to_class(model, 'creator') - - return model - - -class Document(models.Model): - """File in repository. Subclass it to use version control in your app.""" - - __metaclass__ = DocumentMeta - - # default repository path - REPO_PATH = os.path.join(settings.MEDIA_ROOT, 'dvcs') - - user = models.ForeignKey(User, null=True, blank=True, verbose_name=_('user'), help_text=_('Work assignment.')) - - class Meta: - abstract = True - - def __unicode__(self): - return u"{0}, HEAD: {1}".format(self.id, self.head_id) - - def materialize(self, change=None): - if self.head is None: - return u'' - if change is None: - change = self.head - elif not isinstance(change, Change): - change = self.change_set.get(pk=change) - return change.materialize() - - def commit(self, text, author=None, author_name=None, author_email=None, publishable=False, **kwargs): - """Commits a new revision. - - This will automatically merge the commit into the main branch, - if parent is not document's head. - - :param unicode text: new version of the document - :param parent: parent revision (head, if not specified) - :type parent: Change or None - :param User author: the commiter - :param unicode author_name: commiter name (if ``author`` not specified) - :param unicode author_email: commiter e-mail (if ``author`` not specified) - :param Tag[] tags: list of tags to apply to the new commit - :param bool publishable: set new commit as ready to publish - :returns: new head - """ - if 'parent' not in kwargs: - parent = self.head - else: - parent = kwargs['parent'] - if parent is not None and not isinstance(parent, Change): - parent = self.change_set.objects.get(pk=kwargs['parent']) - - tags = kwargs.get('tags', []) - if tags: - # set stage to next tag after the commited one - self.stage = max(tags, key=lambda t: t.ordering).get_next() - - change = self.change_set.create( - author=author, author_name=author_name, author_email=author_email, - description=kwargs.get('description', ''), publishable=publishable, parent=parent) - - change.tags = tags - change.data.save('', ContentFile(text.encode('utf-8'))) - change.save() - - if self.head: - # merge new change as new head - self.head = self.head.merge_with(change, author=author, - author_name=author_name, - author_email=author_email) - else: - self.head = change - self.save() - - post_commit.send(sender=self.head) - - return self.head - - def history(self): - return self.change_set.all().order_by('revision') - - def revision(self): - rev = self.change_set.aggregate( - models.Max('revision'))['revision__max'] - return rev - - def at_revision(self, rev): - """Returns a Change with given revision number.""" - return self.change_set.get(revision=rev) - - def publishable(self): - changes = self.history().filter(publishable=True) - if changes.exists(): - return changes.order_by('-revision')[0] - else: - return None - - @transaction.atomic - def prepend_history(self, other): - """Takes over the the other document's history and prepends to own.""" - - assert self != other - other_revs = other.change_set.all().count() - # workaround for a non-atomic UPDATE in SQLITE - self.change_set.all().update(revision=0-models.F('revision')) - self.change_set.all().update(revision=other_revs - models.F('revision')) - other.change_set.all().update(tree=self) - assert not other.change_set.exists() - other.delete() diff --git a/apps/dvcs/signals.py b/apps/dvcs/signals.py deleted file mode 100755 index 5da075be..00000000 --- a/apps/dvcs/signals.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.dispatch import Signal - -post_commit = Signal() -post_publishable = Signal(providing_args=['publishable']) diff --git a/apps/dvcs/storage.py b/apps/dvcs/storage.py deleted file mode 100755 index 6bb5b595..00000000 --- a/apps/dvcs/storage.py +++ /dev/null @@ -1,18 +0,0 @@ -from zlib import compress, decompress - -from django.core.files.base import ContentFile, File -from django.core.files.storage import FileSystemStorage - - -class GzipFileSystemStorage(FileSystemStorage): - def _open(self, name, mode='rb'): - """TODO: This is good for reading; what about writing?""" - f = open(self.path(name), 'rb') - text = f.read() - f.close() - return ContentFile(decompress(text)) - - def _save(self, name, content): - content = ContentFile(compress(content.read())) - - return super(GzipFileSystemStorage, self)._save(name, content) diff --git a/apps/dvcs/tests/__init__.py b/apps/dvcs/tests/__init__.py deleted file mode 100755 index 868f00a3..00000000 --- a/apps/dvcs/tests/__init__.py +++ /dev/null @@ -1,178 +0,0 @@ -from nose.tools import * -from django.test import TestCase -from dvcs.models import Document - - -class ADocument(Document): - class Meta: - app_label = 'dvcs' - - -class DocumentModelTests(TestCase): - - def assertTextEqual(self, given, expected): - return self.assertEqual(given, expected, - "Expected '''%s'''\n differs from text: '''%s'''" % (expected, given) - ) - - def test_empty_file(self): - doc = ADocument.objects.create() - self.assertTextEqual(doc.materialize(), u"") - - def test_single_commit(self): - doc = ADocument.objects.create() - doc.commit(text=u"Ala ma kota", description="Commit #1") - self.assertTextEqual(doc.materialize(), u"Ala ma kota") - - def test_chained_commits(self): - doc = ADocument.objects.create() - text1 = u""" - Line #1 - Line #2 is cool - """ - text2 = u""" - Line #1 - Line #2 is hot - """ - text3 = u""" - Line #1 - ... is hot - Line #3 ate Line #2 - """ - - c1 = doc.commit(description="Commit #1", text=text1) - c2 = doc.commit(description="Commit #2", text=text2) - c3 = doc.commit(description="Commit #3", text=text3) - - self.assertTextEqual(doc.materialize(), text3) - self.assertTextEqual(doc.materialize(change=c3), text3) - self.assertTextEqual(doc.materialize(change=c2), text2) - self.assertTextEqual(doc.materialize(change=c1), text1) - - def test_parallel_commit_noconflict(self): - doc = ADocument.objects.create() - text1 = u""" - Line #1 - Line #2 - """ - text2 = u""" - Line #1 is hot - Line #2 - """ - text3 = u""" - Line #1 - Line #2 - Line #3 - """ - text_merged = u""" - Line #1 is hot - Line #2 - Line #3 - """ - - base = doc.commit(description="Commit #1", text=text1) - c1 = doc.commit(description="Commit #2", text=text2) - commits = doc.change_set.count() - c2 = doc.commit(description="Commit #3", text=text3, parent=base) - self.assertEqual(doc.change_set.count(), commits + 2, - u"Parallel commits should create an additional merge commit") - self.assertTextEqual(doc.materialize(), text_merged) - - def test_parallel_commit_conflict(self): - doc = ADocument.objects.create() - text1 = u""" - Line #1 - Line #2 - Line #3 - """ - text2 = u""" - Line #1 - Line #2 is hot - Line #3 - """ - text3 = u""" - Line #1 - Line #2 is cool - Line #3 - """ - text_merged = u""" - Line #1 -<<<<<<< - Line #2 is hot -======= - Line #2 is cool ->>>>>>> - Line #3 - """ - base = doc.commit(description="Commit #1", text=text1) - c1 = doc.commit(description="Commit #2", text=text2) - commits = doc.change_set.count() - c2 = doc.commit(description="Commit #3", text=text3, parent=base) - self.assertEqual(doc.change_set.count(), commits + 2, - u"Parallel commits should create an additional merge commit") - self.assertTextEqual(doc.materialize(), text_merged) - - - def test_multiple_parallel_commits(self): - text_a1 = u""" - Line #1 - - Line #2 - - Line #3 - """ - text_a2 = u""" - Line #1 * - - Line #2 - - Line #3 - """ - text_b1 = u""" - Line #1 - - Line #2 ** - - Line #3 - """ - text_c1 = u""" - Line #1 - - Line #2 - - Line #3 *** - """ - text_merged = u""" - Line #1 * - - Line #2 ** - - Line #3 *** - """ - - - doc = ADocument.objects.create() - c1 = doc.commit(description="Commit A1", text=text_a1) - c2 = doc.commit(description="Commit A2", text=text_a2, parent=c1) - c3 = doc.commit(description="Commit B1", text=text_b1, parent=c1) - c4 = doc.commit(description="Commit C1", text=text_c1, parent=c1) - self.assertTextEqual(doc.materialize(), text_merged) - - - def test_prepend_history(self): - doc1 = ADocument.objects.create() - doc2 = ADocument.objects.create() - doc1.commit(text='Commit 1') - doc2.commit(text='Commit 2') - doc2.prepend_history(doc1) - self.assertEqual(ADocument.objects.all().count(), 1) - self.assertTextEqual(doc2.at_revision(1).materialize(), 'Commit 1') - self.assertTextEqual(doc2.materialize(), 'Commit 2') - - def test_prepend_to_self(self): - doc = ADocument.objects.create() - doc.commit(text='Commit 1') - with self.assertRaises(AssertionError): - doc.prepend_history(doc) - self.assertTextEqual(doc.materialize(), 'Commit 1') - diff --git a/apps/email_mangler/__init__.py b/apps/email_mangler/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/email_mangler/locale/pl/LC_MESSAGES/django.mo b/apps/email_mangler/locale/pl/LC_MESSAGES/django.mo deleted file mode 100644 index ed20bfb8..00000000 Binary files a/apps/email_mangler/locale/pl/LC_MESSAGES/django.mo and /dev/null differ diff --git a/apps/email_mangler/locale/pl/LC_MESSAGES/django.po b/apps/email_mangler/locale/pl/LC_MESSAGES/django.po deleted file mode 100644 index 046b8835..00000000 --- a/apps/email_mangler/locale/pl/LC_MESSAGES/django.po +++ /dev/null @@ -1,27 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-11-30 14:27+0100\n" -"PO-Revision-Date: 2011-11-30 14:27+0100\n" -"Last-Translator: Radek Czajka \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" - -#: templatetags/email.py:17 -msgid "at" -msgstr "na" - -#: templatetags/email.py:18 -msgid "dot" -msgstr "kropka" - diff --git a/apps/email_mangler/models.py b/apps/email_mangler/models.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/email_mangler/templatetags/__init__.py b/apps/email_mangler/templatetags/__init__.py deleted file mode 100755 index e69de29b..00000000 diff --git a/apps/email_mangler/templatetags/email.py b/apps/email_mangler/templatetags/email.py deleted file mode 100755 index 376117a8..00000000 --- a/apps/email_mangler/templatetags/email.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.utils.html import escape -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext as _ -from django import template - -register = template.Library() - - -@register.filter -def email_link(email): - email_safe = escape(email) - try: - name, domain = email_safe.split('@', 1) - except ValueError: - return email - - at = escape(_('at')) - dot = escape(_('dot')) - mangled = "%s %s %s" % (name, at, (' %s ' % dot).join(domain.split('.'))) - return mark_safe("%(mangled)s" % { - 'name': name.encode('rot13'), - 'domain': domain.encode('rot13'), - 'mangled': mangled, - }) diff --git a/apps/fileupload/__init__.py b/apps/fileupload/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/fileupload/forms.py b/apps/fileupload/forms.py deleted file mode 100644 index f5e10699..00000000 --- a/apps/fileupload/forms.py +++ /dev/null @@ -1,4 +0,0 @@ -from django import forms - -class UploadForm(forms.Form): - files = forms.FileField() diff --git a/apps/fileupload/locale/pl/LC_MESSAGES/django.mo b/apps/fileupload/locale/pl/LC_MESSAGES/django.mo deleted file mode 100644 index 8fdb9c48..00000000 Binary files a/apps/fileupload/locale/pl/LC_MESSAGES/django.mo and /dev/null differ diff --git a/apps/fileupload/locale/pl/LC_MESSAGES/django.po b/apps/fileupload/locale/pl/LC_MESSAGES/django.po deleted file mode 100644 index a4b60990..00000000 --- a/apps/fileupload/locale/pl/LC_MESSAGES/django.po +++ /dev/null @@ -1,39 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-03-07 16:27+0100\n" -"PO-Revision-Date: 2013-03-07 16:27+0100\n" -"Last-Translator: Radek Czajka \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" - -#: templates/fileupload/picture_form.html:18 -msgid "Browse:" -msgstr "Przeglądanie:" - -#: templates/fileupload/picture_form.html:35 -msgid "Add files..." -msgstr "Dodaj pliki..." - -#: templates/fileupload/picture_form.html:40 -msgid "Start upload" -msgstr "Zacznij wysyłać" - -#: templates/fileupload/picture_form.html:44 -msgid "Cancel upload" -msgstr "Anuluj wysyłanie" - -#: templates/fileupload/picture_form.html:48 -msgid "Delete" -msgstr "Usuń" - diff --git a/apps/fileupload/models.py b/apps/fileupload/models.py deleted file mode 100644 index 8b137891..00000000 --- a/apps/fileupload/models.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/fileupload/static/fileupload/css/bootstrap-image-gallery.min.css b/apps/fileupload/static/fileupload/css/bootstrap-image-gallery.min.css deleted file mode 100644 index a2bffbfc..00000000 --- a/apps/fileupload/static/fileupload/css/bootstrap-image-gallery.min.css +++ /dev/null @@ -1,21 +0,0 @@ -@charset 'UTF-8'; -.modal-gallery{width:auto;max-height:none;} -.modal-gallery .modal-body{max-height:none;} -.modal-gallery .modal-title{display:inline-block;max-height:54px;overflow:hidden;} -.modal-gallery .modal-image{position:relative;margin:auto;min-width:128px;min-height:128px;overflow:hidden;cursor:pointer;} -.modal-gallery .modal-image:hover:before,.modal-gallery .modal-image:hover:after{content:'‹';position:absolute;top:50%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);z-index:1;} -.modal-gallery .modal-image:hover:after{content:'›';left:auto;right:15px;} -.modal-single .modal-image:hover:before,.modal-single .modal-image:hover:after{display:none;} -.modal-loading .modal-image{background:url(../img/loading.gif) center no-repeat;} -.modal-gallery.fade .modal-image{-webkit-transition:width 0.15s ease, height 0.15s ease;-moz-transition:width 0.15s ease, height 0.15s ease;-ms-transition:width 0.15s ease, height 0.15s ease;-o-transition:width 0.15s ease, height 0.15s ease;transition:width 0.15s ease, height 0.15s ease;} -.modal-gallery .modal-image *{position:absolute;top:0;opacity:0;filter:alpha(opacity=0);} -.modal-gallery.fade .modal-image *{-webkit-transition:opacity 0.5s linear;-moz-transition:opacity 0.5s linear;-ms-transition:opacity 0.5s linear;-o-transition:opacity 0.5s linear;transition:opacity 0.5s linear;} -.modal-gallery .modal-image *.in{opacity:1;filter:alpha(opacity=100);} -.modal-fullscreen{border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;background:transparent;overflow:hidden;} -.modal-fullscreen.modal-loading{border:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} -.modal-fullscreen .modal-body{padding:0;} -.modal-fullscreen .modal-header,.modal-fullscreen .modal-footer{position:absolute;top:0;right:0;left:0;background:transparent;border:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:0;z-index:2000;} -.modal-fullscreen .modal-footer{top:auto;bottom:0;} -.modal-fullscreen .close,.modal-fullscreen .modal-title{color:#fff;text-shadow:0 0 2px rgba(33, 33, 33, 0.8);} -.modal-fullscreen .modal-header:hover,.modal-fullscreen .modal-footer:hover{opacity:1;} -@media (max-width:480px){.modal-gallery .btn span{display:none;}} diff --git a/apps/fileupload/static/fileupload/css/bootstrap.min.css b/apps/fileupload/static/fileupload/css/bootstrap.min.css deleted file mode 100644 index 99995247..00000000 --- a/apps/fileupload/static/fileupload/css/bootstrap.min.css +++ /dev/null @@ -1,766 +0,0 @@ -article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} -audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} -audio:not([controls]){display:none;} -a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} -a:hover,a:active{outline:0;} -sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;} -sup{top:-0.5em;} -sub{bottom:-0.25em;} -img{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;} -button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;} -button,input{*overflow:visible;line-height:normal;} -button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;} -button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} -input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;} -input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;} -textarea{overflow:auto;vertical-align:top;} -.clearfix{*zoom:1;} -.clearfix:before,.clearfix:after{display:table;content:"";} -.clearfix:after{clear:both;} -.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;} -.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} -.row{margin-left:-20px;*zoom:1;} -.row:before,.row:after{display:table;content:"";} -.row:after{clear:both;} -[class*="span"]{float:left;margin-left:20px;} -.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} -.span12{width:940px;} -.span11{width:860px;} -.span10{width:780px;} -.span9{width:700px;} -.span8{width:620px;} -.span7{width:540px;} -.span6{width:460px;} -.span5{width:380px;} -.span4{width:300px;} -.span3{width:220px;} -.span2{width:140px;} -.span1{width:60px;} -.offset12{margin-left:980px;} -.offset11{margin-left:900px;} -.offset10{margin-left:820px;} -.offset9{margin-left:740px;} -.offset8{margin-left:660px;} -.offset7{margin-left:580px;} -.offset6{margin-left:500px;} -.offset5{margin-left:420px;} -.offset4{margin-left:340px;} -.offset3{margin-left:260px;} -.offset2{margin-left:180px;} -.offset1{margin-left:100px;} -.row-fluid{width:100%;*zoom:1;} -.row-fluid:before,.row-fluid:after{display:table;content:"";} -.row-fluid:after{clear:both;} -.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.127659574%;*margin-left:2.0744680846382977%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} -.row-fluid [class*="span"]:first-child{margin-left:0;} -.row-fluid .span12{width:99.99999998999999%;*width:99.94680850063828%;} -.row-fluid .span11{width:91.489361693%;*width:91.4361702036383%;} -.row-fluid .span10{width:82.97872339599999%;*width:82.92553190663828%;} -.row-fluid .span9{width:74.468085099%;*width:74.4148936096383%;} -.row-fluid .span8{width:65.95744680199999%;*width:65.90425531263828%;} -.row-fluid .span7{width:57.446808505%;*width:57.3936170156383%;} -.row-fluid .span6{width:48.93617020799999%;*width:48.88297871863829%;} -.row-fluid .span5{width:40.425531911%;*width:40.3723404216383%;} -.row-fluid .span4{width:31.914893614%;*width:31.8617021246383%;} -.row-fluid .span3{width:23.404255317%;*width:23.3510638276383%;} -.row-fluid .span2{width:14.89361702%;*width:14.8404255306383%;} -.row-fluid .span1{width:6.382978723%;*width:6.329787233638298%;} -.container{margin-right:auto;margin-left:auto;*zoom:1;} -.container:before,.container:after{display:table;content:"";} -.container:after{clear:both;} -.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;} -.container-fluid:before,.container-fluid:after{display:table;content:"";} -.container-fluid:after{clear:both;} -p{margin:0 0 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;} -p small{font-size:11px;color:#999999;} -.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;} -.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;} -.page-header h1{line-height:1;} -ul,ol{padding:0;margin:0 0 9px 25px;} -ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} -ul{list-style:disc;} -ol{list-style:decimal;} -li{line-height:18px;} -ul.unstyled,ol.unstyled{margin-left:0;list-style:none;} -dl{margin-bottom:18px;} -dt,dd{line-height:18px;} -dt{font-weight:bold;line-height:17px;} -dd{margin-left:9px;} -.dl-horizontal dt{float:left;width:120px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap;} -.dl-horizontal dd{margin-left:130px;} -hr{margin:18px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;} -strong{font-weight:bold;} -em{font-style:italic;} -.muted{color:#999999;} -abbr[title]{cursor:help;border-bottom:1px dotted #ddd;} -abbr.initialism{font-size:90%;text-transform:uppercase;} -blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;} -blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;} -blockquote small{display:block;line-height:18px;color:#999999;} -blockquote small:before{content:'\2014 \00A0';} -blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;} -blockquote.pull-right p,blockquote.pull-right small{text-align:right;} -q:before,q:after,blockquote:before,blockquote:after{content:"";} -address{display:block;margin-bottom:18px;font-style:normal;line-height:18px;} -small{font-size:100%;} -cite{font-style:normal;} -code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} -code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;} -pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.025px;line-height:18px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} -pre.prettyprint{margin-bottom:18px;} -pre code{padding:0;color:inherit;background-color:transparent;border:0;} -.pre-scrollable{max-height:340px;overflow-y:scroll;} -form{margin:0 0 18px;} -fieldset{padding:0;margin:0;border:0;} -legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #eee;} -legend small{font-size:13.5px;color:#999999;} -label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:18px;} -input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;} -label{display:block;margin-bottom:5px;color:#333333;} -input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555555;background-color:#ffffff;border:1px solid #cccccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} -.uneditable-textarea{width:auto;height:auto;} -label input,label textarea,label select{display:block;} -input[type="image"],input[type="checkbox"],input[type="radio"]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer;background-color:transparent;border:0 \9;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} -input[type="image"]{border:0;} -input[type="file"]{width:auto;padding:initial;line-height:initial;background-color:#ffffff;background-color:initial;border:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} -input[type="button"],input[type="reset"],input[type="submit"]{width:auto;height:auto;} -select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px;} -input[type="file"]{line-height:18px \9;} -select{width:220px;background-color:#ffffff;} -select[multiple],select[size]{height:auto;} -input[type="image"]{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} -textarea{height:auto;} -input[type="hidden"]{display:none;} -.radio,.checkbox{min-height:18px;padding-left:18px;} -.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;} -.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;} -.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;} -.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;} -input,textarea{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;} -input:focus,textarea:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);} -input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus,select:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} -.input-mini{width:60px;} -.input-small{width:90px;} -.input-medium{width:150px;} -.input-large{width:210px;} -.input-xlarge{width:270px;} -.input-xxlarge{width:530px;} -input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;} -input,textarea,.uneditable-input{margin-left:0;} -input.span12,textarea.span12,.uneditable-input.span12{width:930px;} -input.span11,textarea.span11,.uneditable-input.span11{width:850px;} -input.span10,textarea.span10,.uneditable-input.span10{width:770px;} -input.span9,textarea.span9,.uneditable-input.span9{width:690px;} -input.span8,textarea.span8,.uneditable-input.span8{width:610px;} -input.span7,textarea.span7,.uneditable-input.span7{width:530px;} -input.span6,textarea.span6,.uneditable-input.span6{width:450px;} -input.span5,textarea.span5,.uneditable-input.span5{width:370px;} -input.span4,textarea.span4,.uneditable-input.span4{width:290px;} -input.span3,textarea.span3,.uneditable-input.span3{width:210px;} -input.span2,textarea.span2,.uneditable-input.span2{width:130px;} -input.span1,textarea.span1,.uneditable-input.span1{width:50px;} -input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eeeeee;border-color:#ddd;} -input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;} -.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;} -.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853;} -.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e;} -.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;} -.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;} -.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48;} -.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392;} -.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;} -.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;} -.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847;} -.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b;} -.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;} -input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;} -input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} -.form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#f5f5f5;border-top:1px solid #ddd;*zoom:1;} -.form-actions:before,.form-actions:after{display:table;content:"";} -.form-actions:after{clear:both;} -.uneditable-input{overflow:hidden;white-space:nowrap;cursor:not-allowed;background-color:#ffffff;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);} -:-moz-placeholder{color:#999999;} -::-webkit-input-placeholder{color:#999999;} -.help-block,.help-inline{color:#555555;} -.help-block{display:block;margin-bottom:9px;} -.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1;} -.input-prepend,.input-append{margin-bottom:5px;} -.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:middle;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} -.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{z-index:2;} -.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc;} -.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;height:18px;min-width:16px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #ffffff;vertical-align:middle;background-color:#eeeeee;border:1px solid #ccc;} -.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} -.input-prepend .active,.input-append .active{background-color:#a9dba9;border-color:#46a546;} -.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;} -.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} -.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} -.input-append .uneditable-input{border-right-color:#ccc;border-left-color:#eee;} -.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} -.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} -.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} -.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} -.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;} -.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;*zoom:1;} -.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;} -.form-search label,.form-inline label{display:inline-block;} -.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;} -.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;} -.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;} -.control-group{margin-bottom:9px;} -legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate;} -.form-horizontal .control-group{margin-bottom:18px;*zoom:1;} -.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";} -.form-horizontal .control-group:after{clear:both;} -.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right;} -.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:160px;*margin-left:0;} -.form-horizontal .controls:first-child{*padding-left:160px;} -.form-horizontal .help-block{margin-top:9px;margin-bottom:0;} -.form-horizontal .form-actions{padding-left:160px;} -table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;} -.table{width:100%;margin-bottom:18px;} -.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;} -.table th{font-weight:bold;} -.table thead th{vertical-align:bottom;} -.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;} -.table tbody+tbody{border-top:2px solid #dddddd;} -.table-condensed th,.table-condensed td{padding:4px 5px;} -.table-bordered{border:1px solid #dddddd;border-collapse:separate;*border-collapse:collapsed;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} -.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;} -.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;} -.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px;} -.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px;} -.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;} -.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;} -.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;} -.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;} -table .span1{float:none;width:44px;margin-left:0;} -table .span2{float:none;width:124px;margin-left:0;} -table .span3{float:none;width:204px;margin-left:0;} -table .span4{float:none;width:284px;margin-left:0;} -table .span5{float:none;width:364px;margin-left:0;} -table .span6{float:none;width:444px;margin-left:0;} -table .span7{float:none;width:524px;margin-left:0;} -table .span8{float:none;width:604px;margin-left:0;} -table .span9{float:none;width:684px;margin-left:0;} -table .span10{float:none;width:764px;margin-left:0;} -table .span11{float:none;width:844px;margin-left:0;} -table .span12{float:none;width:924px;margin-left:0;} -table .span13{float:none;width:1004px;margin-left:0;} -table .span14{float:none;width:1084px;margin-left:0;} -table .span15{float:none;width:1164px;margin-left:0;} -table .span16{float:none;width:1244px;margin-left:0;} -table .span17{float:none;width:1324px;margin-left:0;} -table .span18{float:none;width:1404px;margin-left:0;} -table .span19{float:none;width:1484px;margin-left:0;} -table .span20{float:none;width:1564px;margin-left:0;} -table .span21{float:none;width:1644px;margin-left:0;} -table .span22{float:none;width:1724px;margin-left:0;} -table .span23{float:none;width:1804px;margin-left:0;} -table .span24{float:none;width:1884px;margin-left:0;} -[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;} -[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;} -.icon-white{background-image:url("../img/glyphicons-halflings-white.png");} -.icon-glass{background-position:0 0;} -.icon-music{background-position:-24px 0;} -.icon-search{background-position:-48px 0;} -.icon-envelope{background-position:-72px 0;} -.icon-heart{background-position:-96px 0;} -.icon-star{background-position:-120px 0;} -.icon-star-empty{background-position:-144px 0;} -.icon-user{background-position:-168px 0;} -.icon-film{background-position:-192px 0;} -.icon-th-large{background-position:-216px 0;} -.icon-th{background-position:-240px 0;} -.icon-th-list{background-position:-264px 0;} -.icon-ok{background-position:-288px 0;} -.icon-remove{background-position:-312px 0;} -.icon-zoom-in{background-position:-336px 0;} -.icon-zoom-out{background-position:-360px 0;} -.icon-off{background-position:-384px 0;} -.icon-signal{background-position:-408px 0;} -.icon-cog{background-position:-432px 0;} -.icon-trash{background-position:-456px 0;} -.icon-home{background-position:0 -24px;} -.icon-file{background-position:-24px -24px;} -.icon-time{background-position:-48px -24px;} -.icon-road{background-position:-72px -24px;} -.icon-download-alt{background-position:-96px -24px;} -.icon-download{background-position:-120px -24px;} -.icon-upload{background-position:-144px -24px;} -.icon-inbox{background-position:-168px -24px;} -.icon-play-circle{background-position:-192px -24px;} -.icon-repeat{background-position:-216px -24px;} -.icon-refresh{background-position:-240px -24px;} -.icon-list-alt{background-position:-264px -24px;} -.icon-lock{background-position:-287px -24px;} -.icon-flag{background-position:-312px -24px;} -.icon-headphones{background-position:-336px -24px;} -.icon-volume-off{background-position:-360px -24px;} -.icon-volume-down{background-position:-384px -24px;} -.icon-volume-up{background-position:-408px -24px;} -.icon-qrcode{background-position:-432px -24px;} -.icon-barcode{background-position:-456px -24px;} -.icon-tag{background-position:0 -48px;} -.icon-tags{background-position:-25px -48px;} -.icon-book{background-position:-48px -48px;} -.icon-bookmark{background-position:-72px -48px;} -.icon-print{background-position:-96px -48px;} -.icon-camera{background-position:-120px -48px;} -.icon-font{background-position:-144px -48px;} -.icon-bold{background-position:-167px -48px;} -.icon-italic{background-position:-192px -48px;} -.icon-text-height{background-position:-216px -48px;} -.icon-text-width{background-position:-240px -48px;} -.icon-align-left{background-position:-264px -48px;} -.icon-align-center{background-position:-288px -48px;} -.icon-align-right{background-position:-312px -48px;} -.icon-align-justify{background-position:-336px -48px;} -.icon-list{background-position:-360px -48px;} -.icon-indent-left{background-position:-384px -48px;} -.icon-indent-right{background-position:-408px -48px;} -.icon-facetime-video{background-position:-432px -48px;} -.icon-picture{background-position:-456px -48px;} -.icon-pencil{background-position:0 -72px;} -.icon-map-marker{background-position:-24px -72px;} -.icon-adjust{background-position:-48px -72px;} -.icon-tint{background-position:-72px -72px;} -.icon-edit{background-position:-96px -72px;} -.icon-share{background-position:-120px -72px;} -.icon-check{background-position:-144px -72px;} -.icon-move{background-position:-168px -72px;} -.icon-step-backward{background-position:-192px -72px;} -.icon-fast-backward{background-position:-216px -72px;} -.icon-backward{background-position:-240px -72px;} -.icon-play{background-position:-264px -72px;} -.icon-pause{background-position:-288px -72px;} -.icon-stop{background-position:-312px -72px;} -.icon-forward{background-position:-336px -72px;} -.icon-fast-forward{background-position:-360px -72px;} -.icon-step-forward{background-position:-384px -72px;} -.icon-eject{background-position:-408px -72px;} -.icon-chevron-left{background-position:-432px -72px;} -.icon-chevron-right{background-position:-456px -72px;} -.icon-plus-sign{background-position:0 -96px;} -.icon-minus-sign{background-position:-24px -96px;} -.icon-remove-sign{background-position:-48px -96px;} -.icon-ok-sign{background-position:-72px -96px;} -.icon-question-sign{background-position:-96px -96px;} -.icon-info-sign{background-position:-120px -96px;} -.icon-screenshot{background-position:-144px -96px;} -.icon-remove-circle{background-position:-168px -96px;} -.icon-ok-circle{background-position:-192px -96px;} -.icon-ban-circle{background-position:-216px -96px;} -.icon-arrow-left{background-position:-240px -96px;} -.icon-arrow-right{background-position:-264px -96px;} -.icon-arrow-up{background-position:-289px -96px;} -.icon-arrow-down{background-position:-312px -96px;} -.icon-share-alt{background-position:-336px -96px;} -.icon-resize-full{background-position:-360px -96px;} -.icon-resize-small{background-position:-384px -96px;} -.icon-plus{background-position:-408px -96px;} -.icon-minus{background-position:-433px -96px;} -.icon-asterisk{background-position:-456px -96px;} -.icon-exclamation-sign{background-position:0 -120px;} -.icon-gift{background-position:-24px -120px;} -.icon-leaf{background-position:-48px -120px;} -.icon-fire{background-position:-72px -120px;} -.icon-eye-open{background-position:-96px -120px;} -.icon-eye-close{background-position:-120px -120px;} -.icon-warning-sign{background-position:-144px -120px;} -.icon-plane{background-position:-168px -120px;} -.icon-calendar{background-position:-192px -120px;} -.icon-random{background-position:-216px -120px;} -.icon-comment{background-position:-240px -120px;} -.icon-magnet{background-position:-264px -120px;} -.icon-chevron-up{background-position:-288px -120px;} -.icon-chevron-down{background-position:-313px -119px;} -.icon-retweet{background-position:-336px -120px;} -.icon-shopping-cart{background-position:-360px -120px;} -.icon-folder-close{background-position:-384px -120px;} -.icon-folder-open{background-position:-408px -120px;} -.icon-resize-vertical{background-position:-432px -119px;} -.icon-resize-horizontal{background-position:-456px -118px;} -.icon-hdd{background-position:0 -144px;} -.icon-bullhorn{background-position:-24px -144px;} -.icon-bell{background-position:-48px -144px;} -.icon-certificate{background-position:-72px -144px;} -.icon-thumbs-up{background-position:-96px -144px;} -.icon-thumbs-down{background-position:-120px -144px;} -.icon-hand-right{background-position:-144px -144px;} -.icon-hand-left{background-position:-168px -144px;} -.icon-hand-up{background-position:-192px -144px;} -.icon-hand-down{background-position:-216px -144px;} -.icon-circle-arrow-right{background-position:-240px -144px;} -.icon-circle-arrow-left{background-position:-264px -144px;} -.icon-circle-arrow-up{background-position:-288px -144px;} -.icon-circle-arrow-down{background-position:-312px -144px;} -.icon-globe{background-position:-336px -144px;} -.icon-wrench{background-position:-360px -144px;} -.icon-tasks{background-position:-384px -144px;} -.icon-filter{background-position:-408px -144px;} -.icon-briefcase{background-position:-432px -144px;} -.icon-fullscreen{background-position:-456px -144px;} -.dropup,.dropdown{position:relative;} -.dropdown-toggle{*margin-bottom:-3px;} -.dropdown-toggle:active,.open .dropdown-toggle{outline:0;} -.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";opacity:0.3;filter:alpha(opacity=30);} -.dropdown .caret{margin-top:8px;margin-left:2px;} -.dropdown:hover .caret,.open .caret{opacity:1;filter:alpha(opacity=100);} -.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:4px 0;margin:1px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;} -.dropdown-menu.pull-right{right:0;left:auto;} -.dropdown-menu .divider{*width:100%;height:1px;margin:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} -.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#333333;white-space:nowrap;} -.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;background-color:#0088cc;} -.open{*z-index:1000;} -.open .dropdown-menu{display:block;} -.pull-right .dropdown-menu{right:0;left:auto;} -.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"\2191";} -.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;} -.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} -.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);} -.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} -.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} -.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} -.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;} -.fade.in{opacity:1;} -.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;} -.collapse.in{height:auto;} -.close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);} -.close:hover{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);} -button.close{padding:0;cursor:pointer;background-color:transparent;border:0;-webkit-appearance:none;} -.btn{display:inline-block;*display:inline;padding:4px 10px 4px;margin-bottom:0;*margin-left:.3em;font-size:13px;line-height:18px;*line-height:20px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-ms-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(top, #ffffff, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-repeat:repeat-x;border:1px solid #cccccc;*border:0;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);} -.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9;} -.btn:active,.btn.active{background-color:#cccccc \9;} -.btn:first-child{*margin-left:0;} -.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-ms-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;} -.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} -.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);} -.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} -.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} -.btn-large [class^="icon-"]{margin-top:1px;} -.btn-small{padding:5px 9px;font-size:11px;line-height:16px;} -.btn-small [class^="icon-"]{margin-top:-1px;} -.btn-mini{padding:2px 6px;font-size:11px;line-height:14px;} -.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} -.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);} -.btn{border-color:#ccc;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} -.btn-primary{background-color:#0074cc;*background-color:#0055cc;background-image:-ms-linear-gradient(top, #0088cc, #0055cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0055cc));background-image:-webkit-linear-gradient(top, #0088cc, #0055cc);background-image:-o-linear-gradient(top, #0088cc, #0055cc);background-image:-moz-linear-gradient(top, #0088cc, #0055cc);background-image:linear-gradient(top, #0088cc, #0055cc);background-repeat:repeat-x;border-color:#0055cc #0055cc #003580;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#0088cc', endColorstr='#0055cc', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} -.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#0055cc;*background-color:#004ab3;} -.btn-primary:active,.btn-primary.active{background-color:#004099 \9;} -.btn-warning{background-color:#faa732;*background-color:#f89406;background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} -.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;*background-color:#df8505;} -.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;} -.btn-danger{background-color:#da4f49;*background-color:#bd362f;background-image:-ms-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(top, #ee5f5b, #bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} -.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a;} -.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;} -.btn-success{background-color:#5bb75b;*background-color:#51a351;background-image:-ms-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(top, #62c462, #51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} -.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249;} -.btn-success:active,.btn-success.active{background-color:#408140 \9;} -.btn-info{background-color:#49afcd;*background-color:#2f96b4;background-image:-ms-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(top, #5bc0de, #2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} -.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0;} -.btn-info:active,.btn-info.active{background-color:#24748c \9;} -.btn-inverse{background-color:#414141;*background-color:#222222;background-image:-ms-linear-gradient(top, #555555, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#222222));background-image:-webkit-linear-gradient(top, #555555, #222222);background-image:-o-linear-gradient(top, #555555, #222222);background-image:-moz-linear-gradient(top, #555555, #222222);background-image:linear-gradient(top, #555555, #222222);background-repeat:repeat-x;border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#555555', endColorstr='#222222', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} -.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222222;*background-color:#151515;} -.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;} -button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;} -button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;} -button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;} -button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;} -button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;} -.btn-group{position:relative;*margin-left:.3em;*zoom:1;} -.btn-group:before,.btn-group:after{display:table;content:"";} -.btn-group:after{clear:both;} -.btn-group:first-child{*margin-left:0;} -.btn-group+.btn-group{margin-left:5px;} -.btn-toolbar{margin-top:9px;margin-bottom:9px;} -.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;} -.btn-group>.btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} -.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px;} -.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px;} -.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px;} -.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px;} -.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;} -.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;} -.btn-group>.dropdown-toggle{*padding-top:4px;padding-right:8px;*padding-bottom:4px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);} -.btn-group>.btn-mini.dropdown-toggle{padding-right:5px;padding-left:5px;} -.btn-group>.btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px;} -.btn-group>.btn-large.dropdown-toggle{padding-right:12px;padding-left:12px;} -.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);} -.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;} -.btn-group.open .btn-primary.dropdown-toggle{background-color:#0055cc;} -.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406;} -.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;} -.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;} -.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;} -.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222222;} -.btn .caret{margin-top:7px;margin-left:0;} -.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100);} -.btn-mini .caret{margin-top:5px;} -.btn-small .caret{margin-top:6px;} -.btn-large .caret{margin-top:6px;border-top-width:5px;border-right-width:5px;border-left-width:5px;} -.dropup .btn-large .caret{border-top:0;border-bottom:5px solid #000000;} -.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);} -.alert{padding:8px 35px 8px 14px;margin-bottom:18px;color:#c09853;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} -.alert-heading{color:inherit;} -.alert .close{position:relative;top:-2px;right:-21px;line-height:18px;} -.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6;} -.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7;} -.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1;} -.alert-block{padding-top:14px;padding-bottom:14px;} -.alert-block>p,.alert-block>ul{margin-bottom:0;} -.alert-block p+p{margin-top:5px;} -.nav{margin-bottom:18px;margin-left:0;list-style:none;} -.nav>li>a{display:block;} -.nav>li>a:hover{text-decoration:none;background-color:#eeeeee;} -.nav>.pull-right{float:right;} -.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;} -.nav li+.nav-header{margin-top:9px;} -.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0;} -.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} -.nav-list>li>a{padding:3px 15px;} -.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;} -.nav-list [class^="icon-"]{margin-right:2px;} -.nav-list .divider{*width:100%;height:1px;margin:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} -.nav-tabs,.nav-pills{*zoom:1;} -.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";} -.nav-tabs:after,.nav-pills:after{clear:both;} -.nav-tabs>li,.nav-pills>li{float:left;} -.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;} -.nav-tabs{border-bottom:1px solid #ddd;} -.nav-tabs>li{margin-bottom:-1px;} -.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:18px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} -.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;} -.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;cursor:default;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;} -.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} -.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#0088cc;} -.nav-stacked>li{float:none;} -.nav-stacked>li>a{margin-right:0;} -.nav-tabs.nav-stacked{border-bottom:0;} -.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} -.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} -.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} -.nav-tabs.nav-stacked>li>a:hover{z-index:2;border-color:#ddd;} -.nav-pills.nav-stacked>li>a{margin-bottom:3px;} -.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;} -.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;} -.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} -.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{margin-top:6px;border-top-color:#0088cc;border-bottom-color:#0088cc;} -.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580;} -.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;border-bottom-color:#333333;} -.nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;} -.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;} -.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);} -.tabs-stacked .open>a:hover{border-color:#999999;} -.tabbable{*zoom:1;} -.tabbable:before,.tabbable:after{display:table;content:"";} -.tabbable:after{clear:both;} -.tab-content{overflow:auto;} -.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;} -.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;} -.tab-content>.active,.pill-content>.active{display:block;} -.tabs-below>.nav-tabs{border-top:1px solid #ddd;} -.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;} -.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} -.tabs-below>.nav-tabs>li>a:hover{border-top-color:#ddd;border-bottom-color:transparent;} -.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd;} -.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;} -.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;} -.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;} -.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} -.tabs-left>.nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;} -.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;} -.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;} -.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} -.tabs-right>.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;} -.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;} -.navbar{*position:relative;*z-index:2;margin-bottom:18px;overflow:visible;} -.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);} -.navbar .container{width:auto;} -.nav-collapse.collapse{height:auto;} -.navbar{color:#999999;} -.navbar .brand:hover{text-decoration:none;} -.navbar .brand{display:block;float:left;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#999999;} -.navbar .navbar-text{margin-bottom:0;line-height:40px;} -.navbar .navbar-link{color:#999999;} -.navbar .navbar-link:hover{color:#ffffff;} -.navbar .btn,.navbar .btn-group{margin-top:5px;} -.navbar .btn-group .btn{margin:0;} -.navbar-form{margin-bottom:0;*zoom:1;} -.navbar-form:before,.navbar-form:after{display:table;content:"";} -.navbar-form:after{clear:both;} -.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;} -.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0;} -.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;} -.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;} -.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;} -.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;} -.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;background-color:#626262;border:1px solid #151515;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0 rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0 rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0 rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;} -.navbar-search .search-query:-moz-placeholder{color:#cccccc;} -.navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;} -.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);} -.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;} -.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} -.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} -.navbar-fixed-top{top:0;} -.navbar-fixed-bottom{bottom:0;} -.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;} -.navbar .nav.pull-right{float:right;} -.navbar .nav>li{display:block;float:left;} -.navbar .nav>li>a{float:none;padding:9px 10px 11px;line-height:19px;color:#999999;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} -.navbar .btn{display:inline-block;padding:4px 10px 4px;margin:5px 5px 6px;line-height:18px;} -.navbar .btn-group{padding:5px 5px 6px;margin:0;} -.navbar .nav>li>a:hover{color:#ffffff;text-decoration:none;background-color:transparent;} -.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#ffffff;text-decoration:none;background-color:#222222;} -.navbar .divider-vertical{width:1px;height:40px;margin:0 9px;overflow:hidden;background-color:#222222;border-right:1px solid #333333;} -.navbar .nav.pull-right{margin-right:0;margin-left:10px;} -.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;background-color:#2c2c2c;*background-color:#222222;background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-image:-moz-linear-gradient(top, #333333, #222222);background-repeat:repeat-x;border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);} -.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{background-color:#222222;*background-color:#151515;} -.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#080808 \9;} -.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);} -.btn-navbar .icon-bar+.icon-bar{margin-top:3px;} -.navbar .dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0, 0, 0, 0.2);content:'';} -.navbar .dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #ffffff;border-left:6px solid transparent;content:'';} -.navbar-fixed-bottom .dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0, 0, 0, 0.2);} -.navbar-fixed-bottom .dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #ffffff;border-bottom:0;} -.navbar .nav li.dropdown .dropdown-toggle .caret,.navbar .nav li.dropdown.open .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} -.navbar .nav li.dropdown.active .caret{opacity:1;filter:alpha(opacity=100);} -.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:transparent;} -.navbar .nav li.dropdown.active>.dropdown-toggle:hover{color:#ffffff;} -.navbar .pull-right .dropdown-menu,.navbar .dropdown-menu.pull-right{right:0;left:auto;} -.navbar .pull-right .dropdown-menu:before,.navbar .dropdown-menu.pull-right:before{right:12px;left:auto;} -.navbar .pull-right .dropdown-menu:after,.navbar .dropdown-menu.pull-right:after{right:13px;left:auto;} -.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);background-repeat:repeat-x;border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;} -.breadcrumb li{display:inline-block;*display:inline;text-shadow:0 1px 0 #ffffff;*zoom:1;} -.breadcrumb .divider{padding:0 5px;color:#999999;} -.breadcrumb .active a{color:#333333;} -.pagination{height:36px;margin:18px 0;} -.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} -.pagination li{display:inline;} -.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0;} -.pagination a:hover,.pagination .active a{background-color:#f5f5f5;} -.pagination .active a{color:#999999;cursor:default;} -.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999999;cursor:default;background-color:transparent;} -.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} -.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} -.pagination-centered{text-align:center;} -.pagination-right{text-align:right;} -.pager{margin-bottom:18px;margin-left:0;text-align:center;list-style:none;*zoom:1;} -.pager:before,.pager:after{display:table;content:"";} -.pager:after{clear:both;} -.pager li{display:inline;} -.pager a{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} -.pager a:hover{text-decoration:none;background-color:#f5f5f5;} -.pager .next a{float:right;} -.pager .previous a{float:left;} -.pager .disabled a,.pager .disabled a:hover{color:#999999;cursor:default;background-color:#fff;} -.modal-open .dropdown-menu{z-index:2050;} -.modal-open .dropdown.open{*z-index:2050;} -.modal-open .popover{z-index:2060;} -.modal-open .tooltip{z-index:2070;} -.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;} -.modal-backdrop.fade{opacity:0;} -.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);} -.modal{position:fixed;top:50%;left:50%;z-index:1050;width:560px;margin:-250px 0 0 -280px;overflow:auto;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;} -.modal.fade{top:-25%;-webkit-transition:opacity 0.3s linear,top 0.3s ease-out;-moz-transition:opacity 0.3s linear,top 0.3s ease-out;-ms-transition:opacity 0.3s linear,top 0.3s ease-out;-o-transition:opacity 0.3s linear,top 0.3s ease-out;transition:opacity 0.3s linear,top 0.3s ease-out;} -.modal.fade.in{top:50%;} -.modal-header{padding:9px 15px;border-bottom:1px solid #eee;} -.modal-header .close{margin-top:2px;} -.modal-body{max-height:400px;padding:15px;overflow-y:auto;} -.modal-form{margin-bottom:0;} -.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;} -.modal-footer:before,.modal-footer:after{display:table;content:"";} -.modal-footer:after{clear:both;} -.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px;} -.modal-footer .btn-group .btn+.btn{margin-left:-1px;} -.tooltip{position:absolute;z-index:1020;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible;} -.tooltip.in{opacity:0.8;filter:alpha(opacity=80);} -.tooltip.top{margin-top:-2px;} -.tooltip.right{margin-left:2px;} -.tooltip.bottom{margin-top:2px;} -.tooltip.left{margin-left:-2px;} -.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000000;border-right:5px solid transparent;border-left:5px solid transparent;} -.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} -.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000000;border-left:5px solid transparent;} -.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000000;border-bottom:5px solid transparent;} -.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} -.tooltip-arrow{position:absolute;width:0;height:0;} -.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;} -.popover.top{margin-top:-5px;} -.popover.right{margin-left:5px;} -.popover.bottom{margin-top:5px;} -.popover.left{margin-left:-5px;} -.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000000;border-right:5px solid transparent;border-left:5px solid transparent;} -.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000000;border-bottom:5px solid transparent;} -.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000000;border-left:5px solid transparent;} -.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} -.popover .arrow{position:absolute;width:0;height:0;} -.popover-inner{width:280px;padding:3px;overflow:hidden;background:#000000;background:rgba(0, 0, 0, 0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);} -.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;} -.popover-content{padding:14px;background-color:#ffffff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;} -.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;} -.thumbnails{margin-left:-20px;list-style:none;*zoom:1;} -.thumbnails:before,.thumbnails:after{display:table;content:"";} -.thumbnails:after{clear:both;} -.row-fluid .thumbnails{margin-left:0;} -.thumbnails>li{margin-bottom:18px;} -.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);} -a.thumbnail:hover{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} -.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto;} -.thumbnail .caption{padding:9px;} -.label,.badge{font-size:10.998px;font-weight:bold;line-height:14px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);white-space:nowrap;vertical-align:baseline;background-color:#999999;} -.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} -.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;} -a.label:hover,a.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;} -.label-important,.badge-important{background-color:#b94a48;} -.label-important[href],.badge-important[href]{background-color:#953b39;} -.label-warning,.badge-warning{background-color:#f89406;} -.label-warning[href],.badge-warning[href]{background-color:#c67605;} -.label-success,.badge-success{background-color:#468847;} -.label-success[href],.badge-success[href]{background-color:#356635;} -.label-info,.badge-info{background-color:#3a87ad;} -.label-info[href],.badge-info[href]{background-color:#2d6987;} -.label-inverse,.badge-inverse{background-color:#333333;} -.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;} -@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{height:18px;margin-bottom:18px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-ms-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(top, #f5f5f5, #f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);} -.progress .bar{width:0;height:18px;font-size:12px;color:#ffffff;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(top, #149bdf, #0480be);background-image:-ms-linear-gradient(top, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-ms-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;} -.progress-striped .bar{background-color:#149bdf;background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;} -.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;} -.progress-danger .bar{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);} -.progress-danger.progress-striped .bar{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} -.progress-success .bar{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);} -.progress-success.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} -.progress-info .bar{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);} -.progress-info.progress-striped .bar{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} -.progress-warning .bar{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);} -.progress-warning.progress-striped .bar{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} -.accordion{margin-bottom:18px;} -.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} -.accordion-heading{border-bottom:0;} -.accordion-heading .accordion-toggle{display:block;padding:8px 15px;} -.accordion-toggle{cursor:pointer;} -.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;} -.carousel{position:relative;margin-bottom:18px;line-height:1;} -.carousel-inner{position:relative;width:100%;overflow:hidden;} -.carousel .item{position:relative;display:none;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-ms-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;} -.carousel .item>img{display:block;line-height:1;} -.carousel .active,.carousel .next,.carousel .prev{display:block;} -.carousel .active{left:0;} -.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;} -.carousel .next{left:100%;} -.carousel .prev{left:-100%;} -.carousel .next.left,.carousel .prev.right{left:0;} -.carousel .active.left{left:-100%;} -.carousel .active.right{left:100%;} -.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);} -.carousel-control.right{right:15px;left:auto;} -.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);} -.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:10px 15px 5px;background:#333333;background:rgba(0, 0, 0, 0.75);} -.carousel-caption h4,.carousel-caption p{color:#ffffff;} -.hero-unit{padding:60px;margin-bottom:30px;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} -.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit;} -.hero-unit p{font-size:18px;font-weight:200;line-height:27px;color:inherit;} -.pull-right{float:right;} -.pull-left{float:left;} -.hide{display:none;} -.show{display:block;} -.invisible{visibility:hidden;} diff --git a/apps/fileupload/static/fileupload/css/jquery.fileupload-ui.css b/apps/fileupload/static/fileupload/css/jquery.fileupload-ui.css deleted file mode 100644 index e36a93df..00000000 --- a/apps/fileupload/static/fileupload/css/jquery.fileupload-ui.css +++ /dev/null @@ -1,84 +0,0 @@ -@charset 'UTF-8'; -/* - * jQuery File Upload UI Plugin CSS 6.3 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2010, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -.fileinput-button { - position: relative; - overflow: hidden; - float: left; - margin-right: 4px; -} -.fileinput-button input { - position: absolute; - top: 0; - right: 0; - margin: 0; - border: solid transparent; - border-width: 0 0 100px 200px; - opacity: 0; - filter: alpha(opacity=0); - -moz-transform: translate(-300px, 0) scale(4); - direction: ltr; - cursor: pointer; -} -.fileupload-buttonbar .btn, -.fileupload-buttonbar .toggle { - margin-bottom: 5px; -} -.files .progress { - width: 200px; -} -.progress-animated .bar { - background: url(../img/progressbar.gif) !important; - filter: none; -} -.fileupload-loading { - position: absolute; - left: 50%; - width: 128px; - height: 128px; - background: url(../img/loading.gif) center no-repeat; - display: none; -} -.fileupload-processing .fileupload-loading { - display: block; -} - -/* Fix for IE 6: */ -*html .fileinput-button { - line-height: 22px; - margin: 1px -3px 0 0; -} - -/* Fix for IE 7: */ -*+html .fileinput-button { - margin: 1px 0 0 0; -} - -@media (max-width: 480px) { - .files .btn span { - display: none; - } - .files .preview * { - width: 40px; - } - .files .name * { - width: 80px; - display: inline-block; - word-wrap: break-word; - } - .files .progress { - width: 20px; - } - .files .delete { - width: 60px; - } -} diff --git a/apps/fileupload/static/fileupload/css/style.css b/apps/fileupload/static/fileupload/css/style.css deleted file mode 100644 index e45d81dc..00000000 --- a/apps/fileupload/static/fileupload/css/style.css +++ /dev/null @@ -1,10 +0,0 @@ -.preview img { - max-height: 50px; -} - -.delete button[data-type=""] { - display: none; -} -.delete button[data-type=""] + input { - display: none; -} diff --git a/apps/fileupload/static/fileupload/img/glyphicons-halflings-white.png b/apps/fileupload/static/fileupload/img/glyphicons-halflings-white.png deleted file mode 100644 index 3bf6484a..00000000 Binary files a/apps/fileupload/static/fileupload/img/glyphicons-halflings-white.png and /dev/null differ diff --git a/apps/fileupload/static/fileupload/img/glyphicons-halflings.png b/apps/fileupload/static/fileupload/img/glyphicons-halflings.png deleted file mode 100644 index 79bc568c..00000000 Binary files a/apps/fileupload/static/fileupload/img/glyphicons-halflings.png and /dev/null differ diff --git a/apps/fileupload/static/fileupload/img/loading.gif b/apps/fileupload/static/fileupload/img/loading.gif deleted file mode 100644 index 90f28cbd..00000000 Binary files a/apps/fileupload/static/fileupload/img/loading.gif and /dev/null differ diff --git a/apps/fileupload/static/fileupload/img/progressbar.gif b/apps/fileupload/static/fileupload/img/progressbar.gif deleted file mode 100644 index fbcce6bc..00000000 Binary files a/apps/fileupload/static/fileupload/img/progressbar.gif and /dev/null differ diff --git a/apps/fileupload/static/fileupload/js/bootstrap-image-gallery.min.js b/apps/fileupload/static/fileupload/js/bootstrap-image-gallery.min.js deleted file mode 100644 index a749f55c..00000000 --- a/apps/fileupload/static/fileupload/js/bootstrap-image-gallery.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(a){"use strict",typeof define=="function"&&define.amd?define(["jquery","load-image","bootstrap"],a):a(window.jQuery,window.loadImage)})(function(a,b){"use strict",a.extend(a.fn.modal.defaults,{delegate:document,selector:null,filter:"*",index:0,href:null,preloadRange:2,offsetWidth:100,offsetHeight:200,canvas:!1,slideshow:0,imageClickDivision:.5});var c=a.fn.modal.Constructor.prototype.show,d=a.fn.modal.Constructor.prototype.hide;a.extend(a.fn.modal.Constructor.prototype,{initLinks:function(){var b=this,c=this.options,d=c.selector||"a[data-target="+c.target+"]";this.$links=a(c.delegate).find(d).filter(c.filter).each(function(a){b.getUrl(this)===c.href&&(c.index=a)}),this.$links[c.index]||(c.index=0)},getUrl:function(b){return b.href||a(b).data("href")},startSlideShow:function(){var a=this;this.options.slideshow&&(this._slideShow=window.setTimeout(function(){a.next()},this.options.slideshow))},stopSlideShow:function(){window.clearTimeout(this._slideShow)},toggleSlideShow:function(){var a=this.$element.find(".modal-slideshow");this.options.slideshow?(this.options.slideshow=0,this.stopSlideShow()):(this.options.slideshow=a.data("slideshow")||5e3,this.startSlideShow()),a.find("i").toggleClass("icon-play icon-pause")},preloadImages:function(){var b=this.options,c=b.index+b.preloadRange+1,d,e;for(e=b.index-b.preloadRange;e").prop("src",this.getUrl(d))},loadImage:function(){var a=this,c=this.$element,d=this.options.index,e=this.getUrl(this.$links[d]),f;this.abortLoad(),this.stopSlideShow(),c.trigger("beforeLoad"),this._loadingTimeout=window.setTimeout(function(){c.addClass("modal-loading")},100),f=c.find(".modal-image").children().removeClass("in"),window.setTimeout(function(){f.remove()},3e3),c.find(".modal-title").text(this.$links[d].title),c.find(".modal-download").prop("href",e),this._loadingImage=b(e,function(b){a.img=b,window.clearTimeout(a._loadingTimeout),c.removeClass("modal-loading"),c.trigger("load"),a.showImage(b),a.startSlideShow()},this._loadImageOptions),this.preloadImages()},showImage:function(b){var c=this.$element,d=a.support.transition&&c.hasClass("fade"),e=d?c.animate:c.css,f=c.find(".modal-image"),g,h;f.css({width:b.width,height:b.height}),c.find(".modal-title").css({width:Math.max(b.width,380)}),a(window).width()>480&&(d&&(g=c.clone().hide().appendTo(document.body)),e.call(c.stop(),{"margin-top":-((g||c).outerHeight()/2),"margin-left":-((g||c).outerWidth()/2)}),g&&g.remove()),f.append(b),h=b.offsetWidth,c.trigger("display"),d?c.is(":visible")?a(b).on(a.support.transition.end,function(d){d.target===b&&(a(b).off(a.support.transition.end),c.trigger("displayed"))}).addClass("in"):(a(b).addClass("in"),c.one("shown",function(){c.trigger("displayed")})):(a(b).addClass("in"),c.trigger("displayed"))},abortLoad:function(){this._loadingImage&&(this._loadingImage.onload=this._loadingImage.onerror=null),window.clearTimeout(this._loadingTimeout)},prev:function(){var a=this.options;a.index-=1,a.index<0&&(a.index=this.$links.length-1),this.loadImage()},next:function(){var a=this.options;a.index+=1,a.index>this.$links.length-1&&(a.index=0),this.loadImage()},keyHandler:function(a){switch(a.which){case 37:case 38:a.preventDefault(),this.prev();break;case 39:case 40:a.preventDefault(),this.next()}},wheelHandler:function(a){a.preventDefault(),a=a.originalEvent,this._wheelCounter=this._wheelCounter||0,this._wheelCounter+=a.wheelDelta||a.detail||0;if(a.wheelDelta&&this._wheelCounter>=120||!a.wheelDelta&&this._wheelCounter<0)this.prev(),this._wheelCounter=0;else if(a.wheelDelta&&this._wheelCounter<=-120||!a.wheelDelta&&this._wheelCounter>0)this.next(),this._wheelCounter=0},initGalleryEvents:function(){var b=this,c=this.$element;c.find(".modal-image").on("click.modal-gallery",function(c){var d=a(this);b.$links.length===1?b.hide():(c.pageX-d.offset().left)/d.width()480&&b.css({"margin-top":-(b.outerHeight()/2),"margin-left":-(b.outerWidth()/2)}),this.initGalleryEvents(),this.initLinks(),this.$links.length&&(b.find(".modal-slideshow, .modal-prev, .modal-next").toggle(this.$links.length!==1),b.toggleClass("modal-single",this.$links.length===1),this.loadImage())}c.apply(this,arguments)},hide:function(){this.isShown&&this.$element.hasClass("modal-gallery")&&(this.options.delegate=document,this.options.href=null,this.destroyGalleryEvents()),d.apply(this,arguments)}}),a(function(){a(document.body).on("click.modal-gallery.data-api",'[data-toggle="modal-gallery"]',function(b){var c=a(this),d=c.data(),e=a(d.target),f=e.data("modal"),g;f||(d=a.extend(e.data(),d)),d.selector||(d.selector="a[rel=gallery]"),g=a(b.target).closest(d.selector),g.length&&e.length&&(b.preventDefault(),d.href=g.prop("href")||g.data("href"),d.delegate=g[0]!==this?this:document,f&&a.extend(f.options,d),e.modal(d))})})}); diff --git a/apps/fileupload/static/fileupload/js/bootstrap.min.js b/apps/fileupload/static/fileupload/js/bootstrap.min.js deleted file mode 100644 index fcfb38bc..00000000 --- a/apps/fileupload/static/fileupload/js/bootstrap.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** -* Bootstrap.js by @fat & @mdo -* Copyright 2012 Twitter, Inc. -* http://www.apache.org/licenses/LICENSE-2.0.txt -*/ -!function(a){a(function(){"use strict",a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.parent('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=c,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(b){return b||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},to:function(b){var c=this.$element.find(".active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(a){return a||(this.paused=!0),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this,j=a.Event("slide");this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();if(e.hasClass("active"))return;if(a.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(j);if(j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})}else{this.$element.trigger(j);if(j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=a.extend({},a.fn.carousel.defaults,typeof c=="object"&&c);e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):typeof c=="string"||(c=f.slide)?e[c]():f.interval&&e.cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b,c,d,e;if(this.transitioning)return;b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find("> .accordion-group > .in");if(d&&d.length){e=d.data("collapse");if(e&&e.transitioning)return;d.collapse("hide"),e||d.data("collapse",null)}this.$element[b](0),this.transition("addClass",a.Event("show"),"shown"),this.$element[b](this.$element[0][c])},hide:function(){var b;if(this.transitioning)return;b=this.dimension(),this.reset(this.$element[b]()),this.transition("removeClass",a.Event("hide"),"hidden"),this.$element[b](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c=="show"&&e.reset(),e.transitioning=0,e.$element.trigger(d)};this.$element.trigger(c);if(c.isDefaultPrevented())return;this.transitioning=1,this.$element[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();a(e).collapse(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}"use strict";var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e,f,g;if(c.is(".disabled, :disabled"))return;return f=c.attr("data-target"),f||(f=c.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,"")),e=a(f),e.length||(e=c.parent()),g=e.hasClass("open"),d(),g||e.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown",".dropdown form",function(a){a.stopPropagation()}).on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('').css({ - position: 'absolute', - height: h, - left: x, - top: y, - width: w - }).appendTo($box[0].offsetParent || $box.parent()).show(); - - - if ($origin.is('.motyw')) { - $('.akap-edit-button').remove(); - withThemes(function(canonThemes){ - $('textarea', $overlay).autocomplete(canonThemes, { - autoFill: true, - multiple: true, - selectFirst: true, - highlight: false - }); - }) - } - - if ($origin.is('.motyw')){ - $('.delete-button', $overlay).click(function(){ - if (window.confirm("Czy jesteś pewien, że chcesz usunąć ten motyw ?")) { - $('[theme-class=' + $origin.attr('theme-class') + ']').remove(); - $overlay.remove(); - $(document).unbind('click.blur-overlay'); - return false; - }; - }); - } - else if($box.is('*[x-annotation-box]')) { - $('.delete-button', $overlay).click(function(){ - if (window.confirm("Czy jesteś pewien, że chcesz usunąć ten przypis?")) { - $origin.remove(); - $overlay.remove(); - $(document).unbind('click.blur-overlay'); - return false; - }; - }); - } - else { - $('.delete-button', $overlay).html("Anuluj"); - $('.delete-button', $overlay).click(function(){ - if (window.confirm("Czy jesteś pewien, że chcesz anulować zmiany?")) { - $overlay.remove(); - $(document).unbind('click.blur-overlay'); - return false; - }; - }); - } - - - var serializer = new XMLSerializer(); - - html2text({ - element: $box[0], - stripOuter: true, - success: function(text){ - $('textarea', $overlay).val($.trim(text)); - - setTimeout(function(){ - $('textarea', $overlay).elastic().focus(); - }, 50); - - function save(argument){ - var nodeName = $box.attr('x-node') || 'pe'; - var insertedText = $('textarea', $overlay).val(); - - if ($origin.is('.motyw')) { - insertedText = insertedText.replace(/,\s*$/, ''); - } - - xml2html({ - xml: '<' + nodeName + '>' + insertedText + '', - success: function(element){ - if (nodeName == 'out-of-flow-text') { - $(element).children().insertAfter($origin); - $origin.remove() - } - else { - $origin.html($(element).html()); - } - $overlay.remove(); - }, - error: function(text){ - alert('Błąd! ' + text); - } - }) - - var msg = $("

Pamiętaj, żeby zapisać swoje zmiany.

"); - $("#base").prepend(msg); - $('#base .saveNotify').fadeOut(3000, function(){ - $(this).remove(); - }); - } - - $('.akap-edit-button', $overlay).click(function(){ - var textAreaOpened = $('textarea', $overlay)[0]; - var startTag = ""; - var endTag = ""; - var buttonName = this.innerHTML; - - if(buttonName == "słowo obce") { - startTag = ""; - endTag = ""; - } else if (buttonName == "wyróżnienie") { - startTag = ""; - endTag = ""; - } else if (buttonName == "tytuł dzieła") { - startTag = ""; - endTag = ""; - } else if(buttonName == "znak spec."){ - addSymbol(); - return false; - } - - var myField = textAreaOpened; - - //IE support - if (document.selection) { - textAreaOpened.focus(); - sel = document.selection.createRange(); - sel.text = startTag + sel.text + endTag; - } - //MOZILLA/NETSCAPE support - else if (textAreaOpened.selectionStart || textAreaOpened.selectionStart == '0') { - var startPos = textAreaOpened.selectionStart; - var endPos = textAreaOpened.selectionEnd; - textAreaOpened.value = textAreaOpened.value.substring(0, startPos) - + startTag + textAreaOpened.value.substring(startPos, endPos) + endTag + textAreaOpened.value.substring(endPos, textAreaOpened.value.length); - } - }); - - $('.accept-button', $overlay).click(function(){ - save(); - $(document).unbind('click.blur-overlay'); - }); - - $(document).bind('click.blur-overlay', function(event){ - if ($(event.target).closest('.html-editarea, #specialCharsContainer').length > 0) { - return; - } - save(); - $(document).unbind('click.blur-overlay'); - }); - - }, - error: function(text){ - alert('Błąd! ' + text); - } - }); - } - - function VisualPerspective(options){ - - var old_callback = options.callback; - - options.callback = function(){ - var element = $("#html-view"); - var button = $(''); - - if (!CurrentDocument.readonly) { - $('#html-view').bind('mousemove', function(event){ - var editable = $(event.target).closest('*[x-editable]'); - $('.active', element).not(editable).removeClass('active').children('.edit-button').remove(); - - if (!editable.hasClass('active')) { - editable.addClass('active').append(button); - } - if (editable.is('.annotation-inline-box')) { - $('*[x-annotation-box]', editable).css({ - position: 'absolute', - left: event.clientX - editable.offset().left + 5, - top: event.clientY - editable.offset().top + 5 - }).show(); - } - else { - $('*[x-annotation-box]').hide(); - } - }); - - $('#insert-annotation-button').click(function(){ - addAnnotation(); - return false; - }); - - $('#insert-theme-button').click(function(){ - addTheme(); - return false; - }); - - $('.edit-button').live('click', function(event){ - event.preventDefault(); - openForEdit($(this).parent()); - }); - - } - - $('.motyw').live('click', function(){ - selectTheme($(this).attr('theme-class')); - }); - - old_callback.call(this); - }; - - $.wiki.Perspective.call(this, options); - }; - - VisualPerspective.prototype = new $.wiki.Perspective(); - - VisualPerspective.prototype.freezeState = function(){ - - }; - - VisualPerspective.prototype.onEnter = function(success, failure){ - $.wiki.Perspective.prototype.onEnter.call(this); - - $.blockUI({ - message: 'Uaktualnianie widoku...' - }); - - function _finalize(callback){ - $.unblockUI(); - if (callback) - callback(); - } - - xml2html({ - xml: this.doc.text, - success: function(element){ - var htmlView = $('#html-view'); - htmlView.html(element); - htmlView.find('*[x-node]').dblclick(function(e) { - if($(e.target).is('textarea')) - return; - var selection = window.getSelection(); - selection.collapseToStart(); - selection.modify('extend', 'forward', 'word'); - e.stopPropagation(); - }); - _finalize(success); - }, - error: function(text, source){ - err = '

Wystąpił błąd:

'+text+'

'; - if (source) - err += '
'+source.replace(/&/g, '&').replace(/'
-                $('#html-view').html(err);
-                _finalize(failure);
-            }
-        });
-    };
-
-    VisualPerspective.prototype.onExit = function(success, failure){
-        var self = this;
-
-        $.blockUI({
-            message: 'Zapisywanie widoku...'
-        });
-
-        function _finalize(callback){
-            $.unblockUI();
-            if (callback)
-                callback();
-        }
-
-        if ($('#html-view .error').length > 0)
-            return _finalize(failure);
-
-        html2text({
-            element: $('#html-view').get(0),
-            stripOuter: true,
-            success: function(text){
-                self.doc.setText(text);
-                _finalize(success);
-            },
-            error: function(text){
-                $('#source-editor').html('

Wystąpił błąd:

' + text + '
'); - _finalize(failure); - } - }); - }; - - $.wiki.VisualPerspective = VisualPerspective; - -})(jQuery); diff --git a/redakcja/static/js/wiki/view_gallery.js b/redakcja/static/js/wiki/view_gallery.js deleted file mode 100644 index 65a716af..00000000 --- a/redakcja/static/js/wiki/view_gallery.js +++ /dev/null @@ -1,259 +0,0 @@ -(function($){ - - function normalizeNumber(pageNumber, pageCount){ - // Page number should be >= 1, <= pageCount; 0 if pageCount = 0 - var pageNumber = parseInt(pageNumber, 10); - - if (!pageCount) - return 0; - - if (!pageNumber || - isNaN(pageNumber) || - pageNumber == Infinity || - pageNumber == -Infinity || - pageNumber < 1) - return 1; - - if (pageNumber > pageCount) - return pageCount; - - return pageNumber; - } - - function bounds(galleryWidth, galleryHeight, imageWidth, imageHeight){ - return { - maxX: 0, - maxY: 0, - minX: galleryWidth - imageWidth, - minY: galleryHeight - imageHeight - } - }; - - function normalizePosition(x, y, galleryWidth, galleryHeight, imageWidth, imageHeight){ - var b = bounds(galleryWidth, galleryHeight, imageWidth, imageHeight); - return { - x: Math.min(b.maxX, Math.max(b.minX, x)), - y: Math.min(b.maxY, Math.max(b.minY, y)) - } - }; - - function fixImageSize(){ - - } - - /* - * Perspective - */ - function ScanGalleryPerspective(options){ - var old_callback = options.callback || function() { }; - - this.noupdate_hash_onenter = true; - this.vsplitbar = 'GALERIA'; - - options.callback = function(){ - var self = this; - - this.dimensions = {}; - this.zoomFactor = 1; - if (this.config().page == undefined) - this.config().page = CurrentDocument.galleryStart; - this.$element = $("#side-gallery"); - this.$numberInput = $('.page-number', this.$element); - - // ... - var origin = {}; - var imageOrigin = {}; - - this.$image = $('.gallery-image img', this.$element).attr('unselectable', 'on'); - - // button handlers - this.$numberInput.change(function(event){ - event.preventDefault(); - self.setPage($(this).val()); - }); - - $('.start-page', this.$element).click(function(){ - self.setPage(CurrentDocument.galleryStart); - }); - - $('.previous-page', this.$element).click(function(){ - self.setPage(parseInt(self.$numberInput.val(),10) - 1); - }); - - $('.next-page', this.$element).click(function(){ - self.setPage(parseInt(self.$numberInput.val(),10) + 1); - }); - - $('.zoom-in', this.$element).click(function(){ - self.alterZoom(0.2); - }); - - $('.zoom-out', this.$element).click(function(){ - self.alterZoom((-0.2)); - }); - - $(window).resize(function(){ - self.dimensions.galleryWidth = self.$image.parent().width(); - self.dimensions.galleryHeight = self.$image.parent().height(); - }); - - this.$image.load(function(){ - console.log("Image loaded.") - self._resizeImage(); - }).bind('mousedown', function() { - self.imageMoveStart.apply(self, arguments); - }); - - - - old_callback.call(this); - }; - - $.wiki.Perspective.call(this, options); - }; - - ScanGalleryPerspective.prototype = new $.wiki.Perspective(); - - ScanGalleryPerspective.prototype._resizeImage = function(){ - var $img = this.$image; - - $img.css({ - width: '', - height: '' - }); - - this.dimensions = { - width: $img.width() * this.zoomFactor, - height: $img.height() * this.zoomFactor, - originWidth: $img.width(), - originHeight: $img.height(), - galleryWidth: $img.parent().width(), - galleryHeight: $img.parent().height() - }; - - if (!(this.dimensions.width && this.dimensions.height)) { - setTimeout(function(){ - $img.load(); - }, 100); - } - - var position = normalizePosition($img.position().left, $img.position().top, this.dimensions.galleryWidth, this.dimensions.galleryHeight, this.dimensions.width, this.dimensions.height); - - $img.css({ - left: position.x, - top: position.y, - width: $img.width() * this.zoomFactor, - height: $img.height() * this.zoomFactor - }); - }; - - ScanGalleryPerspective.prototype.setPage = function(newPage){ - newPage = normalizeNumber(newPage, this.doc.galleryImages.length); - this.$numberInput.val(newPage); - this.config().page = newPage; - $('.gallery-image img', this.$element).attr('src', this.doc.galleryImages[newPage - 1]); - }; - - ScanGalleryPerspective.prototype.alterZoom = function(delta){ - var zoomFactor = this.zoomFactor + delta; - if (zoomFactor < 0.2) - zoomFactor = 0.2; - if (zoomFactor > 2) - zoomFactor = 2; - this.setZoom(zoomFactor); - }; - - ScanGalleryPerspective.prototype.setZoom = function(factor){ - this.zoomFactor = factor; - - this.dimensions.width = this.dimensions.originWidth * this.zoomFactor; - this.dimensions.height = this.dimensions.originHeight * this.zoomFactor; - - // var position = normalizePosition(this.$image.position().left, this.$image.position().top, this.dimensions.galleryWidth, this.dimensions.galleryHeight, this.dimensions.width, this.dimensions.height); - - this._resizeImage(); - }; - - /* - * Movement - */ - ScanGalleryPerspective.prototype.imageMoved = function(event){ - event.preventDefault(); - - // origin is where the drag started - // imageOrigin is where the drag started on the image - - var newX = event.clientX - this.origin.x + this.imageOrigin.left; - var newY = event.clientY - this.origin.y + this.imageOrigin.top; - - var position = normalizePosition(newX, newY, this.dimensions.galleryWidth, this.dimensions.galleryHeight, this.dimensions.width, this.dimensions.height); - - this.$image.css({ - left: position.x, - top: position.y, - }); - - return false; - }; - - ScanGalleryPerspective.prototype.imageMoveStart = function(event){ - event.preventDefault(); - - var self = this; - - this.origin = { - x: event.clientX, - y: event.clientY - }; - - this.imageOrigin = self.$image.position(); - $(document).bind('mousemove.gallery', function(){ - self.imageMoved.apply(self, arguments); - }).bind('mouseup.gallery', function() { - self.imageMoveStop.apply(self, arguments); - }); - - return false; - }; - - ScanGalleryPerspective.prototype.imageMoveStop = function(event){ - $(document).unbind('mousemove.gallery').unbind('mouseup.gallery'); - }; - - /* - * Loading gallery - */ - ScanGalleryPerspective.prototype.onEnter = function(success, failure){ - var self = this; - - $.wiki.Perspective.prototype.onEnter.call(this); - - $('.vsplitbar').not('.active').trigger('click'); - $(".vsplitbar-title").html("↓ GALERIA ↓"); - - this.doc.refreshGallery({ - success: function(doc, data){ - self.$image.show(); - console.log("gconfig:", self.config().page ); - self.setPage( self.config().page ); - $('#imagesCount').html("/" + doc.galleryImages.length); - - $('.error_message', self.$element).hide(); - if(success) success(); - }, - failure: function(doc, message){ - self.$image.hide(); - $('.error_message', self.$element).show().html(message); - if(failure) failure(); - } - }); - - }; - - ScanGalleryPerspective.prototype.onExit = function(success, failure) { - - }; - - $.wiki.ScanGalleryPerspective = ScanGalleryPerspective; - -})(jQuery); diff --git a/redakcja/static/js/wiki/view_history.js b/redakcja/static/js/wiki/view_history.js deleted file mode 100644 index 85adca0a..00000000 --- a/redakcja/static/js/wiki/view_history.js +++ /dev/null @@ -1,204 +0,0 @@ -(function($){ - - function HistoryPerspective(options) { - var old_callback = options.callback || function() {}; - - 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() { - self.makeDiff(); - }); - - $('#pubmark-changeset-button').click(function() { - self.showPubmarkForm(); - }); - - $('#doc-revert-button').click(function() { - self.revertDialog(); - }); - - $('#open-preview-button').click(function(event) { - var selected = $('#changes-list .entry.selected'); - - if (selected.length != 1) { - window.alert("Wybierz dokładnie *jedną* wersję."); - return; - } - - var version = parseInt($("*[data-stub-value='version']", selected[0]).text()); - window.open($(this).attr('data-basehref') + "?revision=" + version); - - event.preventDefault(); - }); - - $('#changes-list .entry').live('click', function(){ - var $this = $(this); - - var selected_count = $("#changes-list .entry.selected").length; - - if ($this.hasClass('selected')) { - $this.removeClass('selected'); - selected_count -= 1; - } - else { - if (selected_count < 2) { - $this.addClass('selected'); - selected_count += 1; - }; - }; - - $('#history-view-editor .toolbar button').attr('disabled', 'disabled'). - filter('*[data-enabled-when~=' + selected_count + '], *[data-enabled-when~=*]'). - attr('disabled', null); - }); - - $('#changes-list span.tag').live('click', function(event){ - return false; - }); - - old_callback.call(this); - } - - $.wiki.Perspective.call(this, options); - }; - - HistoryPerspective.prototype = new $.wiki.Perspective(); - - HistoryPerspective.prototype.freezeState = function(){ - // must - }; - - HistoryPerspective.prototype.onEnter = function(success, failure){ - $.wiki.Perspective.prototype.onEnter.call(this); - - $.blockUI({ - message: 'Odświeżanie historii...' - }); - - function _finalize(s){ - $.unblockUI(); - - if (s) { - if (success) - success(); - } - else { - if (failure) - failure(); - } - } - - function _failure(doc, message){ - $('#history-view .message-box').html('Nie udało się odświeżyć historii:' + message).show(); - _finalize(false); - }; - - function _success(doc, data){ - $('#history-view .message-box').hide(); - var changes_list = $('#changes-list'); - var $stub = $('#history-view .row-stub'); - changes_list.html(''); - - var tags = $('select#id_addtag-tag option'); - - $.each(data, function(){ - $.wiki.renderStub({ - container: changes_list, - stub: $stub, - data: this, - filters: { -// tag: function(value) { -// return tags.filter("*[value='"+value+"']").text(); -// } -// description: function(value) { -// return value.replace('\n', '); -// } - } - }); - }); - - _finalize(true); - }; - - return this.doc.fetchHistory({ - success: _success, - failure: _failure - }); - }; - - 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); - - if (selected.length != 2) { - window.alert("Musisz zaznaczyć dokładnie dwie wersje do porównania."); - return; - } - - $.blockUI({ - message: 'Wczytywanie porównania...' - }); - - var rev_from = $("*[data-stub-value='version']", selected[1]).text(); - var rev_to = $("*[data-stub-value='version']", selected[0]).text(); - - return 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); - $.unblockUI(); - }, - failure: function(doc){ - $.unblockUI(); - } - }); - }; - - HistoryPerspective.prototype.revertDialog = function(){ - var self = this; - 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('#revert_dialog', {revision: version}); - }; - - $.wiki.HistoryPerspective = HistoryPerspective; - -})(jQuery); diff --git a/redakcja/static/js/wiki/view_search.js b/redakcja/static/js/wiki/view_search.js deleted file mode 100644 index b49671c6..00000000 --- a/redakcja/static/js/wiki/view_search.js +++ /dev/null @@ -1,117 +0,0 @@ -(function($){ - - /* - * Perspective - */ - function SearchPerspective(options){ - var old_callback = options.callback || function() { }; - - this.noupdate_hash_onenter = true; - this.vsplitbar = 'ZNAJDŹ I ZAMIEŃ'; - - options.callback = function(){ - var self = this; - - this.editor = null; - this.$element = $("#side-search"); - this.$searchInput = $('#search-input', this.$element); - this.$replaceInput = $('#replace-input', this.$element); - this.$searchButton = $('#search-button', this.$element); - this.$replaceButton = $('#replace-button', this.$element); - - this.$replaceButton.attr("disabled","disabled"); - this.options = Array(); - - // handlers - this.$searchInput.change(function(event){ - self.searchCursor = null; - }); - this.$replaceInput.change(function(event){ - self.searchCursor = null; - }); - - $("#side-search input:checkbox").each(function() { - self.options[this.id] = this.checked; - }).change(function(){ - self.options[this.id] = this.checked; - self.searchCursor = null; - }); - - this.$searchButton.click(function(){ - if (!self.search()) - alert('Brak wyników.'); - }); - - this.$replaceButton.click(function(){ - self.replace(); - }); - - old_callback.call(this); - }; - - $.wiki.Perspective.call(this, options); - }; - - SearchPerspective.prototype = new $.wiki.Perspective(); - - SearchPerspective.prototype.search = function(){ - var self = this; - var query = self.$searchInput.val(); - - if (!self.editor) - self.editor = $.wiki.perspectiveForTab('#CodeMirrorPerspective').codemirror - - if (!self.searchCursor) { - self.searchCursor = self.editor.getSearchCursor( - self.$searchInput.val(), - self.options['search-from-cursor'], - !self.options['search-case-sensitive'] - ); - } - if (self.searchCursor.findNext()) { - self.searchCursor.select(); - self.$replaceButton.removeAttr("disabled"); - return true; - } - else { - self.searchCursor = null; - this.$replaceButton.attr("disabled","disabled"); - return false; - } - }; - - SearchPerspective.prototype.replace = function(){ - var self = this; - var query = self.$replaceInput.val(); - - if (!self.searchCursor) { - self.search(); - } - else {} - self.searchCursor.select(); - self.searchCursor.replace(query); - if(self.search() && self.options['replace-all']) { - self.replace(); - } - }; - - SearchPerspective.prototype.onEnter = function(success, failure){ - var self = this; - - $.wiki.Perspective.prototype.onEnter.call(this); - self.$searchCursor = null; - - $('.vsplitbar').not('.active').trigger('click'); - $(".vsplitbar-title").html("↓ ZNAJDŹ I ZAMIEŃ ↓"); - - if ($.wiki.activePerspective() != 'CodeMirrorPerspective') - $.wiki.switchToTab('#CodeMirrorPerspective'); - }; - - SearchPerspective.prototype.onExit = function(success, failure) { - - }; - - $.wiki.SearchPerspective = SearchPerspective; - -})(jQuery); diff --git a/redakcja/static/js/wiki/view_summary.js b/redakcja/static/js/wiki/view_summary.js deleted file mode 100644 index 099a0e81..00000000 --- a/redakcja/static/js/wiki/view_summary.js +++ /dev/null @@ -1,61 +0,0 @@ -(function($){ - - function SummaryPerspective(options) { - var old_callback = options.callback || function() {}; - - options.callback = function() { - var self = this; - - // first time page is rendered - $('#summary-cover-refresh').click(function() { - self.refreshCover(); - }); - - old_callback.call(this); - } - - $.wiki.Perspective.call(this, options); - }; - - SummaryPerspective.prototype = new $.wiki.Perspective(); - - SummaryPerspective.prototype.refreshCover = function() { - $('#summary-cover-refresh').attr('disabled', 'disabled'); - this.doc.refreshCover({ - success: function(text) { - $('#summary-cover').attr('src', text); - $('#summary-cover-refresh').removeAttr('disabled'); - } - }); - }; - - SummaryPerspective.prototype.showCharCount = function() { - var cc; - try { - cc = this.doc.getLength(); - $('#charcount_untagged').hide(); - } - catch (e) { - $('#charcount_untagged').show(); - cc = this.doc.text.replace(/\s{2,}/g, ' ').length; - } - $('#charcount').html(cc); - $('#charcount_pages').html((Math.round(cc/18)/100).toLocaleString()); - } - - SummaryPerspective.prototype.freezeState = function(){ - // must - }; - - SummaryPerspective.prototype.onEnter = function(success, failure){ - $.wiki.Perspective.prototype.onEnter.call(this); - - this.showCharCount(); - - console.log("Entered summery view"); - }; - - $.wiki.SummaryPerspective = SummaryPerspective; - -})(jQuery); - diff --git a/redakcja/static/js/wiki/wikiapi.js b/redakcja/static/js/wiki/wikiapi.js deleted file mode 100644 index 8df3ef5a..00000000 --- a/redakcja/static/js/wiki/wikiapi.js +++ /dev/null @@ -1,434 +0,0 @@ -(function($) { - $.wikiapi = {}; - var noop = function() { - }; - var noops = { - success: noop, - failure: noop - }; - /* - * Return absolute reverse path of given named view. (at least he have it - * hard-coded in one place) - * - * TODO: think of a way, not to hard-code it here ;) - * - */ - function reverse() { - var vname = arguments[0]; - var base_path = "/editor"; - - if (vname == "ajax_document_text") { - var path = "/text/" + arguments[1] + '/'; - - if (arguments[2] !== undefined) - path += arguments[2] + '/'; - - return base_path + path; - } - - if (vname == "ajax_document_revert") { - return base_path + "/revert/" + arguments[1] + '/'; - } - - - if (vname == "ajax_document_history") { - - return base_path + "/history/" + arguments[1] + '/'; - } - - if (vname == "ajax_document_gallery") { - - return base_path + "/gallery/" + arguments[1] + '/'; - } - - if (vname == "ajax_document_diff") - return base_path + "/diff/" + arguments[1] + '/'; - - if (vname == "ajax_document_rev") - return base_path + "/rev/" + arguments[1] + '/'; - - if (vname == "ajax_document_pubmark") - return base_path + "/pubmark/" + arguments[1] + '/'; - - if (vname == "ajax_cover_preview") - return "/cover/preview/"; - - console.log("Couldn't reverse match:", vname); - return "/404.html"; - }; - - /* - * Document Abstraction - */ - function WikiDocument(element_id) { - var meta = $('#' + element_id); - this.id = meta.attr('data-chunk-id'); - - this.revision = $("*[data-key='revision']", meta).text(); - this.readonly = !!$("*[data-key='readonly']", meta).text(); - - this.galleryLink = $("*[data-key='gallery']", meta).text(); - this.galleryStart = parseInt($("*[data-key='gallery-start']", meta).text()); - - var diff = $("*[data-key='diff']", meta).text(); - if (diff) { - 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; - this._lock = -1; - this._context_lock = -1; - this._lock_count = 0; - }; - - WikiDocument.prototype.triggerDocumentChanged = function() { - $(document).trigger('wlapi_document_changed', this); - }; - /* - * Fetch text of this document. - */ - WikiDocument.prototype.fetch = function(params) { - params = $.extend({}, noops, params); - var self = this; - $.ajax({ - method: "GET", - url: reverse("ajax_document_text", self.id), - data: {"revision": self.revision}, - dataType: 'json', - success: function(data) { - var changed = false; - - if (self.text === null || self.revision !== data.revision) { - self.text = data.text; - self.revision = data.revision; - self.gallery = data.gallery; - changed = true; - self.triggerDocumentChanged(); - }; - - self.has_local_changes = false; - params['success'](self, changed); - }, - error: function() { - params['failure'](self, "Nie udało się wczytać treści dokumentu."); - } - }); - }; - /* - * Fetch history of this document. - * - * from - First revision to fetch (default = 0) upto - Last revision to - * fetch (default = tip) - * - */ - WikiDocument.prototype.fetchHistory = function(params) { - /* this doesn't modify anything, so no locks */ - params = $.extend({}, noops, params); - var self = this; - $.ajax({ - method: "GET", - url: reverse("ajax_document_history", self.id), - dataType: 'json', - data: { - "from": params['from'], - "upto": params['upto'] - }, - success: function(data) { - params['success'](self, data); - }, - error: function() { - params['failure'](self, "Nie udało się wczytać historii dokumentu."); - } - }); - }; - WikiDocument.prototype.fetchDiff = function(params) { - /* this doesn't modify anything, so no locks */ - var self = this; - params = $.extend({ - 'from': self.revision, - 'to': self.revision - }, noops, params); - $.ajax({ - method: "GET", - url: reverse("ajax_document_diff", self.id), - dataType: 'html', - data: { - "from": params['from'], - "to": params['to'] - }, - success: function(data) { - params['success'](self, data); - }, - error: function() { - params['failure'](self, "Nie udało się wczytać porównania wersji."); - } - }); - }; - - WikiDocument.prototype.checkRevision = function(params) { - /* this doesn't modify anything, so no locks */ - var self = this; - $.ajax({ - method: "GET", - url: reverse("ajax_document_rev", self.id), - dataType: 'text', - success: function(data) { - if (data == '') { - if (params.error) - params.error(); - } - else if (data != self.revision) - params.outdated(); - } - }); - }; - - /* - * Fetch gallery - */ - WikiDocument.prototype.refreshGallery = function(params) { - params = $.extend({}, noops, params); - var self = this; - $.ajax({ - method: "GET", - url: reverse("ajax_document_gallery", self.galleryLink), - dataType: 'json', - // data: {}, - success: function(data) { - self.galleryImages = data; - params['success'](self, data); - }, - error: function(xhr) { - switch (xhr.status) { - case 403: - var msg = 'Galerie dostępne tylko dla zalogowanych użytkowników.'; - break; - case 404: - var msg = "Nie znaleziono galerii o nazwie: '" + self.galleryLink + "'."; - default: - var msg = "Nie udało się wczytać galerii o nazwie: '" + self.galleryLink + "'."; - } - self.galleryImages = []; - params['failure'](self, "

" + msg + "

"); - } - }); - }; - - /* - * Set document's text - */ - WikiDocument.prototype.setText = function(text) { - return this.setDocumentProperty('text', text); - }; - - /* - * Set document's gallery link - */ - WikiDocument.prototype.setGalleryLink = function(gallery) { - return this.setDocumentProperty('galleryLink', gallery); - }; - - /* - * Set document's property - */ - WikiDocument.prototype.setDocumentProperty = function(property, value) { - if(this[property] !== value) { - this[property] = value; - this.has_local_changes = true; - } - }; - - /* - * Save text back to the server - */ - WikiDocument.prototype.save = function(params) { - params = $.extend({}, noops, params); - var self = this; - - if (!self.has_local_changes) { - console.log("Abort: no changes."); - return params['success'](self, false, "Nie ma zmian do zapisania."); - }; - - // Serialize form to dictionary - var data = {}; - $.each(params['form'].serializeArray(), function() { - data[this.name] = this.value; - }); - - data['textsave-text'] = self.text; - - $.ajax({ - url: reverse("ajax_document_text", self.id), - type: "POST", - dataType: "json", - data: data, - success: function(data) { - var changed = false; - - $('#header').removeClass('saving'); - - if (data.text) { - self.text = data.text; - self.revision = data.revision; - self.gallery = data.gallery; - changed = true; - self.triggerDocumentChanged(); - }; - - params['success'](self, changed, ((changed && "Udało się zapisać :)") || "Twoja wersja i serwera jest identyczna")); - }, - error: function(xhr) { - if ($('#header').hasClass('saving')) { - $('#header').removeClass('saving'); - $.blockUI({ - message: "

Nie udało się zapisać zmian.

" - }) - } - else { - try { - params['failure'](self, $.parseJSON(xhr.responseText)); - } - catch (e) { - params['failure'](self, { - "__message": "

Nie udało się zapisać - błąd serwera.

" - }); - }; - } - - } - }); - - $('#save-hide').click(function(){ - $('#header').addClass('saving'); - $.unblockUI(); - $.wiki.blocking.unblock(); - }); - }; /* end of save() */ - - WikiDocument.prototype.revertToVersion = function(params) { - var self = this; - params = $.extend({}, noops, params); - - if (params.revision >= this.revision) { - params.failure(self, 'Proszę wybrać rewizję starszą niż aktualna.'); - return; - } - - // Serialize form to dictionary - var data = {}; - $.each(params['form'].serializeArray(), function() { - data[this.name] = this.value; - }); - - $.ajax({ - url: reverse("ajax_document_revert", self.id), - type: "POST", - dataType: "json", - data: data, - success: function(data) { - if (data.text) { - self.text = data.text; - self.revision = data.revision; - self.gallery = data.gallery; - self.triggerDocumentChanged(); - - params.success(self, "Udało się przywrócić wersję :)"); - } - else { - params.failure(self, "Przywracana wersja identyczna z aktualną. Anulowano przywracanie."); - } - }, - error: function(xhr) { - params.failure(self, "Nie udało się przywrócić wersji - błąd serwera."); - } - }); - }; - - 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."] - }); - }; - }; - } - }); - }; - - WikiDocument.prototype.refreshCover = function(params) { - var self = this; - var data = { - xml: self.text // TODO: send just DC - }; - $.ajax({ - url: reverse("ajax_cover_preview"), - type: "POST", - data: data, - success: function(data) { - params.success(data); - }, - error: function(xhr) { - // params.failure("Nie udało się odświeżyć okładki - błąd serwera."); - } - }); - }; - - - WikiDocument.prototype.getLength = function(params) { - var xml = this.text.replace(/\/(\s+)/g, '
$1'); - var parser = new DOMParser(); - var doc = parser.parseFromString(xml, 'text/xml'); - var error = $('parsererror', doc); - - if (error.length > 0) { - throw "Not an XML document."; - } - $.xmlns["rdf"] = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; - $('rdf|RDF, motyw, pa, pe, pr, pt', doc).remove(); - var text = $(doc).text(); - text = $.trim(text.replace(/\s{2,}/g, ' ')); - return text.length; - } - - - $.wikiapi.WikiDocument = WikiDocument; -})(jQuery); diff --git a/redakcja/static/js/wiki/xslt.js b/redakcja/static/js/wiki/xslt.js deleted file mode 100644 index ab90e0c1..00000000 --- a/redakcja/static/js/wiki/xslt.js +++ /dev/null @@ -1,393 +0,0 @@ -/* - * - * XSLT STUFF - * - */ -function createXSLT(xsl) { - var p = new XSLTProcessor(); - p.importStylesheet(xsl); - return p; -} - -var xml2htmlStylesheet = null; - -// Wykonuje block z załadowanymi arkuszami stylów -function withStylesheets(code_block, onError) -{ - if (!xml2htmlStylesheet) { - $.blockUI({message: 'Ładowanie arkuszy stylów...'}); - $.ajax({ - url: STATIC_URL + 'xsl/wl2html_client.xsl?20171106', - dataType: 'xml', - timeout: 10000, - success: function(data) { - xml2htmlStylesheet = createXSLT(data); - $.unblockUI(); - code_block(); - - }, - error: onError - }) - } - else { - code_block(); - } -} - - -// Wykonuje block z załadowanymi kanonicznymi motywami -function withThemes(code_block, onError) -{ - if (typeof withThemes.canon == 'undefined') { - $.ajax({ - url: '/editor/themes', - dataType: 'text', - success: function(data) { - withThemes.canon = data.split('\n'); - code_block(withThemes.canon); - }, - error: function() { - withThemes.canon = null; - code_block(withThemes.canon); - } - }) - } - else { - code_block(withThemes.canon); - } -} - - -function xml2html(options) { - withStylesheets(function() { - var xml = options.xml.replace(/\/(\s+)/g, '
$1'); - xml = xml.replace(/([^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ\s<>«»\\*_!,:;?&%."'=#()\/-]+)/g, '$1'); - var parser = new DOMParser(); - var serializer = new XMLSerializer(); - var doc = parser.parseFromString(xml, 'text/xml'); - var error = $('parsererror', doc); - - if (error.length == 0) { - doc = xml2htmlStylesheet.transformToFragment(doc, document); - console.log(doc.firstChild); - - if(doc.firstChild === null) { - options.error("Błąd w przetwarzaniu XML."); - return; - } - - error = $('parsererror', doc); - } - - if (error.length > 0 && options.error) { - source = $('sourcetext', doc); - source_text = source.text(); - source.text(''); - options.error(error.text(), source_text); - } else { - options.success(doc.childNodes); - - withThemes(function(canonThemes) { - if (canonThemes != null) { - $('.theme-text-list').addClass('canon').each(function(){ - var themes = $(this).html().split(','); - for (i in themes) { - themes[i] = $.trim(themes[i]); - if (canonThemes.indexOf(themes[i]) == -1) - themes[i] = '' + themes[i] + "" - } - $(this).html(themes.join(', ')); - }); - } - }); - } - }, function() { options.error && options.error('Nie udało się załadować XSLT'); }); -} - -/* USEFULL CONSTANTS */ -const ELEMENT_NODE = 1; -const ATTRIBUTE_NODE = 2; -const TEXT_NODE = 3; -const CDATA_SECTION_NODE = 4; -const ENTITY_REFERENCE_NODE = 5; -const ENTITY_NODE = 6; -const PROCESSING_INSTRUCTION_NODE = 7; -const COMMENT_NODE = 8; -const DOCUMENT_NODE = 9; -const DOCUMENT_TYPE_NODE = 10; -const DOCUMENT_FRAGMENT_NODE = 11; -const NOTATION_NODE = 12; -const XATTR_RE = /^x-attr-name-(.*)$/; - -const ELEM_START = 1; -const ELEM_END = 2; -const NS_END = 3; - -const NAMESPACES = { - // namespaces not listed here will be assigned random names - "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf", - "http://purl.org/dc/elements/1.1/": "dc", - "http://www.w3.org/XML/1998/namespace": "xml" -}; - -function HTMLSerializer() { - // empty constructor -} - - - -HTMLSerializer.prototype._prepare = function() { - this.stack = []; - - // XML namespace is implicit - this.nsMap = {"http://www.w3.org/XML/1998/namespace": "xml"}; - - this.result = ""; - this.nsCounter = 1; -} - -HTMLSerializer.prototype._pushElement = function(element) { - this.stack.push({ - "type": ELEM_START, - "node": element - }); -} - -HTMLSerializer.prototype._pushChildren = function(element) { - for(var i = element.childNodes.length-1; i >= 0; i--) - this._pushElement(element.childNodes.item(i)); -} - -HTMLSerializer.prototype._pushTagEnd = function(tagName) { - this.stack.push({ - "type": ELEM_END, - "tagName": tagName - }); -} - -HTMLSerializer.prototype._verseBefore = function(node) { - /* true if previous element is a previous verse of a stanza */ - var parent = node.parentNode; - if (!parent || !parent.hasAttribute('x-node') || parent.getAttribute('x-node') != 'strofa') - return false; - - var prev = node.previousSibling; - - while((prev !== null) && (prev.nodeType != ELEMENT_NODE)) { - prev = prev.previousSibling; - } - - return (prev !== null) && prev.hasAttribute('x-verse'); -} - -HTMLSerializer.prototype._nodeIgnored = function(node) { - return node.getAttribute('x-node') == 'wers'; -} - -HTMLSerializer.prototype._ignoredWithWhitespace = function(node) { - while (node.nodeType == ELEMENT_NODE && this._nodeIgnored(node) && node.childNodes.length > 0) - node = node.childNodes[0]; - if (node.nodeType == TEXT_NODE) - return node.nodeValue.match(/^\s/) - else return false; -} - - -HTMLSerializer.prototype.serialize = function(rootElement, stripOuter) -{ - var self = this; - self._prepare(); - - if(!stripOuter) - self._pushElement(rootElement); - else - self._pushChildren(rootElement); - - var text_buffer = ''; - - while(self.stack.length > 0) { - var token = self.stack.pop(); - - if(token.type === ELEM_END) { - self.result += text_buffer; - text_buffer = ''; - if (token.tagName != '') - self.result += ""; - continue; - }; - - if(token.type === NS_END) { - self._unassignNamespace(token.namespace); - continue; - } - - - switch(token.node.nodeType) { - case ELEMENT_NODE: - if(token.node.hasAttribute('x-pass-thru') - || token.node.hasAttribute('data-pass-thru')) { - self._pushChildren(token.node); - break; - } - - if(!token.node.hasAttribute('x-node')) - break; - - var xnode = token.node.getAttribute('x-node'); - - if(xnode === 'out-of-flow-text') { - self._pushChildren(token.node); - break; - } - - if(token.node.hasAttribute('x-verse') && self._verseBefore(token.node)) { - self.result += '/'; - // add whitespace if there's none - if (!(text_buffer.match(/^\s/) || self._ignoredWithWhitespace(token.node))) - self.result += ' '; - } - - self.result += text_buffer; - text_buffer = ''; - self._serializeElement(token.node); - break; - case TEXT_NODE: - self.result += text_buffer; - text_buffer = token.node.nodeValue.replace(/&/g, '&').replace(/'; - break; - }; - }; - self.result += text_buffer; - - return this.result; -} - -/* - * TODO: this doesn't support prefix redefinitions - */ -HTMLSerializer.prototype._unassignNamespace = function(nsData) { - this.nsMap[nsData.uri] = undefined; -}; - -HTMLSerializer.prototype._assignNamespace = function(uri) { - if(uri === null) { - // default namespace - return ({"prefix": "", "uri": "", "fresh": false}); - } - - if(this.nsMap[uri] === undefined) { - // this prefix hasn't been defined yet in current context - var prefix = NAMESPACES[uri]; - - if (prefix === undefined) { // not predefined - prefix = "ns" + this.nsCounter; - this.nsCounter += 1; - } - - this.nsMap[uri] = prefix; - return ({ - "prefix": prefix, - "uri": uri, - "fresh": true - }); - } - - return ({"prefix": this.nsMap[uri], "uri": uri, "fresh": false}); -}; - -HTMLSerializer.prototype._join = function(prefix, name) { - if(!!prefix) - return prefix + ":" + name; - return name; -}; - -HTMLSerializer.prototype._rjoin = function(prefix, name) { - if(!!name) - return prefix + ":" + name; - return prefix; -}; - -HTMLSerializer.prototype._serializeElement = function(node) { - var self = this; - - if (self._nodeIgnored(node)) { - self._pushTagEnd(''); - self._pushChildren(node); - } - else { - var ns = node.getAttribute('x-ns'); - var nsPrefix = null; - var newNamespaces = []; - - var nsData = self._assignNamespace(node.getAttribute('x-ns')); - - if(nsData.fresh) { - newNamespaces.push(nsData); - self.stack.push({ - "type": NS_END, - "namespace": nsData - }); - } - - var tagName = self._join(nsData.prefix, node.getAttribute('x-node')); - - /* retrieve attributes */ - var attributeIDs = []; - for (var i = 0; i < node.attributes.length; i++) { - var attr = node.attributes.item(i); - - // check if name starts with "x-attr-name" - var m = attr.name.match(XATTR_RE); - if (m !== null) - attributeIDs.push(m[1]); - }; - - /* print out */ - - self.result += '<' + tagName; - - $.each(attributeIDs, function() { - var nsData = self._assignNamespace(node.getAttribute('x-attr-ns-'+this)); - - if(nsData.fresh) { - newNamespaces.push(nsData); - self.stack.push({ - "type": NS_END, - "namespace": nsData - }); - }; - - self.result += ' ' + self._join(nsData.prefix, node.getAttribute('x-attr-name-'+this)); - self.result += '="'+node.getAttribute('x-attr-value-'+this) +'"'; - }); - - /* print new namespace declarations */ - $.each(newNamespaces, function() { - self.result += " " + self._rjoin("xmlns", this.prefix); - self.result += '="' + this.uri + '"'; - }); - - if (node.childNodes.length > 0) { - self.result += ">"; - self._pushTagEnd(tagName); - self._pushChildren(node); - } - else { - self.result += "/>"; - }; - } -}; - -function html2text(params) { - try { - var s = new HTMLSerializer(); - params.success( s.serialize(params.element, params.stripOuter) ); - } catch(e) { - params.error("Nie udało się zserializować tekstu:" + e) - } -} diff --git a/redakcja/static/js/wiki_img/base.js b/redakcja/static/js/wiki_img/base.js deleted file mode 100644 index ffe5a01d..00000000 --- a/redakcja/static/js/wiki_img/base.js +++ /dev/null @@ -1,329 +0,0 @@ -(function($) -{ - var noop = function() { }; - - $.wiki = { - perspectives: {}, - cls: {}, - state: { - "version": 1, - "perspectives": { - "CodeMirrorPerspective": {} - } - } - }; - - $.wiki.loadConfig = function() { - if(!window.localStorage) - return; - - try { - var value = window.localStorage.getItem(CurrentDocument.id) || "{}"; - var config = JSON.parse(value); - - if (config.version == $.wiki.state.version) { - $.wiki.state.perspectives = $.extend($.wiki.state.perspectives, config.perspectives); - } - } catch(e) { - console.log("Failed to load config, using default."); - } - - console.log("Loaded:", $.wiki.state, $.wiki.state.version); - }; - - $(window).bind('unload', function() { - if(window.localStorage) - window.localStorage.setItem(CurrentDocument.id, JSON.stringify($.wiki.state)); - }) - - - $.wiki.activePerspective = function() { - return this.perspectives[$("#tabs li.active").attr('id')]; - }; - - $.wiki.exitContext = function() { - var ap = this.activePerspective(); - if(ap) ap.onExit(); - return ap; - }; - - $.wiki.enterContext = function(ap) { - if(ap) ap.onEnter(); - }; - - $.wiki.isDirty = function() { - var ap = this.activePerspective(); - return (!!CurrentDocument && CurrentDocument.has_local_changes) || ap.dirty(); - }; - - $.wiki.newTab = function(doc, title, klass) { - var base_id = 'id' + Math.floor(Math.random()* 5000000000); - var id = (''+klass)+'_' + base_id; - var $tab = $('
  • ' - + title + '
  • '); - var $view = $('
    '); - - this.perspectives[id] = new $.wiki[klass]({ - doc: doc, - id: id, - base_id: base_id, - }); - - $('#tabs').append($tab); - $view.hide().appendTo('#editor'); - return { - tab: $tab[0], - view: $view[0], - }; - }; - - $.wiki.initTab = function(options) { - var klass = $(options.tab).attr('data-ui-jsclass'); - - return new $.wiki[klass]({ - doc: options.doc, - id: $(options.tab).attr('id'), - callback: function() { - $.wiki.perspectives[this.perspective_id] = this; - if(options.callback) - options.callback.call(this); - } - }); - }; - - $.wiki.perspectiveForTab = function(tab) { // element or id - return this.perspectives[ $(tab).attr('id')]; - } - - $.wiki.switchToTab = function(tab){ - var self = this; - var $tab = $(tab); - - if($tab.length != 1) - $tab = $(DEFAULT_PERSPECTIVE); - - var $old = $tab.closest('.tabs').find('.active'); - - $old.each(function(){ - $(this).removeClass('active'); - self.perspectives[$(this).attr('id')].onExit(); - $('#' + $(this).attr('data-ui-related')).hide(); - }); - - /* show new */ - $tab.addClass('active'); - $('#' + $tab.attr('data-ui-related')).show(); - - console.log($tab); - console.log($.wiki.perspectives); - - $.wiki.perspectives[$tab.attr('id')].onEnter(); - }; - - /* - * Basic perspective. - */ - $.wiki.Perspective = function(options) { - if(!options) return; - - this.doc = options.doc; - if (options.id) { - this.perspective_id = options.id; - } - else { - this.perspective_id = ''; - } - - if(options.callback) - options.callback.call(this); - }; - - $.wiki.Perspective.prototype.config = function() { - return $.wiki.state.perspectives[this.perspective_id]; - } - - $.wiki.Perspective.prototype.toString = function() { - return this.perspective_id; - }; - - $.wiki.Perspective.prototype.dirty = function() { - return true; - }; - - $.wiki.Perspective.prototype.onEnter = function () { - // called when perspective in initialized - if (!this.noupdate_hash_onenter) { - document.location.hash = '#' + this.perspective_id; - } - }; - - $.wiki.Perspective.prototype.onExit = function () { - // called when user switches to another perspective - if (!this.noupdate_hash_onenter) { - document.location.hash = ''; - } - }; - - $.wiki.Perspective.prototype.destroy = function() { - // pass - }; - - $.wiki.Perspective.prototype.freezeState = function () { - // free UI state (don't store data here) - }; - - $.wiki.Perspective.prototype.unfreezeState = function (frozenState) { - // restore UI state - }; - - /* - * Stub rendering (used in generating history) - */ - $.wiki.renderStub = function(params) - { - params = $.extend({ 'filters': {} }, params); - var $elem = params.stub.clone(); - $elem.removeClass('row-stub'); - params.container.append($elem); - - $('*[data-stub-value]', $elem).each(function() { - var $this = $(this); - var field = $this.attr('data-stub-value'); - - var value = params.data[field]; - - if(params.filters[field]) - value = params.filters[field](value); - - if(value === null || value === undefined) return; - - if(!$this.attr('data-stub-target')) { - $this.text(value); - } - else { - $this.attr($this.attr('data-stub-target'), value); - $this.removeAttr('data-stub-target'); - $this.removeAttr('data-stub-value'); - } - }); - - $elem.show(); - return $elem; - }; - - /* - * Dialogs - */ - function GenericDialog(element) { - if(!element) return; - - var self = this; - - self.$elem = $(element); - - if(!self.$elem.attr('data-ui-initialized')) { - console.log("Initializing dialog", this); - self.initialize(); - self.$elem.attr('data-ui-initialized', true); - } - - self.show(); - }; - - GenericDialog.prototype = { - - /* - * Steps to follow when the dialog in first loaded on page. - */ - initialize: function(){ - var self = this; - - /* bind buttons */ - $('button[data-ui-action]', self.$elem).click(function(event) { - event.preventDefault(); - - var action = $(this).attr('data-ui-action'); - console.log("Button pressed, action: ", action); - - try { - self[action + "Action"].call(self); - } catch(e) { - console.log("Action failed:", e); - // always hide on cancel - if(action == 'cancel') - self.hide(); - } - }); - }, - - /* - * Prepare dialog for user. Clear any unnessary data. - */ - show: function() { - $.blockUI({ - message: this.$elem, - css: { - 'top': '25%', - 'left': '25%', - 'width': '50%' - } - }); - }, - - hide: function(){ - $.unblockUI(); - }, - - cancelAction: function() { - this.hide(); - }, - - doneAction: function() { - this.hide(); - }, - - clearForm: function() { - $("*[data-ui-error-for]", this.$elem).text(''); - }, - - reportErrors: function(errors) { - var global = $("*[data-ui-error-for='__all__']", this.$elem); - var unassigned = []; - - for (var field_name in errors) - { - var span = $("*[data-ui-error-for='"+field_name+"']", this.$elem); - - if(!span.length) { - unassigned.push(field_name); - continue; - } - - span.text(errors[field_name].join(' ')); - } - - if(unassigned.length > 0) - global.text( global.text() + 'W formularzu wystąpiły błędy'); - } - }; - - $.wiki.cls.GenericDialog = GenericDialog; - - $.wiki.showDialog = function(selector, options) { - var elem = $(selector); - - if(elem.length != 1) { - console.log("Failed to show dialog:", selector, elem); - return false; - } - - try { - var klass = elem.attr('data-ui-jsclass'); - return new $.wiki.cls[klass](elem, options); - } catch(e) { - console.log("Failed to show dialog", selector, klass, e); - return false; - } - }; - -})(jQuery); diff --git a/redakcja/static/js/wiki_img/loader.js b/redakcja/static/js/wiki_img/loader.js deleted file mode 100644 index c3fe03e0..00000000 --- a/redakcja/static/js/wiki_img/loader.js +++ /dev/null @@ -1,137 +0,0 @@ -if (!window.console) { - window.console = { - log: function(){ - } - } -} - -DEFAULT_PERSPECTIVE = "#MotifsPerspective"; - -$(function() -{ - var tabs = $('ol#tabs li'); - var gallery = null; - CurrentDocument = new $.wikiapi.WikiDocument("document-meta"); - - $.blockUI.defaults.baseZ = 10000; - - function initialize() - { - $(document).keydown(function(event) { - console.log("Received key:", event); - }); - - /* The save button */ - $('#save-button').click(function(event){ - event.preventDefault(); - $.wiki.showDialog('#save_dialog'); - }); - - $('.editor').hide(); - - /* - * TABS - */ - $('.tabs li').live('click', function(event, callback) { - event.preventDefault(); - $.wiki.switchToTab(this); - }); - - $('#tabs li > .tabclose').live('click', function(event, callback) { - var $tab = $(this).parent(); - - if($tab.is('.active')) - $.wiki.switchToTab(DEFAULT_PERSPECTIVE); - - var p = $.wiki.perspectiveForTab($tab); - p.destroy(); - - return false; - }); - - - /*$(window).resize(function(){ - $('iframe').height($(window).height() - $('#tabs').outerHeight() - $('#source-editor .toolbar').outerHeight()); - }); - - $(window).resize();*/ - - /*$('.vsplitbar').toggle( - function() { - $.wiki.state.perspectives.ScanGalleryPerspective.show = true; - $('#sidebar').show(); - $('.vsplitbar').css('right', 480).addClass('active'); - $('#editor .editor').css('right', 510); - $(window).resize(); - $.wiki.perspectiveForTab('#tabs-right .active').onEnter(); - }, - function() { - var active_right = $.wiki.perspectiveForTab('#tabs-right .active'); - $.wiki.state.perspectives.ScanGalleryPerspective.show = false; - $('#sidebar').hide(); - $('.vsplitbar').css('right', 0).removeClass('active'); - $(".vsplitbar-title").html("↑ " + active_right.vsplitbar + " ↑"); - $('#editor .editor').css('right', 30); - $(window).resize(); - active_right.onExit(); - } - );*/ - - window.onbeforeunload = function(e) { - if($.wiki.isDirty()) { - e.returnValue = "Na stronie mogą być nie zapisane zmiany."; - return "Na stronie mogą być nie zapisane zmiany."; - }; - }; - - console.log("Fetching document's text"); - - $(document).bind('wlapi_document_changed', function(event, doc) { - try { - $('#document-revision').text(doc.revision); - } catch(e) { - console.log("Failed handler", e); - } - }); - - CurrentDocument.fetch({ - success: function(){ - console.log("Fetch success"); - $('#loading-overlay').fadeOut(); - var active_tab = document.location.hash || DEFAULT_PERSPECTIVE; - - console.log("Initial tab is:", active_tab) - $.wiki.switchToTab(active_tab); - }, - failure: function() { - $('#loading-overlay').fadeOut(); - alert("FAILURE"); - } - }); - }; /* end of initialize() */ - - - /* Load configuration */ - $.wiki.loadConfig(); - - var initAll = function(a, f) { - if (a.length == 0) return f(); - - $.wiki.initTab({ - tab: a.pop(), - doc: CurrentDocument, - callback: function(){ - initAll(a, f); - } - }); - }; - - - /* - * Initialize all perspectives - */ - initAll( $.makeArray($('.tabs li')), initialize); - console.log(location.hash); -}); - - diff --git a/redakcja/static/js/wiki_img/loader_readonly.js b/redakcja/static/js/wiki_img/loader_readonly.js deleted file mode 100755 index 99e5ad0e..00000000 --- a/redakcja/static/js/wiki_img/loader_readonly.js +++ /dev/null @@ -1,93 +0,0 @@ -if (!window.console) { - window.console = { - log: function(){ - } - } -} - - -DEFAULT_PERSPECTIVE = "#MotifsPerspective"; - - -$(function() -{ - var tabs = $('ol#tabs li'); - var gallery = null; - - CurrentDocument = new $.wikiapi.WikiDocument("document-meta"); - $.blockUI.defaults.baseZ = 10000; - - function initialize() - { - $('.editor').hide(); - - /* - * TABS - */ - $('#tabs li').live('click', function(event, callback) { - event.preventDefault(); - $.wiki.switchToTab(this); - }); - - $('#tabs li > .tabclose').live('click', function(event, callback) { - var $tab = $(this).parent(); - - if($tab.is('.active')) - $.wiki.switchToTab(DEFAULT_PERSPECTIVE); - - var p = $.wiki.perspectiveForTab($tab); - p.destroy(); - return false; - }); -/* - $(window).resize(function(){ - $('iframe').height($(window).height() - $('#tabs').outerHeight() - $('#source-editor .toolbar').outerHeight()); - }); - */ - - $(document).bind('wlapi_document_changed', function(event, doc) { - try { - $('#document-revision').text(doc.revision); - } catch(e) { - console.log("Failed handler", e); - } - }); - - CurrentDocument.fetch({ - success: function(){ - console.log("Fetch success"); - $('#loading-overlay').fadeOut(); - var active_tab = document.location.hash || DEFAULT_PERSPECTIVE; - - console.log("Initial tab is:", active_tab) - $.wiki.switchToTab(active_tab); - }, - failure: function() { - $('#loading-overlay').fadeOut(); - alert("FAILURE"); - } - }); - }; /* end of initialize() */ - - /* Load configuration */ - $.wiki.loadConfig(); - - var initAll = function(a, f) { - if (a.length == 0) return f(); - - $.wiki.initTab({ - tab: a.pop(), - doc: CurrentDocument, - callback: function(){ - initAll(a, f); - } - }); - }; - - - /* - * Initialize all perspectives - */ - initAll( $.makeArray($('ol#tabs li')), initialize); - console.log(location.hash); -}); diff --git a/redakcja/static/js/wiki_img/view_editor_motifs.js b/redakcja/static/js/wiki_img/view_editor_motifs.js deleted file mode 100644 index ad60c229..00000000 --- a/redakcja/static/js/wiki_img/view_editor_motifs.js +++ /dev/null @@ -1,158 +0,0 @@ -(function($){ - - function MotifsPerspective(options){ - - var old_callback = options.callback; - - options.callback = function(){ - var self = this; - - self.$tag_name = $('#motifs-editor .tag-name'); - self.$toolbar = $('#motifs-editor .toolbar'); - self.$scrolled = $('#motifs-editor .scrolled'); - withThemes(function(canonThemes){ - self.$tag_name.autocomplete(canonThemes, { - autoFill: true, - multiple: true, - selectFirst: true, - highlight: false - }); - }) - - self.$objects_list = $('#motifs-editor .objects-list'); - - self.x1 = null; - self.x2 = null; - self.y1 = null; - self.y2 = null; - - if (!CurrentDocument.readonly) { - self.ias = $('#motifs-editor img.area-selectable').imgAreaSelect({ handles: true, onSelectEnd: self._fillCoords(self), instance: true }); - $('#motifs-editor .add').click(self._addObject(self)); - - $('.delete', self.$objects_list).live('click', function() { - $(this).prev().trigger('click'); - if (window.confirm("Czy na pewno chcesz usunąć ten motyw?")) { - $(this).prev().remove(); - $(this).remove(); - self._refreshLayout(); - } - self._resetSelection(); - return false; - }); - } - - $('.image-object', self.$objects_list).live('click', function(){ - $('.active', self.$objects_list).removeClass('active'); - $(this).addClass('active'); - var coords = $(this).data('coords'); - if (coords) { - self.ias.setSelection.apply(self.ias, coords); - self.ias.setOptions({ show: true }); - } - else { - self._resetSelection(); - } - }); - - old_callback.call(this); - }; - - $.wiki.Perspective.call(this, options); - }; - - MotifsPerspective.prototype = new $.wiki.Perspective(); - - MotifsPerspective.prototype.freezeState = function(){ - - }; - - MotifsPerspective.prototype._resetSelection = function() { - var self = this; - self.x1 = self.x2 = self.y1 = self.y2 = null; - self.ias.setOptions({ hide: true }); - } - - MotifsPerspective.prototype._refreshLayout = function() { - this.$scrolled.css({top: this.$toolbar.height()}); - }; - - MotifsPerspective.prototype._push = function(name, x1, y1, x2, y2) { - var $e = $('') - $e.text(name); - if (x1 !== null) - $e.data('coords', [x1, y1, x2, y2]); - this.$objects_list.append($e); - this.$objects_list.append('(x) '); - this._refreshLayout(); - } - - - MotifsPerspective.prototype._addObject = function(self) { - return function() { - outputs = []; - chunks = self.$tag_name.val().split(','); - for (i in chunks) { - item = chunks[i].trim(); - if (item == '') - continue; - outputs.push(item.trim()); - } - output = outputs.join(', '); - - self._push(output, self.x1, self.y1, self.x2, self.y2); - self._resetSelection(); - } - } - - MotifsPerspective.prototype._fillCoords = function(self) { - return function(img, selection) { - $('.active', self.$objects_list).removeClass('active'); - if (selection.x1 != selection.x2 && selection.y1 != selection.y2) { - self.x1 = selection.x1; - self.x2 = selection.x2; - self.y1 = selection.y1; - self.y2 = selection.y2; - } - else { - self.x1 = self.x2 = self.y1 = self.y2 = null; - } - } - } - - MotifsPerspective.prototype.onEnter = function(success, failure){ - var self = this; - this.$objects_list.children().remove(); - - $.each(this.doc.getImageItems('theme'), function(i, e) { - self._push.apply(self, e); - }); - - if (this.x1 !== null) - this.ias.setOptions({enable: true, show: true}); - else - this.ias.setOptions({enable: true}); - - $.wiki.Perspective.prototype.onEnter.call(this); - - }; - - MotifsPerspective.prototype.onExit = function(success, failure){ - var self = this; - var motifs = []; - this.$objects_list.children(".image-object").each(function(i, e) { - var args = $(e).data('coords'); - if (!args) - args = [null, null, null, null]; - args.unshift($(e).text()); - motifs.push(args); - }) - self.doc.setImageItems('theme', motifs); - - this.ias.setOptions({disable: true, hide: true}); - - }; - - $.wiki.MotifsPerspective = MotifsPerspective; - -})(jQuery); diff --git a/redakcja/static/js/wiki_img/view_editor_objects.js b/redakcja/static/js/wiki_img/view_editor_objects.js deleted file mode 100644 index b0cf2a9d..00000000 --- a/redakcja/static/js/wiki_img/view_editor_objects.js +++ /dev/null @@ -1,149 +0,0 @@ -(function($){ - - function ObjectsPerspective(options){ - - var old_callback = options.callback; - - options.callback = function(){ - var self = this; - - self.$tag_name = $('#objects-editor .tag-name'); - self.$toolbar = $('#objects-editor .toolbar'); - self.$scrolled = $('#objects-editor .scrolled'); - self.$objects_list = $('#objects-editor .objects-list'); - - self.x1 = null; - self.x2 = null; - self.y1 = null; - self.y2 = null; - - if (!CurrentDocument.readonly) { - self.ias = $('#objects-editor img.area-selectable').imgAreaSelect({ handles: true, onSelectEnd: self._fillCoords(self), instance: true }); - $('#objects-editor .add').click(self._addObject(self)); - - $('.delete', self.$objects_list).live('click', function() { - $(this).prev().trigger('click'); - if (window.confirm("Czy na pewno chcesz usunąć ten obiekt?")) { - $(this).prev().remove(); - $(this).remove(); - self._refreshLayout(); - } - self._resetSelection(); - return false; - }); - } - - $('.image-object', self.$objects_list).live('click', function(){ - $('.active', self.$objects_list).removeClass('active'); - $(this).addClass('active'); - var coords = $(this).data('coords'); - if (coords) { - self.ias.setSelection.apply(self.ias, coords); - self.ias.setOptions({ show: true }); - } - else { - self._resetSelection(); - } - }); - - old_callback.call(this); - }; - - $.wiki.Perspective.call(this, options); - }; - - ObjectsPerspective.prototype = new $.wiki.Perspective(); - - ObjectsPerspective.prototype.freezeState = function(){ - - }; - - ObjectsPerspective.prototype._resetSelection = function() { - var self = this; - self.x1 = self.x2 = self.y1 = self.y2 = null; - self.ias.setOptions({ hide: true }); - } - - ObjectsPerspective.prototype._refreshLayout = function() { - this.$scrolled.css({top: this.$toolbar.height()}); - }; - - ObjectsPerspective.prototype._push = function(name, x1, y1, x2, y2) { - var $e = $('') - $e.text(name); - if (x1 !== null) - $e.data('coords', [x1, y1, x2, y2]); - this.$objects_list.append($e); - this.$objects_list.append('(x) '); - this._refreshLayout(); - } - - - ObjectsPerspective.prototype._addObject = function(self) { - return function() { - outputs = []; - chunks = self.$tag_name.val().split(','); - for (i in chunks) { - item = chunks[i].trim(); - if (item == '') - continue; - outputs.push(item.trim()); - } - output = outputs.join(', '); - - self._push(output, self.x1, self.y1, self.x2, self.y2); - self._resetSelection(); - } - } - - ObjectsPerspective.prototype._fillCoords = function(self) { - return function(img, selection) { - $('.active', self.$objects_list).removeClass('active'); - if (selection.x1 != selection.x2 && selection.y1 != selection.y2) { - self.x1 = selection.x1; - self.x2 = selection.x2; - self.y1 = selection.y1; - self.y2 = selection.y2; - } - else { - self.x1 = self.x2 = self.y1 = self.y2 = null; - } - } - } - - ObjectsPerspective.prototype.onEnter = function(success, failure){ - var self = this; - this.$objects_list.children().remove(); - - $.each(this.doc.getImageItems('object'), function(i, e) { - self._push.apply(self, e); - }); - - if (this.x1 !== null) - this.ias.setOptions({enable: true, show: true}); - else - this.ias.setOptions({enable: true}); - - $.wiki.Perspective.prototype.onEnter.call(this); - - }; - - ObjectsPerspective.prototype.onExit = function(success, failure){ - var self = this; - var objects = []; - this.$objects_list.children(".image-object").each(function(i, e) { - var args = $(e).data('coords'); - if (!args) - args = [null, null, null, null]; - args.unshift($(e).text()); - objects.push(args); - }) - self.doc.setImageItems('object', objects); - - this.ias.setOptions({disable: true, hide: true}); - - }; - - $.wiki.ObjectsPerspective = ObjectsPerspective; - -})(jQuery); diff --git a/redakcja/static/js/wiki_img/wikiapi.js b/redakcja/static/js/wiki_img/wikiapi.js deleted file mode 100644 index 377e4f94..00000000 --- a/redakcja/static/js/wiki_img/wikiapi.js +++ /dev/null @@ -1,418 +0,0 @@ -(function($) { - $.wikiapi = {}; - var noop = function() { - }; - var noops = { - success: noop, - failure: noop - }; - /* - * Return absolute reverse path of given named view. (at least he have it - * hard-coded in one place) - * - * TODO: think of a way, not to hard-code it here ;) - * - */ - function reverse() { - var vname = arguments[0]; - var base_path = "/images"; - - if (vname == "ajax_document_text") - return base_path + "/text/" + arguments[1] + "/"; - - - if (vname == "ajax_document_revert") { - return base_path + "/revert/" + arguments[1] + '/'; - } - - if (vname == "ajax_document_history") { - return base_path + "/history/" + arguments[1] + '/'; - } - - if (vname == "ajax_document_diff") - return base_path + "/diff/" + arguments[1] + '/'; - - if (vname == "ajax_document_pubmark") - return base_path + "/pubmark/" + arguments[1] + '/'; - - console.log("Couldn't reverse match:", vname); - return "/404.html"; - }; - - /* - * Document Abstraction - */ - function WikiDocument(element_id) { - var meta = $('#' + element_id); - this.id = meta.attr('data-object-id'); - - this.revision = $("*[data-key='revision']", meta).text(); - this.readonly = !!$("*[data-key='readonly']", meta).text(); - - var diff = $("*[data-key='diff']", meta).text(); - if (diff) { - 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.text = null; - this.has_local_changes = false; - this._lock = -1; - this._context_lock = -1; - this._lock_count = 0; - }; - - WikiDocument.prototype.triggerDocumentChanged = function() { - $(document).trigger('wlapi_document_changed', this); - }; - /* - * Fetch text of this document. - */ - WikiDocument.prototype.fetch = function(params) { - params = $.extend({}, noops, params); - var self = this; - $.ajax({ - method: "GET", - url: reverse("ajax_document_text", self.id), - data: {"revision": self.revision}, - dataType: 'json', - success: function(data) { - var changed = false; - - if (self.text === null || self.revision !== data.revision) { - self.text = data.text; - if (self.text === '') { - self.text = ''; - } - self.revision = data.revision; -// self.commit = data.commit; - changed = true; - self.triggerDocumentChanged(); - }; - - self.has_local_changes = false; - params['success'](self, changed); - }, - error: function() { - params['failure'](self, "Nie udało się wczytać treści dokumentu."); - } - }); - }; - /* - * Fetch history of this document. - * - * from - First revision to fetch (default = 0) upto - Last revision to - * fetch (default = tip) - * - */ - WikiDocument.prototype.fetchHistory = function(params) { - /* this doesn't modify anything, so no locks */ - params = $.extend({}, noops, params); - var self = this; - $.ajax({ - method: "GET", - url: reverse("ajax_document_history", self.id), - dataType: 'json', - data: { - "from": params['from'], - "upto": params['upto'] - }, - success: function(data) { - params['success'](self, data); - }, - error: function() { - params['failure'](self, "Nie udało się wczytać historii dokumentu."); - } - }); - }; - WikiDocument.prototype.fetchDiff = function(params) { - /* this doesn't modify anything, so no locks */ - var self = this; - params = $.extend({ - 'from': self.revision, - 'to': self.revision - }, noops, params); - $.ajax({ - method: "GET", - url: reverse("ajax_document_diff", self.id), - dataType: 'html', - data: { - "from": params['from'], - "to": params['to'] - }, - success: function(data) { - params['success'](self, data); - }, - error: function() { - params['failure'](self, "Nie udało się wczytać porównania wersji."); - } - }); - }; - - /* - * Set document's text - */ - WikiDocument.prototype.setText = function(text) { - this.text = text; - this.has_local_changes = true; - }; - - /* - * Save text back to the server - */ - WikiDocument.prototype.save = function(params) { - params = $.extend({}, noops, params); - var self = this; - - if (!self.has_local_changes) { - console.log("Abort: no changes."); - return params['success'](self, false, "Nie ma zmian do zapisania."); - }; - - // Serialize form to dictionary - var data = {}; - $.each(params['form'].serializeArray(), function() { - data[this.name] = this.value; - }); - - data['textsave-text'] = self.text; - - $.ajax({ - url: reverse("ajax_document_text", self.id), - type: "POST", - dataType: "json", - data: data, - success: function(data) { - var changed = false; - - $('#header').removeClass('saving'); - - if (data.text) { - self.text = data.text; - self.revision = data.revision; -// self.commit = data.commit; - changed = true; - self.triggerDocumentChanged(); - }; - - params['success'](self, changed, ((changed && "Udało się zapisać :)") || "Twoja wersja i serwera jest identyczna")); - }, - error: function(xhr) { - if ($('#header').hasClass('saving')) { - $('#header').removeClass('saving'); - $.blockUI({ - message: "

    Nie udało się zapisać zmian.

    " - }) - } - else { - try { - params['failure'](self, $.parseJSON(xhr.responseText)); - } - catch (e) { - params['failure'](self, { - "__message": "

    Nie udało się zapisać - błąd serwera.

    " - }); - }; - } - - } - }); - - $('#save-hide').click(function(){ - $('#header').addClass('saving'); - $.unblockUI(); - $.wiki.blocking.unblock(); - }); - }; /* end of save() */ - - WikiDocument.prototype.revertToVersion = function(params) { - var self = this; - params = $.extend({}, noops, params); - - if (params.revision >= this.revision) { - params.failure(self, 'Proszę wybrać rewizję starszą niż aktualna.'); - return; - } - - // Serialize form to dictionary - var data = {}; - $.each(params['form'].serializeArray(), function() { - data[this.name] = this.value; - }); - - $.ajax({ - url: reverse("ajax_document_revert", self.id), - type: "POST", - dataType: "json", - data: data, - success: function(data) { - if (data.text) { - self.text = data.text; - self.revision = data.revision; - self.gallery = data.gallery; - self.triggerDocumentChanged(); - - params.success(self, "Udało się przywrócić wersję :)"); - } - else { - params.failure(self, "Przywracana wersja identyczna z aktualną. Anulowano przywracanie."); - } - }, - error: function(xhr) { - params.failure(self, "Nie udało się przywrócić wersji - błąd serwera."); - } - }); - }; - - 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."] - }); - }; - }; - } - }); - }; - - - - WikiDocument.prototype.getImageItems = function(tag) { - var self = this; - - var parser = new DOMParser(); - var doc = parser.parseFromString(self.text, 'text/xml'); - var error = $('parsererror', doc); - - if (error.length != 0) { - return null; - } - - var a = []; - $('sem[type="'+tag+'"]', doc).each(function(i, e) { - var $e = $(e); - var $div = $e.children().first() - var value = $e.attr(tag); - $e.find('div').each(function(i, div) { - var $div = $(div); - switch ($div.attr('type')) { - case 'rect': - a.push([ - value, - $div.attr('x1'), - $div.attr('y1'), - $div.attr('x2'), - $div.attr('y2') - ]); - break; - case 'whole': - a.push([ - value, - null, null, null, null - ]); - break - } - }); - }); - - return a; - } - - WikiDocument.prototype.setImageItems = function(tag, items) { - var self = this; - - var parser = new DOMParser(); - var doc = parser.parseFromString(self.text, 'text/xml'); - var serializer = new XMLSerializer(); - var error = $('parsererror', doc); - - if (error.length != 0) { - return null; - } - - $('sem[type="'+tag+'"]', doc).remove(); - $root = $(doc.firstChild); - $.each(items, function(i, e) { - var $sem = $(doc.createElement("sem")); - $sem.attr('type', tag); - $sem.attr(tag, e[0]); - $div = $(doc.createElement("div")); - if (e[1]) { - $div.attr('type', 'rect'); - $div.attr('x1', e[1]); - $div.attr('y1', e[2]); - $div.attr('x2', e[3]); - $div.attr('y2', e[4]); - } - else { - $div.attr('type', 'whole'); - } - $sem.append($div); - $root.append($sem); - }); - self.setText(serializer.serializeToString(doc)); - } - - - $.wikiapi.WikiDocument = WikiDocument; -})(jQuery); - - - -// Wykonuje block z załadowanymi kanonicznymi motywami -function withThemes(code_block, onError) -{ - if (typeof withThemes.canon == 'undefined') { - $.ajax({ - url: '/editor/themes', - dataType: 'text', - success: function(data) { - withThemes.canon = data.split('\n'); - code_block(withThemes.canon); - }, - error: function() { - withThemes.canon = null; - code_block(withThemes.canon); - } - }) - } - else { - code_block(withThemes.canon); - } -} - diff --git a/redakcja/static/xsl/wl2html_client.xsl b/redakcja/static/xsl/wl2html_client.xsl deleted file mode 100644 index 784acb7e..00000000 --- a/redakcja/static/xsl/wl2html_client.xsl +++ /dev/null @@ -1,867 +0,0 @@ - - - - - - - - - - - - - - - - -
    - - - - -
    -
    - - - - - - - -
    - - - - -
    -
    - - - -
    - - - - -
    -
    - - - -
    - - - - -
    -
    - - - -
    - - - - -
    -
    - - - - - - -

    - - - - - -

    -
    - - - - -

    - - - - - -

    -
    - - - - -

    - - - - - -

    -
    - - - - -

    - - - - - -

    -
    - - - - - -
    - - - - -
    -
    - - - -
    - - - - -
    -
    - - - - -
    - - - - -
    -
    - - - - -
    - - - - -
    -
    - - - - -
    -
    - - - - -
    - - - -
    -
    - - - - - - - - - - -

    - - - - - -

    -
    - - - - - - - - -
    - - - - -
    -
    - - - -

    - - - - - -

    -
    - - - -

    - - - - - -

    -
    - - - - -

    - - - - - -

    -
    - - - - - - - -

    - - - - - -

    -
    - - - - -

    - - - - - -

    -
    - - - - -

    - - - - - -

    -
    - - - - - -

    - - - - - -

    -
    - - - -

    - - - - - -

    -
    - - - - - -

    - - - - - -

    -
    - - - -

    - - - - - -

    -
    - - - - - -
    - - - - -
    -
    - - - -
    - - - - -
    -
    - - - -
    - - - - -
    -
    - - - -
    - - - - -
    -
    - - - -
    - - - - -
    -
    - - - -
    - - - - -
    -
    - - - - - -

    - - - - - -

    -
    - - - -

    - - - - - -

    -
    - - - -

    - - - - - -

    -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - - - - - - - - - -

    - - - -

    -
    - - - - - - -
    -
    - - - - -

    - - - - -

    - -
    - - / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - -

    -
    - - - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - unknown-tag - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    diff --git a/redakcja/templates/404.html b/redakcja/templates/404.html deleted file mode 100644 index 11edc9d5..00000000 --- a/redakcja/templates/404.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "catalogue/base.html" %} -{% load i18n %} - -{% block titleextra %}{% trans "Page not found" %}{% endblock %} - -{% block content %} - -

    {% trans "Page not found" %}

    - -{% blocktrans with p=request.build_absolute_uri %}The page you're trying -to reach ({{p}}) could not be found. If it's a document, try -looking for it on the document list.{% endblocktrans %} - -

    -{% blocktrans with m="mailto:radoslaw.czajka@nowoczesnapolska.org.pl" %}If you -still can't find what you're looking for, please -contact the administrator.{% endblocktrans %} -

    - -{% url "catalogue_user" as m %} -

    -{% blocktrans %}If you're coming from Redmine, please note that -work is no longer managed there. -Go to the document list -or to your page instead.{% endblocktrans %} -

    - - -{% endblock content %} diff --git a/redakcja/templates/500.html b/redakcja/templates/500.html deleted file mode 100644 index bd7404f3..00000000 --- a/redakcja/templates/500.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "error_base.html" %} - -{% block "content" %} - -

    Błąd po stronie serwera.

    - -

    Niestety nasz serwer WWW nie był w stanie dostarczyć Ci strony o którą prosisz.

    -

    Serdecznie przepraszamy.

    -

    Administrator został już powiadomiony o błędzie, ale jeśli chciałbyś -przekazać nam więcej informacji na temat błędu, napisz na nasz adres.

    - - -{% endblock %} diff --git a/redakcja/templates/503.html b/redakcja/templates/503.html deleted file mode 100644 index 1c1d39a1..00000000 --- a/redakcja/templates/503.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "error_base.html" %} - -{% block "content" %} - -

    Serwis tymczasowo niedostępny

    - -

    -Platfroma redakcyjna serwisu Wolne Lektury jest -tymczasowo niedostępna z powodu prac administracyjnych. -

    - -

    Prosimy o wyrozumiałość i ponowne odwiedziny.

    - -{% endblock "content" %} diff --git a/redakcja/templates/base.html b/redakcja/templates/base.html deleted file mode 100644 index fc609954..00000000 --- a/redakcja/templates/base.html +++ /dev/null @@ -1,28 +0,0 @@ - -{% load i18n %} - - - - - {% block title %}{% block titleextra %}{% endblock titleextra %} :: - {% trans "Platforma Redakcyjna" %}{% endblock title %} - {% block extrahead %} - {% endblock %} - - - -
    -
    - Loading -

    {% trans "Loading" %}

    -
    -
    - -
    - -
    {% block maincontent %} {% endblock %}
    -
    - - {% block extrabody %}{% endblock %} - - diff --git a/redakcja/templates/error_base.html b/redakcja/templates/error_base.html deleted file mode 100755 index 58784dc6..00000000 --- a/redakcja/templates/error_base.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - Platforma Redakcyjna - - - -
    - - - -
    -
    - -
    -{% block "content" %} -{% endblock %} -
    - - - - diff --git a/redakcja/templates/pagination/pagination.html b/redakcja/templates/pagination/pagination.html deleted file mode 100755 index fe566a86..00000000 --- a/redakcja/templates/pagination/pagination.html +++ /dev/null @@ -1,26 +0,0 @@ -{% if is_paginated %} -{% load i18n %} - -{% endif %} diff --git a/redakcja/templates/registration/head_login.html b/redakcja/templates/registration/head_login.html deleted file mode 100644 index 2a8fd3bd..00000000 --- a/redakcja/templates/registration/head_login.html +++ /dev/null @@ -1,16 +0,0 @@ -{% load i18n %} - -{% if user.is_authenticated %} -{{ user.username }} | - -{% if user.is_staff %} - {% trans "Admin" %} | -{% endif %} - -{% trans "Log Out" %} -{% else %} -{% url "login" as login_url %} -{% ifnotequal login_url request.path %} - {% trans "Log In" %} -{% endifnotequal %} -{% endif %} diff --git a/redakcja/templates/registration/login.html b/redakcja/templates/registration/login.html deleted file mode 100644 index adbef3cb..00000000 --- a/redakcja/templates/registration/login.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "catalogue/base.html" %} - -{% block titleextra %}Logowanie{% endblock %} -{% block subtitle %} - Logowanie {% endblock subtitle %} - -{% block content %} - -
    -
    -{% csrf_token %} -{{ form.as_p }} -

    - -
    -
    - -{% endblock content %} diff --git a/redakcja/urls.py b/redakcja/urls.py deleted file mode 100644 index c0629fac..00000000 --- a/redakcja/urls.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.conf.urls import include, patterns, url -from django.contrib import admin -from django.conf import settings -from django.conf.urls.static import static -from django.contrib.staticfiles.urls import staticfiles_urlpatterns -from django.views.generic import RedirectView - - -admin.autodiscover() - -urlpatterns = patterns('', - # Auth - url(r'^accounts/login/$', 'django_cas.views.login', name='login'), - url(r'^accounts/logout/$', 'django_cas.views.logout', name='logout'), - url(r'^admin/login/$', 'django_cas.views.login', name='login'), - url(r'^admin/logout/$', 'django_cas.views.logout', name='logout'), - - # Admin panel - url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - (r'^admin/', include(admin.site.urls)), - - (r'^comments/', include('django.contrib.comments.urls')), - - url(r'^$', RedirectView.as_view(url= '/documents/')), - url(r'^documents/', include('catalogue.urls')), - url(r'^apiclient/', include('apiclient.urls')), - url(r'^editor/', include('wiki.urls')), - url(r'^images/', include('wiki_img.urls')), - url(r'^cover/', include('cover.urls')), -) - -if settings.DEBUG: - urlpatterns += staticfiles_urlpatterns() - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/redakcja/wsgi.py b/redakcja/wsgi.py deleted file mode 100755 index 5ffd742e..00000000 --- a/redakcja/wsgi.py +++ /dev/null @@ -1,19 +0,0 @@ -import os -import os.path -import sys - -ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -# Add apps and lib directories to PYTHONPATH -sys.path = [ - ROOT, - os.path.join(ROOT, 'apps'), -] + sys.path - - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "redakcja.settings") - -# This application object is used by the development server -# as well as any WSGI server configured to use this file. -from django.core.wsgi import get_wsgi_application -application = get_wsgi_application() diff --git a/src/apiclient/__init__.py b/src/apiclient/__init__.py new file mode 100644 index 00000000..56ecb96d --- /dev/null +++ b/src/apiclient/__init__.py @@ -0,0 +1,51 @@ +import urllib + +import json +import oauth2 + +from apiclient.settings import WL_CONSUMER_KEY, WL_CONSUMER_SECRET, WL_API_URL, BETA_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, beta=False): + from .models import OAuthConnection + api_url = BETA_API_URL if beta else WL_API_URL + conn = OAuthConnection.get(user=user, beta=beta) + 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: + data = json.dumps(data) + data = urllib.urlencode({"data": data}) + resp, content = client.request( + "%s%s" % (api_url, path), + method="POST", + body=data) + else: + resp, content = client.request( + "%s%s" % (api_url, path)) + status = resp['status'] + + if status == '200': + return json.loads(content) + elif status.startswith('2'): + return + elif status == '401': + raise ApiError('User not authorized for publishing.') + else: + raise ApiError("WL API call error %s, path: %s" % (status, path)) + diff --git a/src/apiclient/migrations/0001_initial.py b/src/apiclient/migrations/0001_initial.py new file mode 100644 index 00000000..4af28a52 --- /dev/null +++ b/src/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/src/apiclient/migrations/0002_auto__add_field_oauthconnection_beta.py b/src/apiclient/migrations/0002_auto__add_field_oauthconnection_beta.py new file mode 100644 index 00000000..094606fe --- /dev/null +++ b/src/apiclient/migrations/0002_auto__add_field_oauthconnection_beta.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as 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 'OAuthConnection.beta' + db.add_column(u'apiclient_oauthconnection', 'beta', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'OAuthConnection.beta' + db.delete_column(u'apiclient_oauthconnection', 'beta') + + + models = { + u'apiclient.oauthconnection': { + 'Meta': {'object_name': 'OAuthConnection'}, + 'access': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'beta': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + u'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': u"orm['auth.User']", 'unique': 'True'}) + }, + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'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'}), + u'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'] \ No newline at end of file diff --git a/src/apiclient/migrations/0003_auto__chg_field_oauthconnection_user__del_unique_oauthconnection_user.py b/src/apiclient/migrations/0003_auto__chg_field_oauthconnection_user__del_unique_oauthconnection_user.py new file mode 100644 index 00000000..ebd0fbb4 --- /dev/null +++ b/src/apiclient/migrations/0003_auto__chg_field_oauthconnection_user__del_unique_oauthconnection_user.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Removing unique constraint on 'OAuthConnection', fields ['user'] + db.delete_unique(u'apiclient_oauthconnection', ['user_id']) + + + # Changing field 'OAuthConnection.user' + db.alter_column(u'apiclient_oauthconnection', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])) + + def backwards(self, orm): + + # Changing field 'OAuthConnection.user' + db.alter_column(u'apiclient_oauthconnection', 'user_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)) + # Adding unique constraint on 'OAuthConnection', fields ['user'] + db.create_unique(u'apiclient_oauthconnection', ['user_id']) + + + models = { + u'apiclient.oauthconnection': { + 'Meta': {'object_name': 'OAuthConnection'}, + 'access': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'beta': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + u'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.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'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'}), + u'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'] \ No newline at end of file diff --git a/src/apiclient/migrations/__init__.py b/src/apiclient/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/apiclient/models.py b/src/apiclient/models.py new file mode 100644 index 00000000..64130bc1 --- /dev/null +++ b/src/apiclient/models.py @@ -0,0 +1,21 @@ +from django.db import models +from django.contrib.auth.models import User + + +class OAuthConnection(models.Model): + user = models.ForeignKey(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) + beta = models.BooleanField(default=False) + + @classmethod + def get(cls, user, beta=False): + try: + return cls.objects.get(user=user, beta=beta) + except cls.DoesNotExist: + o = cls(user=user, beta=beta) + o.save() + return o + + diff --git a/src/apiclient/settings.py b/src/apiclient/settings.py new file mode 100755 index 00000000..51b49069 --- /dev/null +++ b/src/apiclient/settings.py @@ -0,0 +1,20 @@ +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', 'https://wolnelektury.pl/api/') + +BETA_API_URL = getattr(settings, 'APICLIENT_BETA_API_URL', 'http://dev.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/') + +BETA_REQUEST_TOKEN_URL = BETA_API_URL + 'oauth/request_token/' +BETA_ACCESS_TOKEN_URL = BETA_API_URL + 'oauth/access_token/' +BETA_AUTHORIZE_URL = BETA_API_URL + 'oauth/authorize/' diff --git a/src/apiclient/urls.py b/src/apiclient/urls.py new file mode 100755 index 00000000..f623474e --- /dev/null +++ b/src/apiclient/urls.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from django.conf.urls import patterns, url + +urlpatterns = patterns('apiclient.views', + url(r'^oauth/$', 'oauth', name='apiclient_oauth'), + url(r'^oauth_callback/$', 'oauth_callback', name='apiclient_oauth_callback'), + url(r'^oauth-beta/$', 'oauth', kwargs={'beta': True}, name='apiclient_beta_oauth'), + url(r'^oauth_callback-beta/$', 'oauth_callback', kwargs={'beta': True}, name='apiclient_beta_callback'), +) diff --git a/src/apiclient/views.py b/src/apiclient/views.py new file mode 100644 index 00000000..239682a5 --- /dev/null +++ b/src/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 +from apiclient.settings import BETA_REQUEST_TOKEN_URL, BETA_ACCESS_TOKEN_URL, BETA_AUTHORIZE_URL + + +@login_required +def oauth(request, beta=False): + 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 not beta else BETA_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, beta) + # 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 if not beta else BETA_AUTHORIZE_URL, + request_token['oauth_token'], + request.build_absolute_uri(reverse("apiclient_oauth_callback" if not beta else "apiclient_beta_callback")), + ) + + return HttpResponseRedirect(url) + + +@login_required +def oauth_callback(request, beta=False): + if wl_consumer is None: + return HttpResponse("OAuth consumer not configured.") + + oauth_verifier = request.GET.get('oauth_verifier') + conn = OAuthConnection.get(request.user, beta) + 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 if not beta else BETA_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/src/catalogue/__init__.py b/src/catalogue/__init__.py new file mode 100644 index 00000000..c53f0e73 --- /dev/null +++ b/src/catalogue/__init__.py @@ -0,0 +1 @@ + # pragma: no cover diff --git a/src/catalogue/admin.py b/src/catalogue/admin.py new file mode 100644 index 00000000..53e8a256 --- /dev/null +++ b/src/catalogue/admin.py @@ -0,0 +1,18 @@ +from django.contrib import admin + +from catalogue import models + +class BookAdmin(admin.ModelAdmin): + list_display = ['title', 'public', '_published', '_new_publishable', 'project'] + list_filter = ['public', '_published', '_new_publishable', 'project'] + prepopulated_fields = {'slug': ['title']} + search_fields = ['title'] + + +admin.site.register(models.Project) +admin.site.register(models.Book, BookAdmin) +admin.site.register(models.Chunk) +admin.site.register(models.Chunk.tag_model) + +admin.site.register(models.Image) +admin.site.register(models.Image.tag_model) diff --git a/src/catalogue/constants.py b/src/catalogue/constants.py new file mode 100644 index 00000000..0c842324 --- /dev/null +++ b/src/catalogue/constants.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +TRIM_BEGIN = " TRIM_BEGIN " +TRIM_END = " TRIM_END " + +MASTERS = ['powiesc', + 'opowiadanie', + 'liryka_l', + 'liryka_lp', + 'dramat_wierszowany_l', + 'dramat_wierszowany_lp', + 'dramat_wspolczesny', + ] diff --git a/src/catalogue/ebook_utils.py b/src/catalogue/ebook_utils.py new file mode 100644 index 00000000..dae2e769 --- /dev/null +++ b/src/catalogue/ebook_utils.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from StringIO import StringIO +from catalogue.models import Book +from librarian import DocProvider +from django.http import HttpResponse + + +class RedakcjaDocProvider(DocProvider): + """Used for getting books' children.""" + + def __init__(self, publishable): + self.publishable = publishable + + def by_slug(self, slug): + return StringIO(Book.objects.get(dc_slug=slug + ).materialize(publishable=self.publishable + ).encode('utf-8')) + + +def serve_file(file_path, name, mime_type): + def read_chunks(f, size=8192): + chunk = f.read(size) + while chunk: + yield chunk + chunk = f.read(size) + + response = HttpResponse(content_type=mime_type) + response['Content-Disposition'] = 'attachment; filename=%s' % name + with open(file_path) as f: + for chunk in read_chunks(f): + response.write(chunk) + return response diff --git a/src/catalogue/feeds.py b/src/catalogue/feeds.py new file mode 100644 index 00000000..4884a4cd --- /dev/null +++ b/src/catalogue/feeds.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from django.contrib.syndication.views import Feed +from django.shortcuts import get_object_or_404 +from catalogue.models import Book, Chunk + +class PublishTrackFeed(Feed): + title = u"Planowane publikacje" + link = "/" + + def description(self, obj): + tag, published = obj + return u"Publikacje, które dotarły co najmniej do etapu: %s" % tag.name + + def get_object(self, request, slug): + published = request.GET.get('published') + if published is not None: + published = published == 'true' + return get_object_or_404(Chunk.tag_model, slug=slug), published + + def item_title(self, item): + return item.title + + def items(self, obj): + tag, published = obj + books = Book.objects.filter(public=True, _on_track__gte=tag.ordering + ).order_by('-_on_track', 'title') + if published is not None: + books = books.filter(_published=published) + return books diff --git a/src/catalogue/fixtures/stages.json b/src/catalogue/fixtures/stages.json new file mode 100644 index 00000000..5a46ec04 --- /dev/null +++ b/src/catalogue/fixtures/stages.json @@ -0,0 +1,83 @@ +[ + { + "pk": 1, + "model": "catalogue.chunktag", + "fields": { + "ordering": 1, + "name": "Autokorekta", + "slug": "first_correction" + } + }, + { + "pk": 2, + "model": "catalogue.chunktag", + "fields": { + "ordering": 2, + "name": "Tagowanie", + "slug": "tagging" + } + }, + { + "pk": 3, + "model": "catalogue.chunktag", + "fields": { + "ordering": 3, + "name": "Korekta", + "slug": "proofreading" + } + }, + { + "pk": 4, + "model": "catalogue.chunktag", + "fields": { + "ordering": 4, + "name": "Sprawdzenie przypis\u00f3w \u017ar\u00f3d\u0142a", + "slug": "annotation-proofreading" + } + }, + { + "pk": 5, + "model": "catalogue.chunktag", + "fields": { + "ordering": 5, + "name": "Uwsp\u00f3\u0142cze\u015bnienie", + "slug": "modernisation" + } + }, + { + "pk": 6, + "model": "catalogue.chunktag", + "fields": { + "ordering": 6, + "name": "Przypisy", + "slug": "annotations" + } + }, + { + "pk": 7, + "model": "catalogue.chunktag", + "fields": { + "ordering": 7, + "name": "Motywy", + "slug": "themes" + } + }, + { + "pk": 8, + "model": "catalogue.chunktag", + "fields": { + "ordering": 8, + "name": "Ostateczna redakcja literacka", + "slug": "editor-proofreading" + } + }, + { + "pk": 9, + "model": "catalogue.chunktag", + "fields": { + "ordering": 9, + "name": "Ostateczna redakcja techniczna", + "slug": "technical-editor-proofreading" + } + } +] diff --git a/src/catalogue/forms.py b/src/catalogue/forms.py new file mode 100644 index 00000000..ea6a4aef --- /dev/null +++ b/src/catalogue/forms.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from catalogue.models import User +from django.db.models import Count +from django import forms +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings + +from catalogue.constants import MASTERS +from catalogue.models import Book, Chunk, Image + +class DocumentCreateForm(forms.ModelForm): + """ + Form used for creating new documents. + """ + file = forms.FileField(required=False) + text = forms.CharField(required=False, widget=forms.Textarea) + + class Meta: + model = Book + exclude = ['parent', 'parent_number', 'project'] + + def __init__(self, *args, **kwargs): + super(DocumentCreateForm, self).__init__(*args, **kwargs) + self.fields['slug'].widget.attrs={'class': 'autoslug'} + self.fields['gallery'].widget.attrs={'class': 'autoslug'} + self.fields['title'].widget.attrs={'class': 'autoslug-source'} + + def clean(self): + super(DocumentCreateForm, self).clean() + file = self.cleaned_data['file'] + + if file is not None: + try: + self.cleaned_data['text'] = file.read().decode('utf-8') + except UnicodeDecodeError: + raise forms.ValidationError(_("Text file must be UTF-8 encoded.")) + + if not self.cleaned_data["text"]: + self._errors["file"] = self.error_class([_("You must either enter text or upload a file")]) + + return self.cleaned_data + + +class DocumentsUploadForm(forms.Form): + """ + Form used for uploading new documents. + """ + file = forms.FileField(required=True, label=_('ZIP file')) + dirs = forms.BooleanField(label=_('Directories are documents in chunks'), + widget = forms.CheckboxInput(attrs={'disabled':'disabled'})) + + def clean(self): + file = self.cleaned_data['file'] + + import zipfile + try: + z = self.cleaned_data['zip'] = zipfile.ZipFile(file) + except zipfile.BadZipfile: + raise forms.ValidationError("Should be a ZIP file.") + if z.testzip(): + raise forms.ValidationError("ZIP file corrupt.") + + return self.cleaned_data + + +class ChunkForm(forms.ModelForm): + """ + Form used for editing a chunk. + """ + user = forms.ModelChoiceField(queryset= + User.objects.annotate(count=Count('chunk')). + order_by('last_name', 'first_name'), required=False, + label=_('Assigned to')) + + class Meta: + model = Chunk + fields = ['title', 'slug', 'gallery_start', 'user', 'stage'] + exclude = ['number'] + + def __init__(self, *args, **kwargs): + super(ChunkForm, self).__init__(*args, **kwargs) + self.fields['gallery_start'].widget.attrs={'class': 'number-input'} + self.fields['slug'].widget.attrs={'class': 'autoslug'} + self.fields['title'].widget.attrs={'class': 'autoslug-source'} + + def clean_slug(self): + slug = self.cleaned_data['slug'] + try: + chunk = Chunk.objects.get(book=self.instance.book, slug=slug) + except Chunk.DoesNotExist: + return slug + if chunk == self.instance: + return slug + raise forms.ValidationError(_('Chunk with this slug already exists')) + + +class ChunkAddForm(ChunkForm): + """ + Form used for adding a chunk to a document. + """ + + def clean_slug(self): + slug = self.cleaned_data['slug'] + try: + user = Chunk.objects.get(book=self.instance.book, slug=slug) + except Chunk.DoesNotExist: + return slug + raise forms.ValidationError(_('Chunk with this slug already exists')) + + +class BookAppendForm(forms.Form): + """ + Form for appending a book to another book. + It means moving all chunks from book A to book B and deleting A. + """ + append_to = forms.ModelChoiceField(queryset=Book.objects.all(), + label=_("Append to")) + + def __init__(self, book, *args, **kwargs): + ret = super(BookAppendForm, self).__init__(*args, **kwargs) + self.fields['append_to'].queryset = Book.objects.exclude(pk=book.pk) + return ret + + +class BookForm(forms.ModelForm): + """Form used for editing a Book.""" + + class Meta: + model = Book + exclude = ['project'] + + def __init__(self, *args, **kwargs): + ret = super(BookForm, self).__init__(*args, **kwargs) + self.fields['slug'].widget.attrs.update({"class": "autoslug"}) + self.fields['title'].widget.attrs.update({"class": "autoslug-source"}) + return ret + + def save(self, **kwargs): + orig_instance = Book.objects.get(pk=self.instance.pk) + old_gallery = orig_instance.gallery + new_gallery = self.cleaned_data['gallery'] + if new_gallery != old_gallery: + import shutil + import os.path + from django.conf import settings + shutil.move(orig_instance.gallery_path(), + os.path.join(settings.MEDIA_ROOT, settings.IMAGE_DIR, new_gallery)) + super(BookForm, self).save(**kwargs) + + +class ReadonlyBookForm(BookForm): + """Form used for not editing a Book.""" + + def __init__(self, *args, **kwargs): + ret = super(ReadonlyBookForm, self).__init__(*args, **kwargs) + for field in self.fields.values(): + field.widget.attrs.update({"disabled": "disabled"}) + return ret + + +class ChooseMasterForm(forms.Form): + """ + Form used for fixing the chunks in a book. + """ + + master = forms.ChoiceField(choices=((m, m) for m in MASTERS)) + + +class ImageForm(forms.ModelForm): + """Form used for editing an Image.""" + user = forms.ModelChoiceField(queryset= + User.objects.annotate(count=Count('chunk')). + order_by('-count', 'last_name', 'first_name'), required=False, + label=_('Assigned to')) + + class Meta: + model = Image + fields = ['title', 'slug', 'user', 'stage'] + + def __init__(self, *args, **kwargs): + super(ImageForm, self).__init__(*args, **kwargs) + self.fields['slug'].widget.attrs={'class': 'autoslug'} + self.fields['title'].widget.attrs={'class': 'autoslug-source'} + + +class ReadonlyImageForm(ImageForm): + """Form used for not editing an Image.""" + + def __init__(self, *args, **kwargs): + super(ReadonlyImageForm, self).__init__(*args, **kwargs) + for field in self.fields.values(): + field.widget.attrs.update({"disabled": "disabled"}) + + +class MarkFinalForm(forms.Form): + username = forms.CharField(initial=settings.LITERARY_DIRECTOR_USERNAME) + comment = forms.CharField(initial=u'Ostateczna akceptacja merytoryczna przez kierownika literackiego.') + books = forms.CharField(widget=forms.Textarea, help_text=u'linki do książek w redakcji, po jednym na wiersz') + + def clean_books(self): + books_value = self.cleaned_data['books'] + slugs = [line.strip().strip('/').split('/')[-1] for line in books_value.split('\n') if line.strip()] + books = Book.objects.filter(slug__in=slugs) + if len(books) != len(slugs): + raise forms.ValidationError( + 'Incorrect slug(s): %s' % ' '.join(slug for slug in slugs if not Book.objects.filter(slug=slug))) + return books + + def clean_username(self): + username = self.cleaned_data['username'] + if not User.objects.filter(username=username): + raise forms.ValidationError('Invalid username') + return username + + def save(self): + for book in self.cleaned_data['books']: + for chunk in book.chunk_set.all(): + src = chunk.head.materialize() + chunk.commit( + text=src, + author=User.objects.get(username=self.cleaned_data['username']), + description=self.cleaned_data['comment'], + tags=[Chunk.tag_model.objects.get(slug='editor-proofreading')], + publishable=True + ) + + +class PublishOptionsForm(forms.Form): + days = forms.IntegerField(label=u'po ilu dniach udostępnienić (0 = od razu)', min_value=0, initial=0) + beta = forms.BooleanField(label=u'Opublikuj na wersji testowej', required=False) diff --git a/src/catalogue/helpers.py b/src/catalogue/helpers.py new file mode 100644 index 00000000..d340b461 --- /dev/null +++ b/src/catalogue/helpers.py @@ -0,0 +1,148 @@ +from datetime import date +from functools import wraps +from os.path import join +from os import listdir, stat +from shutil import move, rmtree +from django.conf import settings +import re +import filecmp + +from django.db.models import Count + + +def active_tab(tab): + """ + View decorator, which puts tab info on a request. + """ + def wrapper(f): + @wraps(f) + def wrapped(request, *args, **kwargs): + request.catalogue_active_tab = tab + return f(request, *args, **kwargs) + return wrapped + return wrapper + + +def cached_in_field(field_name): + def decorator(f): + @property + @wraps(f) + def wrapped(self, *args, **kwargs): + value = getattr(self, field_name) + if value is None: + value = f(self, *args, **kwargs) + type(self)._default_manager.filter(pk=self.pk).update(**{field_name: value}) + return value + return wrapped + return decorator + + +def parse_isodate(isodate): + try: + return date(*[int(p) for p in isodate.split('-')]) + except (AttributeError, TypeError, ValueError): + raise ValueError("Not a date in ISO format.") + + +class GalleryMerger(object): + def __init__(self, dest_gallery, src_gallery): + self.dest = dest_gallery + self.src = src_gallery + self.dest_size = None + self.src_size = None + self.num_deleted = 0 + + @staticmethod + def path(gallery): + return join(settings.MEDIA_ROOT, settings.IMAGE_DIR, gallery) + + @staticmethod + def get_prefix(name): + m = re.match(r"^([0-9])-", name) + if m: + return int(m.groups()[0]) + return None + + @staticmethod + def set_prefix(name, prefix, always=False): + m = not always and re.match(r"^([0-9])-", name) + return "%1d-%s" % (prefix, m and name[2:] or name) + + @property + def was_merged(self): + "Check if we have gallery size recorded" + return self.dest_size is not None + + def merge(self): + if not self.dest: + return self.src + if not self.src: + return self.dest + + files = listdir(self.path(self.dest)) + files.sort() + self.dest_size = len(files) + files_other = listdir(self.path(self.src)) + files_other.sort() + self.src_size = len(files_other) + + if files and files_other: + print "compare %s with %s" % (files[-1], files_other[0]) + if filecmp.cmp( + join(self.path(self.dest), files[-1]), + join(self.path(self.src), files_other[0]), + False + ): + files_other.pop(0) + self.num_deleted = 1 + + prefixes = {} + renamed_files = {} + renamed_files_other = {} + last_pfx = -1 + + # check if all elements of my files have a prefix + files_prefixed = True + for f in files: + p = self.get_prefix(f) + if p: + if p > last_pfx: last_pfx = p + else: + files_prefixed = False + break + + # if not, add a 0 prefix to them + if not files_prefixed: + prefixes[0] = 0 + for f in files: + renamed_files[f] = self.set_prefix(f, 0, True) + + # two cases here - either all are prefixed or not. + files_other_prefixed = True + for f in files_other: + pfx = self.get_prefix(f) + if pfx is not None: + if not pfx in prefixes: + last_pfx += 1 + prefixes[pfx] = last_pfx + renamed_files_other[f] = self.set_prefix(f, prefixes[pfx]) + else: + # ops, not all files here were prefixed. + files_other_prefixed = False + break + + # just set a 1- prefix to all of them + if not files_other_prefixed: + for f in files_other: + renamed_files_other[f] = self.set_prefix(f, 1, True) + + # finally, move / rename files. + for frm, to in renamed_files.items(): + move(join(self.path(self.dest), frm), + join(self.path(self.dest), to)) + for frm, to in renamed_files_other.items(): + move(join(self.path(self.src), frm), + join(self.path(self.dest), to)) + + rmtree(join(self.path(self.src))) + return self.dest diff --git a/src/catalogue/locale/pl/LC_MESSAGES/django.mo b/src/catalogue/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 00000000..87bdfbf9 Binary files /dev/null and b/src/catalogue/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/catalogue/locale/pl/LC_MESSAGES/django.po b/src/catalogue/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 00000000..6387b5d6 --- /dev/null +++ b/src/catalogue/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,804 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Platforma Redakcyjna\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-12-20 12:30+0100\n" +"PO-Revision-Date: 2014-03-27 13:17+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: Fundacja Nowoczesna Polska \n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 1.5.4\n" + +#: forms.py:40 +msgid "Text file must be UTF-8 encoded." +msgstr "Plik powinien mieć kodowanie UTF-8." + +#: forms.py:43 +msgid "You must either enter text or upload a file" +msgstr "Proszę wpisać tekst albo wybrać plik do załadowania" + +#: forms.py:52 +msgid "ZIP file" +msgstr "Plik ZIP" + +#: forms.py:53 +msgid "Directories are documents in chunks" +msgstr "Katalogi zawierają dokumenty w częściach" + +#: forms.py:77 forms.py:178 +msgid "Assigned to" +msgstr "Przypisane do" + +#: forms.py:98 forms.py:112 +msgid "Chunk with this slug already exists" +msgstr "Część z tym slugiem już istnieje" + +#: forms.py:121 +msgid "Append to" +msgstr "Dołącz do" + +#: views.py:172 +#, python-format +msgid "Slug already used for %s" +msgstr "Slug taki sam jak dla pliku %s" + +#: views.py:174 +msgid "Slug already used in repository." +msgstr "Dokument o tym slugu już istnieje w repozytorium." + +#: views.py:180 +msgid "File should be UTF-8 encoded." +msgstr "Plik powinien mieć kodowanie UTF-8." + +#: views.py:619 models/book.py:56 +msgid "books" +msgstr "książki" + +#: views.py:621 +msgid "scan gallery" +msgstr "galeria skanów" + +#: models/book.py:28 models/chunk.py:23 models/image.py:22 +msgid "title" +msgstr "tytuł" + +#: models/book.py:29 models/chunk.py:24 models/image.py:23 +msgid "slug" +msgstr "slug" + +#: models/book.py:30 models/image.py:24 +msgid "public" +msgstr "publiczna" + +#: models/book.py:31 +msgid "scan gallery name" +msgstr "nazwa galerii skanów" + +#: models/book.py:35 +msgid "parent" +msgstr "rodzic" + +#: models/book.py:36 +msgid "parent number" +msgstr "numeracja rodzica" + +#: models/book.py:55 models/chunk.py:21 models/publish_log.py:17 +msgid "book" +msgstr "książka" + +#: models/book.py:261 +msgid "No chunks in the book." +msgstr "Książka nie ma części." + +#: models/book.py:265 +msgid "Not all chunks have publishable revisions." +msgstr "Niektóre części nie są gotowe do publikacji." + +#: models/book.py:272 models/image.py:86 +msgid "Invalid XML" +msgstr "Nieprawidłowy XML" + +#: models/book.py:274 models/image.py:88 +msgid "No Dublin Core found." +msgstr "Brak sekcji Dublin Core." + +#: models/book.py:276 models/image.py:90 +msgid "Invalid Dublin Core" +msgstr "Nieprawidłowy Dublin Core" + +#: models/book.py:279 models/image.py:94 +msgid "rdf:about is not" +msgstr "rdf:about jest różny od" + +#: models/chunk.py:22 +msgid "number" +msgstr "numer" + +#: models/chunk.py:25 +msgid "gallery start" +msgstr "początek galerii" + +#: models/chunk.py:40 +msgid "chunk" +msgstr "część" + +#: models/chunk.py:41 +msgid "chunks" +msgstr "części" + +#: models/image.py:21 models/image.py:36 models/publish_log.py:45 +msgid "image" +msgstr "obraz" + +#: models/image.py:37 +msgid "images" +msgstr "obrazy" + +#: models/image.py:79 +msgid "There is no publishable revision" +msgstr "Żadna wersja nie została oznaczona do publikacji." + +#: models/project.py:13 +msgid "name" +msgstr "nazwa" + +#: models/project.py:14 +msgid "notes" +msgstr "notatki" + +#: models/project.py:19 templates/catalogue/image_table.html:58 +#: templates/catalogue/book_list/book_list.html:65 +msgid "project" +msgstr "projekt" + +#: models/project.py:20 +msgid "projects" +msgstr "projekty" + +#: models/publish_log.py:18 models/publish_log.py:46 +msgid "time" +msgstr "czas" + +#: models/publish_log.py:19 models/publish_log.py:47 +#: templates/catalogue/wall.html:20 +msgid "user" +msgstr "użytkownik" + +#: models/publish_log.py:24 models/publish_log.py:33 +msgid "book publish record" +msgstr "zapis publikacji książki" + +#: models/publish_log.py:25 +msgid "book publish records" +msgstr "zapisy publikacji książek" + +#: models/publish_log.py:34 models/publish_log.py:48 +msgid "change" +msgstr "zmiana" + +#: models/publish_log.py:38 +msgid "chunk publish record" +msgstr "zapis publikacji części" + +#: models/publish_log.py:39 +msgid "chunk publish records" +msgstr "zapisy publikacji części" + +#: models/publish_log.py:53 +msgid "image publish record" +msgstr "zapis publikacji obrazu" + +#: models/publish_log.py:54 +msgid "image publish records" +msgstr "zapisy publikacji obrazów" + +#: templates/catalogue/active_users_list.html:5 +msgid "Active users" +msgstr "Aktywni użytkownicy" + +#: templates/catalogue/active_users_list.html:11 +msgid "Active users since" +msgstr "Użytkownicy aktywni od" + +#: templates/catalogue/activity.html:6 templates/catalogue/activity.html:12 +#: templatetags/catalogue.py:29 +msgid "Activity" +msgstr "Aktywność" + +#: templates/catalogue/base.html:10 +msgid "Platforma Redakcyjna" +msgstr "Platforma Redakcyjna" + +#: templates/catalogue/book_append_to.html:4 +#: templates/catalogue/book_append_to.html:11 +msgid "Append book" +msgstr "Dołącz książkę" + +#: templates/catalogue/book_detail.html:18 +#: templates/catalogue/book_edit.html:13 templates/catalogue/chunk_edit.html:16 +#: templates/catalogue/image_detail.html:18 +msgid "Save" +msgstr "Zapisz" + +#: templates/catalogue/book_detail.html:25 +msgid "Edit gallery" +msgstr "Edytuj galerię" + +#: templates/catalogue/book_detail.html:28 +msgid "Append to other book" +msgstr "Dołącz do innej książki" + +#: templates/catalogue/book_detail.html:34 +msgid "Chunks" +msgstr "Części" + +#: templates/catalogue/book_detail.html:49 +#: templates/catalogue/image_detail.html:36 templatetags/wall.py:108 +#: templatetags/wall.py:129 +msgid "Publication" +msgstr "Publikacja" + +#: templates/catalogue/book_detail.html:58 +#: templates/catalogue/image_detail.html:38 +msgid "Last published" +msgstr "Ostatnio opublikowano" + +#: templates/catalogue/book_detail.html:68 +msgid "Full XML" +msgstr "Pełny XML" + +#: templates/catalogue/book_detail.html:69 +msgid "HTML version" +msgstr "Wersja HTML" + +#: templates/catalogue/book_detail.html:70 +msgid "TXT version" +msgstr "Wersja TXT" + +#: templates/catalogue/book_detail.html:71 +msgid "PDF version" +msgstr "Wersja PDF" + +#: templates/catalogue/book_detail.html:72 +msgid "PDF version for mobiles" +msgstr "Wersja PDF na telefony" + +#: templates/catalogue/book_detail.html:73 +msgid "EPUB version" +msgstr "Wersja EPUB" + +#: templates/catalogue/book_detail.html:74 +msgid "MOBI version" +msgstr "Wersja MOBI" + +#: templates/catalogue/book_detail.html:88 +#: templates/catalogue/image_detail.html:57 +msgid "Publish" +msgstr "Opublikuj" + +#: templates/catalogue/book_detail.html:92 +#: templates/catalogue/image_detail.html:61 +msgid "Log in to publish." +msgstr "Zaloguj się, aby opublikować." + +#: templates/catalogue/book_detail.html:95 +#: templates/catalogue/image_detail.html:64 +msgid "This book can't be published yet, because:" +msgstr "Ta książka nie może jeszcze zostać opublikowana. Powód:" + +#: templates/catalogue/book_edit.html:5 +msgid "Edit book" +msgstr "Edytuj książkę" + +#: templates/catalogue/book_html.html:12 templates/catalogue/book_text.html:15 +msgid "Table of contents" +msgstr "Spis treści" + +#: templates/catalogue/book_html.html:13 templates/catalogue/book_text.html:17 +msgid "Edit. note" +msgstr "Nota red." + +#: templates/catalogue/book_html.html:14 +msgid "Infobox" +msgstr "Informacje" + +#: templates/catalogue/book_text.html:7 +msgid "Redakcja" +msgstr "" + +#: templates/catalogue/chunk_add.html:5 templates/catalogue/chunk_add.html:9 +#: templates/catalogue/chunk_edit.html:22 +msgid "Split chunk" +msgstr "Podziel część" + +#: templates/catalogue/chunk_add.html:14 +msgid "Insert empty chunk after" +msgstr "Wstaw pustą część po" + +#: templates/catalogue/chunk_add.html:17 +msgid "Add chunk" +msgstr "Dodaj część" + +#: templates/catalogue/chunk_edit.html:5 templates/catalogue/chunk_edit.html:9 +#: templates/catalogue/book_list/book.html:9 +#: templates/catalogue/book_list/chunk.html:7 +msgid "Chunk settings" +msgstr "Ustawienia części" + +#: templates/catalogue/chunk_edit.html:14 +msgid "Book" +msgstr "Książka" + +#: templates/catalogue/document_create_missing.html:5 +#: templates/catalogue/document_create_missing.html:9 +msgid "Create a new book" +msgstr "Utwórz nową książkę" + +#: templates/catalogue/document_create_missing.html:15 +msgid "Create book" +msgstr "Utwórz książkę" + +#: templates/catalogue/document_list.html:7 +msgid "Book list" +msgstr "Lista książek" + +#: templates/catalogue/document_upload.html:5 +msgid "Bulk document upload" +msgstr "Hurtowe dodawanie dokumentów" + +#: templates/catalogue/document_upload.html:11 +msgid "Bulk documents upload" +msgstr "Hurtowe dodawanie dokumentów" + +#: templates/catalogue/document_upload.html:14 +msgid "" +"Please submit a ZIP with UTF-8 encoded XML files. Files not ending with " +".xml will be ignored." +msgstr "" +"Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie " +"kończące się na .xml zostaną zignorowane." + +#: templates/catalogue/document_upload.html:20 +#: templates/catalogue/upload_pdf.html:16 templatetags/catalogue.py:36 +msgid "Upload" +msgstr "Załaduj" + +#: templates/catalogue/document_upload.html:27 +msgid "" +"There have been some errors. No files have been added to the repository." +msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium." + +#: templates/catalogue/document_upload.html:28 +msgid "Offending files" +msgstr "Błędne pliki" + +#: templates/catalogue/document_upload.html:36 +msgid "Correct files" +msgstr "Poprawne pliki" + +#: templates/catalogue/document_upload.html:47 +msgid "Files have been successfully uploaded to the repository." +msgstr "Pliki zostały dodane do repozytorium." + +#: templates/catalogue/document_upload.html:48 +msgid "Uploaded files" +msgstr "Dodane pliki" + +#: templates/catalogue/document_upload.html:58 +msgid "Skipped files" +msgstr "Pominięte pliki" + +#: templates/catalogue/document_upload.html:59 +msgid "Files skipped due to no .xml extension" +msgstr "Pliki pominięte z powodu braku rozszerzenia .xml." + +#: templates/catalogue/image_detail.html:26 +msgid "Editor" +msgstr "Edytor" + +#: templates/catalogue/image_detail.html:28 +msgid "Proceed to the editor." +msgstr "Przejdź do edytora." + +#: templates/catalogue/image_list.html:8 +msgid "Image list" +msgstr "Lista obrazów" + +#: templates/catalogue/image_short.html:6 +msgid "Image settings" +msgstr "Ustawienia obrazu" + +#: templates/catalogue/image_table.html:23 +#: templates/catalogue/book_list/book_list.html:28 +msgid "Search in book titles" +msgstr "Szukaj w tytułach książek" + +#: templates/catalogue/image_table.html:28 +#: templates/catalogue/book_list/book_list.html:33 +msgid "stage" +msgstr "etap" + +#: templates/catalogue/image_table.html:30 +#: templates/catalogue/image_table.html:41 +#: templates/catalogue/image_table.html:60 +#: templates/catalogue/book_list/book_list.html:35 +#: templates/catalogue/book_list/book_list.html:46 +#: templates/catalogue/book_list/book_list.html:67 +msgid "none" +msgstr "brak" + +#: templates/catalogue/image_table.html:39 +#: templates/catalogue/book_list/book_list.html:44 +msgid "editor" +msgstr "redaktor" + +#: templates/catalogue/image_table.html:50 +#: templates/catalogue/book_list/book_list.html:57 +msgid "status" +msgstr "status" + +#: templates/catalogue/image_table.html:77 +#, python-format +msgid "%(c)s image" +msgid_plural "%(c)s images" +msgstr[0] "%(c)s obraz" +msgstr[1] "%(c)s obrazy" +msgstr[2] "%(c)s obrazów" + +#: templates/catalogue/image_table.html:82 +msgid "No images found." +msgstr "Nie znaleziono obrazów." + +#: templates/catalogue/image_table.html:88 +#: templates/catalogue/book_list/book_list.html:102 +msgid "Set stage" +msgstr "Ustaw etap" + +#: templates/catalogue/image_table.html:89 +#: templates/catalogue/book_list/book_list.html:103 +msgid "Set user" +msgstr "Przypisz redaktora" + +#: templates/catalogue/image_table.html:91 +#: templates/catalogue/book_list/book_list.html:105 +msgid "Project" +msgstr "Projekt" + +#: templates/catalogue/image_table.html:92 +#: templates/catalogue/book_list/book_list.html:106 +msgid "More users" +msgstr "Więcej użytkowników" + +#: templates/catalogue/my_page.html:15 templatetags/catalogue.py:27 +msgid "My page" +msgstr "Moja strona" + +#: templates/catalogue/my_page.html:24 +msgid "Your last edited documents" +msgstr "Twoje ostatnie edycje" + +#: templates/catalogue/my_page.html:39 templates/catalogue/user_page.html:16 +msgid "Recent activity for" +msgstr "Ostatnia aktywność dla:" + +#: templates/catalogue/upload_pdf.html:5 templates/catalogue/upload_pdf.html:11 +msgid "PDF file upload" +msgstr "Ładowanie pliku PDF" + +#: templates/catalogue/user_list.html:7 templates/catalogue/user_list.html:12 +#: templatetags/catalogue.py:32 +msgid "Users" +msgstr "Użytkownicy" + +#: templates/catalogue/wall.html:30 +msgid "not logged in" +msgstr "nie zalogowany" + +#: templates/catalogue/wall.html:35 +msgid "No activity recorded." +msgstr "Nie zanotowano aktywności." + +#: templates/catalogue/book_list/book.html:8 +#: templates/catalogue/book_list/book.html:29 +msgid "Book settings" +msgstr "Ustawienia książki" + +#: templates/catalogue/book_list/book_list.html:23 +msgid "Show hidden books" +msgstr "Pokaż ukryte książki" + +#: templates/catalogue/book_list/book_list.html:91 +#, python-format +msgid "%(c)s book" +msgid_plural "%(c)s books" +msgstr[0] "%(c)s książka" +msgstr[1] "%(c)s książki" +msgstr[2] "%(c)s książek" + +#: templates/catalogue/book_list/book_list.html:96 +msgid "No books found." +msgstr "Nie znaleziono książek." + +#: templatetags/book_list.py:84 templatetags/book_list.py:152 +msgid "publishable" +msgstr "do publikacji" + +#: templatetags/book_list.py:85 templatetags/book_list.py:153 +msgid "changed" +msgstr "zmienione" + +#: templatetags/book_list.py:86 templatetags/book_list.py:154 +msgid "published" +msgstr "opublikowane" + +#: templatetags/book_list.py:87 templatetags/book_list.py:155 +msgid "unpublished" +msgstr "nie opublikowane" + +#: templatetags/book_list.py:88 templatetags/book_list.py:156 +msgid "empty" +msgstr "puste" + +#: templatetags/catalogue.py:30 +msgid "All" +msgstr "Wszystkie" + +#: templatetags/catalogue.py:31 +msgid "Images" +msgstr "Obrazy" + +#: templatetags/catalogue.py:35 +msgid "Add" +msgstr "Dodaj" + +#: templatetags/catalogue.py:38 +msgid "Covers" +msgstr "Okładki" + +#: templatetags/wall.py:49 templatetags/wall.py:78 +msgid "Related edit" +msgstr "Powiązana zmiana" + +#: templatetags/wall.py:51 templatetags/wall.py:80 +msgid "Edit" +msgstr "Zmiana" + +#: templatetags/wall.py:150 +msgid "Comment" +msgstr "Komentarz" + +#~ msgid "Comments" +#~ msgstr "Komentarze" + +#~ msgid "Mark publishable" +#~ msgstr "Oznacz do publikacji" + +#~ msgid "Mark not publishable" +#~ msgstr "Odznacz do publikacji" + +#~ msgid "Other user" +#~ msgstr "Inny użytkownik" + +#~ msgid "Admin" +#~ msgstr "Administracja" + +#~ msgid "edit" +#~ msgstr "edytuj" + +#~ msgid "add basic document structure" +#~ msgstr "dodaj podstawową strukturę dokumentu" + +#~ msgid "change master tag to" +#~ msgstr "zmień tak master na" + +#~ msgid "add begin trimming tag" +#~ msgstr "dodaj początkowy ogranicznik" + +#~ msgid "add end trimming tag" +#~ msgstr "dodaj końcowy ogranicznik" + +#~ msgid "unstructured text" +#~ msgstr "tekst bez struktury" + +#~ msgid "unknown XML" +#~ msgstr "nieznany XML" + +#~ msgid "broken document" +#~ msgstr "uszkodzony dokument" + +#~ msgid "Apply fixes" +#~ msgstr "Wykonaj zmiany" + +#~ msgid "Can mark for publishing" +#~ msgstr "Oznacza do publikacji" + +#~ msgid "Author" +#~ msgstr "Autor" + +#~ msgid "Your name" +#~ msgstr "Imię i nazwisko" + +#~ msgid "Author's email" +#~ msgstr "E-mail autora" + +#~ msgid "Your email address, so we can show a gravatar :)" +#~ msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)" + +#~ msgid "Describe changes you made." +#~ msgstr "Opisz swoje zmiany" + +#~ msgid "Completed" +#~ msgstr "Ukończono" + +#~ msgid "If you completed a life cycle stage, select it." +#~ msgstr "Jeśli został ukończony etap prac, wskaż go." + +#~ msgid "Describe the reason for reverting." +#~ msgstr "Opisz powód przywrócenia." + +#~ msgid "theme" +#~ msgstr "motyw" + +#~ msgid "themes" +#~ msgstr "motywy" + +#~ msgid "Tag added" +#~ msgstr "Dodano tag" + +#~ msgid "Revision marked" +#~ msgstr "Wersja oznaczona" + +#~ msgid "New version" +#~ msgstr "Nowa wersja" + +#~ msgid "Click to open/close gallery" +#~ msgstr "Kliknij, aby (ro)zwinąć galerię" + +#~ msgid "Help" +#~ msgstr "Pomoc" + +#~ msgid "Version" +#~ msgstr "Wersja" + +#~ msgid "Unknown" +#~ msgstr "nieznana" + +#~ msgid "Save attempt in progress" +#~ msgstr "Trwa zapisywanie" + +#~ msgid "There is a newer version of this document!" +#~ msgstr "Istnieje nowsza wersja tego dokumentu!" + +#~ msgid "Clear filter" +#~ msgstr "Wyczyść filtr" + +#~ msgid "Cancel" +#~ msgstr "Anuluj" + +#~ msgid "Revert" +#~ msgstr "Przywróć" + +#~ msgid "all" +#~ msgstr "wszystkie" + +#~ msgid "Annotations" +#~ msgstr "Przypisy" + +#~ msgid "Previous" +#~ msgstr "Poprzednie" + +#~ msgid "Next" +#~ msgstr "Następne" + +#~ msgid "Zoom in" +#~ msgstr "Powiększ" + +#~ msgid "Zoom out" +#~ msgstr "Zmniejsz" + +#~ msgid "Gallery" +#~ msgstr "Galeria" + +#~ msgid "Compare versions" +#~ msgstr "Porównaj wersje" + +#~ msgid "Revert document" +#~ msgstr "Przywróć wersję" + +#~ msgid "View version" +#~ msgstr "Zobacz wersję" + +#~ msgid "History" +#~ msgstr "Historia" + +#~ msgid "Search" +#~ msgstr "Szukaj" + +#~ msgid "Replace with" +#~ msgstr "Zamień na" + +#~ msgid "Replace" +#~ msgstr "Zamień" + +#~ msgid "Options" +#~ msgstr "Opcje" + +#~ msgid "Case sensitive" +#~ msgstr "Rozróżniaj wielkość liter" + +#~ msgid "From cursor" +#~ msgstr "Zacznij od kursora" + +#~ msgid "Search and replace" +#~ msgstr "Znajdź i zamień" + +#~ msgid "Source code" +#~ msgstr "Kod źródłowy" + +#~ msgid "Title" +#~ msgstr "Tytuł" + +#~ msgid "Document ID" +#~ msgstr "ID dokumentu" + +#~ msgid "Current version" +#~ msgstr "Aktualna wersja" + +#~ msgid "Last edited by" +#~ msgstr "Ostatnio edytowane przez" + +#~ msgid "Summary" +#~ msgstr "Podsumowanie" + +#~ msgid "Insert theme" +#~ msgstr "Wstaw motyw" + +#~ msgid "Insert annotation" +#~ msgstr "Wstaw przypis" + +#~ msgid "Visual editor" +#~ msgstr "Edytor wizualny" + +#~ msgid "Unassigned" +#~ msgstr "Nie przypisane" + +#~ msgid "First correction" +#~ msgstr "Autokorekta" + +#~ msgid "Tagging" +#~ msgstr "Tagowanie" + +#~ msgid "Initial Proofreading" +#~ msgstr "Korekta" + +#~ msgid "Annotation Proofreading" +#~ msgstr "Sprawdzenie przypisów źródła" + +#~ msgid "Modernisation" +#~ msgstr "Uwspółcześnienie" + +#~ msgid "Themes" +#~ msgstr "Motywy" + +#~ msgid "Editor's Proofreading" +#~ msgstr "Ostateczna redakcja literacka" + +#~ msgid "Technical Editor's Proofreading" +#~ msgstr "Ostateczna redakcja techniczna" + +#~ msgid "Finished stage: %s" +#~ msgstr "Ukończony etap: %s" + +#~ msgid "Refresh" +#~ msgstr "Odśwież" diff --git a/src/catalogue/management/__init__.py b/src/catalogue/management/__init__.py new file mode 100644 index 00000000..bc3d6c02 --- /dev/null +++ b/src/catalogue/management/__init__.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from collections import defaultdict +from django.db import transaction +from lxml import etree + + +class XmlUpdater(object): + """A base class for massive XML updates. + + In a subclass, override `fix_tree` and/or use `fixes_field` decorator. + Attributes: + * commit_desc: commits description + * retain_publishable: set publishable if head is (default: True) + * only_first_chunk: process only first chunks of books (default: False) + """ + commit_desc = "auto-update" + retain_publishable = True + only_first_chunk = False + + _element_fixers = defaultdict(list) + + def __init__(self): + self.counters = defaultdict(lambda: 0) + + @classmethod + def fixes_elements(cls, xpath): + """Decorator, registering a function as a fixer for given field type. + + Any decorated function will be called like + f(element, change=..., verbose=...) + providing changeset as context. + + :param xpath: element lookup, e.g. ".//{namespace-uri}tag-name" + :returns: True if anything changed + """ + def wrapper(fixer): + cls._element_fixers[xpath].append(fixer) + return fixer + return wrapper + + def fix_tree(self, tree, verbose): + """Override to provide general tree-fixing mechanism. + + :param tree: the parsed XML tree + :param verbose: verbosity level + :returns: True if anythig changed + """ + return False + + def fix_chunk(self, chunk, user, verbose=0, dry_run=False): + """Runs the update for a single chunk.""" + if verbose >= 2: + print chunk.get_absolute_url() + old_head = chunk.head + src = old_head.materialize() + try: + tree = etree.fromstring(src) + except: + if verbose: + print "%s: invalid XML" % chunk.get_absolute_url() + self.counters['Bad XML'] += 1 + return + + dirty = False + # Call the general fixing function. + if self.fix_tree(tree, verbose=verbose): + dirty = True + # Call the registered fixers. + for xpath, fixers in self._element_fixers.items(): + for elem in tree.findall(xpath): + for fixer in fixers: + if fixer(elem, change=old_head, verbose=verbose): + dirty = True + + if not dirty: + self.counters['Clean'] += 1 + return + + if not dry_run: + new_head = chunk.commit( + etree.tostring(tree, encoding=unicode), + author=user, + description=self.commit_desc + ) + if self.retain_publishable: + if old_head.publishable: + new_head.set_publishable(True) + if verbose >= 2: + print "done" + self.counters['Updated chunks'] += 1 + + def run(self, user, verbose=0, dry_run=False, books=None): + """Runs the actual update.""" + if books is None: + from catalogue.models import Book + books = Book.objects.all() + + # Start transaction management. + transaction.enter_transaction_management() + + for book in books: + self.counters['All books'] += 1 + chunks = book.chunk_set.all() + if self.only_first_chunk: + chunks = chunks[:1] + for chunk in chunks: + self.counters['All chunks'] += 1 + self.fix_chunk(chunk, user, verbose, dry_run) + + transaction.commit() + transaction.leave_transaction_management() + + def print_results(self): + """Prints the counters.""" + for item in sorted(self.counters.items()): + print "%s: %d" % item diff --git a/src/catalogue/management/commands/__init__.py b/src/catalogue/management/commands/__init__.py new file mode 100644 index 00000000..e6f146f8 --- /dev/null +++ b/src/catalogue/management/commands/__init__.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +import sys +from optparse import make_option +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand +from catalogue.models import Book + + +class XmlUpdaterCommand(BaseCommand): + """Base class for creating massive XML-updating commands. + + In a subclass, provide an XmlUpdater class in the `updater' attribute. + """ + option_list = BaseCommand.option_list + ( + make_option('-q', '--quiet', action='store_false', dest='verbose', + default=True, help='Less output'), + make_option('-d', '--dry-run', action='store_true', dest='dry_run', + default=False, help="Don't actually touch anything"), + make_option('-u', '--username', dest='username', metavar='USER', + help='Assign commits to this user (required, preferably yourself).'), + ) + args = "[slug]..." + + def handle(self, *args, **options): + verbose = options.get('verbose') + dry_run = options.get('dry_run') + username = options.get('username') + + if username: + user = User.objects.get(username=username) + else: + print 'Please provide a username.' + sys.exit(1) + + books = Book.objects.filter(slug__in=args) if args else None + + updater = self.updater() + updater.run(user, verbose=verbose, dry_run=dry_run, books=books) + updater.print_results() diff --git a/src/catalogue/management/commands/add_parent.py b/src/catalogue/management/commands/add_parent.py new file mode 100644 index 00000000..2ab0510c --- /dev/null +++ b/src/catalogue/management/commands/add_parent.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +import sys + +from datetime import date +from lxml import etree + +from django.core.management import BaseCommand + +from catalogue.models import Book +from librarian import RDFNS, DCNS + +TEMPLATE = ''' + + +%(dc)s + + + + +''' + +DC_TEMPLATE = '%(value)s' + +DC_TAGS = ( + 'creator', + 'title', + 'relation.hasPart', + 'contributor.translator', + 'contributor.editor', + 'contributor.technical_editor', + 'contributor.funding', + 'contributor.thanks', + 'publisher', + 'subject.period', + 'subject.type', + 'subject.genre', + 'description', + 'identifier.url', + 'source', + 'source.URL', + 'rights.license', + 'rights', + 'date.pd', + 'format', + 'type', + 'date', + 'audience', + 'language', +) + +IDENTIFIER_PREFIX = 'http://wolnelektury.pl/katalog/lektura/' + + +def dc_desc_element(book): + xml = book.materialize() + tree = etree.fromstring(xml) + return tree.find(".//" + RDFNS("Description")) + + +def distinct_dc_values(tag, desc_elements): + values = set() + for desc in desc_elements: + values.update(elem.text for elem in desc.findall(DCNS(tag))) + return values + + +class Command(BaseCommand): + args = 'slug' + + def handle(self, slug, **options): + children_slugs = [line.strip() for line in sys.stdin] + children = Book.objects.filter(dc_slug__in=children_slugs) + desc_elements = [dc_desc_element(child) for child in children] + title = u'Utwory wybrane' + own_attributes = { + 'title': title, + 'relation.hasPart': [IDENTIFIER_PREFIX + child_slug for child_slug in children_slugs], + 'identifier.url': IDENTIFIER_PREFIX + slug, + 'date': date.today().isoformat(), + } + dc_tags = [] + for tag in DC_TAGS: + if tag in own_attributes: + values = own_attributes[tag] + if not isinstance(values, list): + values = [values] + else: + values = distinct_dc_values(tag, desc_elements) + for value in values: + dc_tags.append(DC_TEMPLATE % {'tag': tag, 'value': value}) + xml = TEMPLATE % {'slug': slug, 'dc': '\n'.join(dc_tags)} + Book.create( + text=xml, + creator=None, + slug=slug, + title=title, + gallery=slug) diff --git a/src/catalogue/management/commands/assign_from_redmine.py b/src/catalogue/management/commands/assign_from_redmine.py new file mode 100644 index 00000000..491fd832 --- /dev/null +++ b/src/catalogue/management/commands/assign_from_redmine.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- + +import csv +from optparse import make_option +import re +import sys +import urllib +import urllib2 + +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand +from django.core.management.color import color_style +from django.db import transaction + +from slugify import slugify +from catalogue.models import Chunk + + +REDMINE_CSV = 'http://redmine.nowoczesnapolska.org.pl/projects/wl-publikacje/issues.csv' +REDAKCJA_URL = 'http://redakcja.wolnelektury.pl/documents/' + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('-r', '--redakcja', dest='redakcja', metavar='URL', + help='Base URL of Redakcja documents', + default=REDAKCJA_URL), + make_option('-q', '--quiet', action='store_false', dest='verbose', default=True, + help='Less output'), + make_option('-f', '--force', action='store_true', dest='force', default=False, + help='Force assignment overwrite'), + ) + help = 'Imports ticket assignments from Redmine.' + args = '[redmine-csv-url]' + + def handle(self, *redmine_csv, **options): + + self.style = color_style() + + redakcja = options.get('redakcja') + verbose = options.get('verbose') + force = options.get('force') + + if not redmine_csv: + if verbose: + print "Using default Redmine CSV URL:", REDMINE_CSV + redmine_csv = REDMINE_CSV + + # Start transaction management. + transaction.enter_transaction_management() + + redakcja_link = re.compile(re.escape(redakcja) + r'([-_.:?&%/a-zA-Z0-9]*)') + + all_tickets = 0 + all_chunks = 0 + done_tickets = 0 + done_chunks = 0 + empty_users = 0 + unknown_users = {} + unknown_books = [] + forced = [] + + if verbose: + print 'Downloading CSV file' + for r in csv.reader(urllib2.urlopen(redmine_csv)): + if r[0] == '#': + continue + all_tickets += 1 + + username = r[6] + if not username: + if verbose: + print "Empty user, skipping" + empty_users += 1 + continue + + first_name, last_name = unicode(username, 'utf-8').rsplit(u' ', 1) + try: + user = User.objects.get(first_name=first_name, last_name=last_name) + except User.DoesNotExist: + print self.style.ERROR('Unknown user: ' + username) + unknown_users.setdefault(username, 0) + unknown_users[username] += 1 + continue + + ticket_done = False + for fname in redakcja_link.findall(r[-1]): + fname = unicode(urllib.unquote(fname), 'utf-8', 'ignore') + if fname.endswith('.xml'): + fname = fname[:-4] + fname = fname.replace(' ', '_') + fname = slugify(fname) + + chunks = Chunk.objects.filter(book__slug=fname) + if not chunks: + print self.style.ERROR('Unknown book: ' + fname) + unknown_books.append(fname) + continue + all_chunks += chunks.count() + + for chunk in chunks: + if chunk.user: + if chunk.user == user: + continue + else: + forced.append((chunk, chunk.user, user)) + if force: + print self.style.WARNING( + '%s assigned to %s, forcing change to %s.' % + (chunk.pretty_name(), chunk.user, user)) + else: + print self.style.WARNING( + '%s assigned to %s not to %s, skipping.' % + (chunk.pretty_name(), chunk.user, user)) + continue + chunk.user = user + chunk.save() + ticket_done = True + done_chunks += 1 + + if ticket_done: + done_tickets += 1 + + + # Print results + print + print "Results:" + print "Assignments imported from %d/%d tickets to %d/%d relevalt chunks." % ( + done_tickets, all_tickets, done_chunks, all_chunks) + if empty_users: + print "%d tickets were unassigned." % empty_users + if forced: + print "%d assignments conficts (%s):" % ( + len(forced), "changed" if force else "left") + for chunk, orig, user in forced: + print " %s: \t%s \t-> %s" % ( + chunk.pretty_name(), orig.username, user.username) + if unknown_books: + print "%d unknown books:" % len(unknown_books) + for fname in unknown_books: + print " %s" % fname + if unknown_users: + print "%d unknown users:" % len(unknown_users) + for name in unknown_users: + print " %s (%d tickets)" % (name, unknown_users[name]) + print + + + transaction.commit() + transaction.leave_transaction_management() + diff --git a/src/catalogue/management/commands/fixdc.py b/src/catalogue/management/commands/fixdc.py new file mode 100644 index 00000000..3f997d0c --- /dev/null +++ b/src/catalogue/management/commands/fixdc.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from librarian import RDFNS, WLURI, ValidationError +from librarian.dcparser import BookInfo +from catalogue.management import XmlUpdater +from catalogue.management.commands import XmlUpdaterCommand + + +class FixDC(XmlUpdater): + commit_desc = "auto-fixing DC" + retain_publishable = True + only_first_chunk = True + + def fix_wluri(elem, change, verbose): + try: + WLURI.strict(elem.text) + except ValidationError: + correct_field = unicode(WLURI.from_slug( + WLURI(elem.text.strip()).slug)) + try: + WLURI.strict(correct_field) + except ValidationError: + # Can't make a valid WLURI out of it, leave as is. + return False + if verbose: + print "Changing %s from %s to %s" % ( + elem.tag, elem.text, correct_field + ) + elem.text = correct_field + return True + for field in BookInfo.FIELDS: + if field.validator == WLURI: + XmlUpdater.fixes_elements('.//' + field.uri)(fix_wluri) + + @XmlUpdater.fixes_elements(".//" + RDFNS("Description")) + def fix_rdfabout(elem, change, verbose): + correct_about = change.tree.book.correct_about() + attr_name = RDFNS("about") + current_about = elem.get(attr_name) + if current_about != correct_about: + if verbose: + print "Changing rdf:about from %s to %s" % ( + current_about, correct_about + ) + elem.set(attr_name, correct_about) + return True + + +class Command(XmlUpdaterCommand): + updater = FixDC + help = 'Fixes obvious errors in DC: rdf:about and WLURI format.' diff --git a/src/catalogue/management/commands/import_wl.py b/src/catalogue/management/commands/import_wl.py new file mode 100644 index 00000000..45c9e331 --- /dev/null +++ b/src/catalogue/management/commands/import_wl.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +from collections import defaultdict +import json +from optparse import make_option +import urllib2 + +from django.core.management.base import BaseCommand +from django.core.management.color import color_style +from django.db import transaction +from librarian.dcparser import BookInfo +from librarian import ParseError, ValidationError + +from catalogue.models import Book + + +WL_API = 'http://www.wolnelektury.pl/api/books/' + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('-q', '--quiet', action='store_false', dest='verbose', default=True, + help='Less output'), + ) + help = 'Imports XML files from WL.' + + def handle(self, *args, **options): + + self.style = color_style() + + verbose = options.get('verbose') + + # Start transaction management. + transaction.enter_transaction_management() + + if verbose: + print 'Reading currently managed files (skipping hidden ones).' + slugs = defaultdict(list) + for b in Book.objects.exclude(slug__startswith='.').all(): + if verbose: + print b.slug + text = b.materialize().encode('utf-8') + try: + info = BookInfo.from_bytes(text) + except (ParseError, ValidationError): + pass + else: + slugs[info.slug].append(b) + + book_count = 0 + commit_args = { + "author_name": 'Platforma', + "description": 'Automatycznie zaimportowane z Wolnych Lektur', + "publishable": True, + } + + if verbose: + print 'Opening books list' + for book in json.load(urllib2.urlopen(WL_API)): + book_detail = json.load(urllib2.urlopen(book['href'])) + xml_text = urllib2.urlopen(book_detail['xml']).read() + info = BookInfo.from_bytes(xml_text) + previous_books = slugs.get(info.slug) + if previous_books: + if len(previous_books) > 1: + print self.style.ERROR("There is more than one book " + "with slug %s:"), + previous_book = previous_books[0] + comm = previous_book.slug + else: + previous_book = None + comm = '*' + print book_count, info.slug , '-->', comm + Book.import_xml_text(xml_text, title=info.title[:255], + slug=info.slug[:128], previous_book=previous_book, + commit_args=commit_args) + book_count += 1 + + # Print results + print + print "Results:" + print "Imported %d books from WL:" % ( + book_count, ) + print + + + transaction.commit() + transaction.leave_transaction_management() + diff --git a/src/catalogue/management/commands/insert_isbn.py b/src/catalogue/management/commands/insert_isbn.py new file mode 100644 index 00000000..7548cb1f --- /dev/null +++ b/src/catalogue/management/commands/insert_isbn.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +import csv + +import sys +from django.contrib.auth.models import User +from lxml import etree +from optparse import make_option + +from collections import defaultdict +from django.core.management import BaseCommand + +from catalogue.models import Book +from librarian import RDFNS, DCNS + +CONTENT_TYPES = { + 'pdf': 'application/pdf', + 'epub': 'application/epub+zip', + 'mobi': 'application/x-mobipocket-ebook', + 'txt': 'text/plain', + 'html': 'text/html', +} + + +ISBN_TEMPLATES = ( + r'%(url)s' + r'', + r'ISBN-%(isbn)s', + r'ISBN', + r'%(content_type)s', +) + + +def url_for_format(slug, format): + if format == 'html': + return 'https://wolnelektury.pl/katalog/lektura/%s.html' % slug + else: + return 'http://wolnelektury.pl/media/book/%(format)s/%(slug)s.%(format)s' % {'slug': slug, 'format': format} + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + # make_option('-q', '--quiet', action='store_false', dest='verbose', + # default=True, help='Less output'), + # make_option('-d', '--dry-run', action='store_true', dest='dry_run', + # default=False, help="Don't actually touch anything"), + make_option( + '-u', '--username', dest='username', metavar='USER', + help='Assign commits to this user (required, preferably yourself).'), + ) + args = 'csv_file' + + def handle(self, csv_file, **options): + username = options.get('username') + + if username: + user = User.objects.get(username=username) + else: + print 'Please provide a username.' + sys.exit(1) + + csvfile = open(csv_file, 'rb') + isbn_lists = defaultdict(list) + for slug, format, isbn in csv.reader(csvfile, delimiter=','): + isbn_lists[slug].append((format, isbn)) + csvfile.close() + + for slug, isbn_list in isbn_lists.iteritems(): + print 'processing %s' % slug + book = Book.objects.get(dc_slug=slug) + chunk = book.chunk_set.first() + old_head = chunk.head + src = old_head.materialize() + tree = etree.fromstring(src) + isbn_node = tree.find('.//' + DCNS("relation.hasFormat")) + if isbn_node is not None: + print '%s already contains ISBN metadata, skipping' % slug + continue + desc = tree.find(".//" + RDFNS("Description")) + for format, isbn in isbn_list: + for template in ISBN_TEMPLATES: + isbn_xml = template % { + 'format': format, + 'isbn': isbn, + 'content_type': CONTENT_TYPES[format], + 'url': url_for_format(slug, format), + } + element = etree.XML(isbn_xml) + element.tail = '\n' + desc.append(element) + new_head = chunk.commit( + etree.tostring(tree, encoding=unicode), + author=user, + description='automatyczne dodanie isbn' + ) + print 'committed %s' % slug + if old_head.publishable: + new_head.set_publishable(True) + else: + print 'Warning: %s not publishable' % slug diff --git a/src/catalogue/management/commands/mark_final.py b/src/catalogue/management/commands/mark_final.py new file mode 100644 index 00000000..cdfaab93 --- /dev/null +++ b/src/catalogue/management/commands/mark_final.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +import sys +from django.contrib.auth.models import User +from optparse import make_option + +from django.core.management import BaseCommand + +from catalogue.models import Book, Chunk + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + # make_option('-q', '--quiet', action='store_false', dest='verbose', + # default=True, help='Less output'), + # make_option('-d', '--dry-run', action='store_true', dest='dry_run', + # default=False, help="Don't actually touch anything"), + make_option( + '-u', '--username', dest='username', metavar='USER', + help='Assign commits to this user (required).'), + ) + args = 'slug_file' + + def handle(self, slug_file, **options): + username = options.get('username') + + if username: + user = User.objects.get(username=username) + else: + print 'Please provide a username.' + sys.exit(1) + + slugs = [line.strip() for line in open(slug_file)] + books = Book.objects.filter(slug__in=slugs) + + for book in books: + print 'processing %s' % book.slug + for chunk in book.chunk_set.all(): + src = chunk.head.materialize() + chunk.commit( + text=src, + author=user, + description=u'Ostateczna akceptacja merytoryczna przez kierownika literackiego.', + tags=[Chunk.tag_model.objects.get(slug='editor-proofreading')], + publishable=True + ) + print 'committed %s' % book.slug diff --git a/src/catalogue/management/commands/merge_books.py b/src/catalogue/management/commands/merge_books.py new file mode 100644 index 00000000..82bd622d --- /dev/null +++ b/src/catalogue/management/commands/merge_books.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- + +from optparse import make_option +import sys + +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand +from django.core.management.color import color_style +from django.db import transaction + +from catalogue.models import Book + + +def common_prefix(texts): + common = [] + + min_len = min(len(text) for text in texts) + for i in range(min_len): + chars = list(set([text[i] for text in texts])) + if len(chars) > 1: + break + common.append(chars[0]) + return "".join(common) + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('-s', '--slug', dest='new_slug', metavar='SLUG', + help='New slug of the merged book (defaults to common part of all slugs).'), + make_option('-t', '--title', dest='new_title', metavar='TITLE', + help='New title of the merged book (defaults to common part of all titles).'), + make_option('-q', '--quiet', action='store_false', dest='verbose', default=True, + help='Less output'), + make_option('-g', '--guess', action='store_true', dest='guess', default=False, + help='Try to guess what merges are needed (but do not apply them).'), + make_option('-d', '--dry-run', action='store_true', dest='dry_run', default=False, + help='Dry run: do not actually change anything.'), + make_option('-f', '--force', action='store_true', dest='force', default=False, + help='On slug conflict, hide the original book to archive.'), + ) + help = 'Merges multiple books into one.' + args = '[slug]...' + + + def print_guess(self, dry_run=True, force=False): + from collections import defaultdict + from pipes import quote + import re + + def read_slug(slug): + res = [] + res.append((re.compile(ur'__?(przedmowa)$'), -1)) + res.append((re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P\d*)$'), None)) + res.append((re.compile(ur'__?(rozdzialy__?)?(?P\d*)-'), None)) + + for r, default in res: + m = r.search(slug) + if m: + start = m.start() + try: + return int(m.group('n')), slug[:start] + except IndexError: + return default, slug[:start] + return None, slug + + def file_to_title(fname): + """ Returns a title-like version of a filename. """ + parts = (p.replace('_', ' ').title() for p in fname.split('__')) + return ' / '.join(parts) + + merges = defaultdict(list) + slugs = [] + for b in Book.objects.all(): + slugs.append(b.slug) + n, ns = read_slug(b.slug) + if n is not None: + merges[ns].append((n, b)) + + conflicting_slugs = [] + for slug in sorted(merges.keys()): + merge_list = sorted(merges[slug]) + if len(merge_list) < 2: + continue + + merge_slugs = [b.slug for i, b in merge_list] + if slug in slugs and slug not in merge_slugs: + conflicting_slugs.append(slug) + + title = file_to_title(slug) + print "./manage.py merge_books %s%s--title=%s --slug=%s \\\n %s\n" % ( + '--dry-run ' if dry_run else '', + '--force ' if force else '', + quote(title), slug, + " \\\n ".join(merge_slugs) + ) + + if conflicting_slugs: + if force: + print self.style.NOTICE('# These books will be archived:') + else: + print self.style.ERROR('# ERROR: Conflicting slugs:') + for slug in conflicting_slugs: + print '#', slug + + + def handle(self, *slugs, **options): + + self.style = color_style() + + force = options.get('force') + guess = options.get('guess') + dry_run = options.get('dry_run') + new_slug = options.get('new_slug').decode('utf-8') + new_title = options.get('new_title').decode('utf-8') + verbose = options.get('verbose') + + if guess: + if slugs: + print "Please specify either slugs, or --guess." + return + else: + self.print_guess(dry_run, force) + return + if not slugs: + print "Please specify some book slugs" + return + + # Start transaction management. + transaction.enter_transaction_management() + + books = [Book.objects.get(slug=slug) for slug in slugs] + common_slug = common_prefix(slugs) + common_title = common_prefix([b.title for b in books]) + + if not new_title: + new_title = common_title + elif common_title.startswith(new_title): + common_title = new_title + + if not new_slug: + new_slug = common_slug + elif common_slug.startswith(new_slug): + common_slug = new_slug + + if slugs[0] != new_slug and Book.objects.filter(slug=new_slug).exists(): + self.style.ERROR('Book already exists, skipping!') + + + if dry_run and verbose: + print self.style.NOTICE('DRY RUN: nothing will be changed.') + print + + if verbose: + print "New title:", self.style.NOTICE(new_title) + print "New slug:", self.style.NOTICE(new_slug) + print + + for i, book in enumerate(books): + chunk_titles = [] + chunk_slugs = [] + + book_title = book.title[len(common_title):].replace(' / ', ' ').lstrip() + book_slug = book.slug[len(common_slug):].replace('__', '_').lstrip('-_') + for j, chunk in enumerate(book): + if j: + new_chunk_title = book_title + '_%d' % j + new_chunk_slug = book_slug + '_%d' % j + else: + new_chunk_title, new_chunk_slug = book_title, book_slug + + chunk_titles.append(new_chunk_title) + chunk_slugs.append(new_chunk_slug) + + if verbose: + print "title: %s // %s -->\n %s // %s\nslug: %s / %s -->\n %s / %s" % ( + book.title, chunk.title, + new_title, new_chunk_title, + book.slug, chunk.slug, + new_slug, new_chunk_slug) + print + + if not dry_run: + try: + conflict = Book.objects.get(slug=new_slug) + except Book.DoesNotExist: + conflict = None + else: + if conflict == books[0]: + conflict = None + + if conflict: + if force: + # FIXME: there still may be a conflict + conflict.slug = '.' + conflict.slug + conflict.save() + print self.style.NOTICE('Book with slug "%s" moved to "%s".' % (new_slug, conflict.slug)) + else: + print self.style.ERROR('ERROR: Book with slug "%s" exists.' % new_slug) + return + + if i: + books[0].append(books[i], slugs=chunk_slugs, titles=chunk_titles) + else: + book.title = new_title + book.slug = new_slug + book.save() + for j, chunk in enumerate(book): + chunk.title = chunk_titles[j] + chunk.slug = chunk_slugs[j] + chunk.save() + + + transaction.commit() + transaction.leave_transaction_management() + diff --git a/src/catalogue/management/commands/prune_audience.py b/src/catalogue/management/commands/prune_audience.py new file mode 100644 index 00000000..114a26f9 --- /dev/null +++ b/src/catalogue/management/commands/prune_audience.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# + +import sys +from django.contrib.auth.models import User +from lxml import etree +from optparse import make_option + +from django.core.management import BaseCommand + +from catalogue.models import Book +from librarian import DCNS + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + # make_option('-q', '--quiet', action='store_false', dest='verbose', + # default=True, help='Less output'), + # make_option('-d', '--dry-run', action='store_true', dest='dry_run', + # default=False, help="Don't actually touch anything"), + make_option( + '-u', '--username', dest='username', metavar='USER', + help='Assign commits to this user (required, preferably yourself).'), + ) + args = 'exclude_file' + + def handle(self, exclude_file, **options): + username = options.get('username') + + if username: + user = User.objects.get(username=username) + else: + print 'Please provide a username.' + sys.exit(1) + + excluded_slugs = [line.strip() for line in open(exclude_file, 'rb') if line.strip()] + books = Book.objects.exclude(slug__in=excluded_slugs) + + for book in books: + if not book.is_published(): + continue + print 'processing %s' % book.slug + chunk = book.chunk_set.first() + old_head = chunk.head + src = old_head.materialize() + tree = etree.fromstring(src) + audience_nodes = tree.findall('.//' + DCNS("audience")) + if not audience_nodes: + print '%s has no audience, skipping' % book.slug + continue + + for node in audience_nodes: + node.getparent().remove(node) + + chunk.commit( + etree.tostring(tree, encoding=unicode), + author=user, + description='automatyczne skasowanie audience', + publishable=old_head.publishable + ) + print 'committed %s' % book.slug + if not old_head.publishable: + print 'Warning: %s not publishable, last head: %s, %s' % ( + book.slug, old_head.author.username, old_head.description[:40].replace('\n', ' ')) diff --git a/src/catalogue/managers.py b/src/catalogue/managers.py new file mode 100644 index 00000000..a131ce94 --- /dev/null +++ b/src/catalogue/managers.py @@ -0,0 +1,5 @@ +from django.db import models + +class VisibleManager(models.Manager): + def get_queryset(self): + return super(VisibleManager, self).get_queryset().exclude(_hidden=True) diff --git a/src/catalogue/migrations/0001_initial.py b/src/catalogue/migrations/0001_initial.py new file mode 100644 index 00000000..dccd9b7b --- /dev/null +++ b/src/catalogue/migrations/0001_initial.py @@ -0,0 +1,240 @@ +# 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 'Book' + db.create_table('catalogue_book', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=128, db_index=True)), + ('gallery', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['catalogue.Book'])), + ('parent_number', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)), + ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('_single', self.gf('django.db.models.fields.NullBooleanField')(db_index=True, null=True, blank=True)), + ('_new_publishable', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('_published', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + )) + db.send_create_signal('catalogue', ['Book']) + + # Adding model 'Chunk' + db.create_table('catalogue_chunk', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_documents', null=True, to=orm['auth.User'])), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), + ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Book'])), + ('number', self.gf('django.db.models.fields.IntegerField')()), + ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('_hidden', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('_changed', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ChunkTag'], null=True, blank=True)), + ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['catalogue.ChunkChange'], null=True, blank=True)), + )) + db.send_create_signal('catalogue', ['Chunk']) + + # Adding unique constraint on 'Chunk', fields ['book', 'number'] + db.create_unique('catalogue_chunk', ['book_id', 'number']) + + # Adding unique constraint on 'Chunk', fields ['book', 'slug'] + db.create_unique('catalogue_chunk', ['book_id', 'slug']) + + # Adding model 'ChunkTag' + db.create_table('catalogue_chunktag', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=64)), + ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=64, unique=True, null=True, blank=True)), + ('ordering', self.gf('django.db.models.fields.IntegerField')()), + )) + db.send_create_signal('catalogue', ['ChunkTag']) + + # Adding model 'ChunkChange' + db.create_table('catalogue_chunkchange', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), + ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), + ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), + ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['catalogue.ChunkChange'])), + ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['catalogue.ChunkChange'])), + ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)), + ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('tree', self.gf('django.db.models.fields.related.ForeignKey')(related_name='change_set', to=orm['catalogue.Chunk'])), + ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + )) + db.send_create_signal('catalogue', ['ChunkChange']) + + # Adding unique constraint on 'ChunkChange', fields ['tree', 'revision'] + db.create_unique('catalogue_chunkchange', ['tree_id', 'revision']) + + # Adding M2M table for field tags on 'ChunkChange' + db.create_table('catalogue_chunkchange_tags', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('chunkchange', models.ForeignKey(orm['catalogue.chunkchange'], null=False)), + ('chunktag', models.ForeignKey(orm['catalogue.chunktag'], null=False)) + )) + db.create_unique('catalogue_chunkchange_tags', ['chunkchange_id', 'chunktag_id']) + + # Adding model 'BookPublishRecord' + db.create_table('catalogue_bookpublishrecord', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('book', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.Book'])), + ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + )) + db.send_create_signal('catalogue', ['BookPublishRecord']) + + # Adding model 'ChunkPublishRecord' + db.create_table('catalogue_chunkpublishrecord', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('book_record', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.BookPublishRecord'])), + ('change', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.ChunkChange'])), + )) + db.send_create_signal('catalogue', ['ChunkPublishRecord']) + + + def backwards(self, orm): + + # Removing unique constraint on 'ChunkChange', fields ['tree', 'revision'] + db.delete_unique('catalogue_chunkchange', ['tree_id', 'revision']) + + # Removing unique constraint on 'Chunk', fields ['book', 'slug'] + db.delete_unique('catalogue_chunk', ['book_id', 'slug']) + + # Removing unique constraint on 'Chunk', fields ['book', 'number'] + db.delete_unique('catalogue_chunk', ['book_id', 'number']) + + # Deleting model 'Book' + db.delete_table('catalogue_book') + + # Deleting model 'Chunk' + db.delete_table('catalogue_chunk') + + # Deleting model 'ChunkTag' + db.delete_table('catalogue_chunktag') + + # Deleting model 'ChunkChange' + db.delete_table('catalogue_chunkchange') + + # Removing M2M table for field tags on 'ChunkChange' + db.delete_table('catalogue_chunkchange_tags') + + # Deleting model 'BookPublishRecord' + db.delete_table('catalogue_bookpublishrecord') + + # Deleting model 'ChunkPublishRecord' + db.delete_table('catalogue_chunkpublishrecord') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.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', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + '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['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/src/catalogue/migrations/0002_stages.py b/src/catalogue/migrations/0002_stages.py new file mode 100644 index 00000000..71554570 --- /dev/null +++ b/src/catalogue/migrations/0002_stages.py @@ -0,0 +1,122 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + + from django.core.management import call_command + call_command("loaddata", "stages.json") + + + def backwards(self, orm): + "Write your backwards methods here." + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.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', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + '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['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/src/catalogue/migrations/0003_from_hg.py b/src/catalogue/migrations/0003_from_hg.py new file mode 100644 index 00000000..e542d508 --- /dev/null +++ b/src/catalogue/migrations/0003_from_hg.py @@ -0,0 +1,281 @@ +# encoding: utf-8 +import datetime +from zlib import compress +import os +import os.path +import re +import urllib + +from django.db import models +from south.db import db +from south.v2 import DataMigration + +from django.conf import settings +from slugify import slugify + +META_REGEX = re.compile(r'\s*', re.DOTALL | re.MULTILINE) +STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE) +AUTHOR_RE = re.compile(r'\s*(.*?)\s*<(.*)>\s*') + + +def urlunquote(url): + """Unqotes URL + + # >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84') + # u'Za\u017c\xf3\u0142\u0107_g\u0119\u015bl\u0105 ja\u017a\u0144' + """ + return unicode(urllib.unquote(url), 'utf-8', 'ignore') + + +def split_name(name): + parts = name.split('__') + return parts + + +def file_to_title(fname): + """ Returns a title-like version of a filename. """ + parts = (p.replace('_', ' ').title() for p in fname.split('__')) + return ' / '.join(parts) + + +def plain_text(text): + return re.sub(META_REGEX, '', text, 1) + + +def gallery(slug, text): + result = {} + + m = re.match(META_REGEX, text) + if m: + for line in m.group(1).split('\n'): + try: + k, v = line.split(':', 1) + result[k.strip()] = v.strip() + except ValueError: + continue + + gallery = result.get('gallery', slugify(slug)) + + if gallery.startswith('/'): + gallery = os.path.basename(gallery) + + return gallery + + +def migrate_file_from_hg(orm, fname, entry): + fname = urlunquote(fname) + print fname + if fname.endswith('.xml'): + fname = fname[:-4] + title = file_to_title(fname) + fname = slugify(fname) + + # create all the needed objects + # what if it already exists? + book = orm.Book.objects.create( + title=title, + slug=fname) + chunk = orm.Chunk.objects.create( + book=book, + number=1, + slug='1') + try: + chunk.stage = orm.ChunkTag.objects.order_by('ordering')[0] + except IndexError: + chunk.stage = None + + maxrev = entry.filerev() + gallery_link = None + + # this will fail if directory exists + os.makedirs(os.path.join(settings.CATALOGUE_REPO_PATH, str(chunk.pk))) + + for rev in xrange(maxrev + 1): + fctx = entry.filectx(rev) + data = fctx.data() + gallery_link = gallery(fname, data) + data = plain_text(data) + + # get tags from description + description = fctx.description().decode("utf-8", 'replace') + tags = STAGE_TAGS_RE.findall(description) + tags = [orm.ChunkTag.objects.get(slug=slug.strip()) for slug in tags] + + if tags: + max_ordering = max(tags, key=lambda x: x.ordering).ordering + try: + chunk.stage = orm.ChunkTag.objects.filter(ordering__gt=max_ordering).order_by('ordering')[0] + except IndexError: + chunk.stage = None + + description = STAGE_TAGS_RE.sub('', description) + + author = author_name = author_email = None + author_desc = fctx.user().decode("utf-8", 'replace') + m = AUTHOR_RE.match(author_desc) + if m: + try: + author = orm['auth.User'].objects.get(username=m.group(1), email=m.group(2)) + except orm['auth.User'].DoesNotExist: + author_name = m.group(1) + author_email = m.group(2) + else: + author_name = author_desc + + head = orm.ChunkChange.objects.create( + tree=chunk, + revision=rev + 1, + created_at=datetime.datetime.fromtimestamp(fctx.date()[0]), + description=description, + author=author, + author_name=author_name, + author_email=author_email, + parent=chunk.head + ) + + path = "%d/%d" % (chunk.pk, head.pk) + abs_path = os.path.join(settings.CATALOGUE_REPO_PATH, path) + f = open(abs_path, 'wb') + f.write(compress(data)) + f.close() + head.data = path + + head.tags = tags + head.save() + + chunk.head = head + + chunk.save() + if gallery_link: + book.gallery = gallery_link + book.save() + + +class Migration(DataMigration): + + def forwards(self, orm): + try: + hg_path = settings.WIKI_REPOSITORY_PATH + except: + print 'repository not configured, skipping' + else: + from mercurial import hg, ui + + print 'migrate from', hg_path + repo = hg.repository(ui.ui(), hg_path) + tip = repo['tip'] + for fname in tip: + if fname.startswith('.'): + continue + migrate_file_from_hg(orm, fname, tip[fname]) + + + def backwards(self, orm): + "Write your backwards methods here." + pass + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.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', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + '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['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/src/catalogue/migrations/0004_fix_revisions.py b/src/catalogue/migrations/0004_fix_revisions.py new file mode 100644 index 00000000..fe5c86be --- /dev/null +++ b/src/catalogue/migrations/0004_fix_revisions.py @@ -0,0 +1,125 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + "Make sure all revisions start with 1, not 0." + for zero_commit in orm.ChunkChange.objects.filter(revision=0): + for change in zero_commit.tree.change_set.all().order_by('-revision'): + change.revision=models.F('revision') + 1 + change.save() + + + def backwards(self, orm): + "Write your backwards methods here." + pass + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.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', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/src/catalogue/migrations/0005_auto__add_field_chunk_gallery_start.py b/src/catalogue/migrations/0005_auto__add_field_chunk_gallery_start.py new file mode 100644 index 00000000..71af5f6c --- /dev/null +++ b/src/catalogue/migrations/0005_auto__add_field_chunk_gallery_start.py @@ -0,0 +1,125 @@ +# 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 'Chunk.gallery_start' + db.add_column('catalogue_chunk', 'gallery_start', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'Chunk.gallery_start' + db.delete_column('catalogue_chunk', 'gallery_start') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.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', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), + 'gallery_start': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/src/catalogue/migrations/0006_auto__add_field_book_public.py b/src/catalogue/migrations/0006_auto__add_field_book_public.py new file mode 100644 index 00000000..fd1cea56 --- /dev/null +++ b/src/catalogue/migrations/0006_auto__add_field_book_public.py @@ -0,0 +1,126 @@ +# 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.public' + db.add_column('catalogue_book', 'public', self.gf('django.db.models.fields.BooleanField')(default=True, db_index=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'Book.public' + db.delete_column('catalogue_book', 'public') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': '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['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), + 'gallery_start': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/src/catalogue/migrations/0007_auto__add_field_book_dc_slug.py b/src/catalogue/migrations/0007_auto__add_field_book_dc_slug.py new file mode 100644 index 00000000..5ae20ea3 --- /dev/null +++ b/src/catalogue/migrations/0007_auto__add_field_book_dc_slug.py @@ -0,0 +1,127 @@ +# 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.dc_slug' + db.add_column('catalogue_book', 'dc_slug', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'Book.dc_slug' + db.delete_column('catalogue_book', 'dc_slug') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'dc_slug': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': '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['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), + 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/src/catalogue/migrations/0008_auto.py b/src/catalogue/migrations/0008_auto.py new file mode 100644 index 00000000..5276b27b --- /dev/null +++ b/src/catalogue/migrations/0008_auto.py @@ -0,0 +1,127 @@ +# 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 index on 'Book', fields ['dc_slug'] + db.create_index('catalogue_book', ['dc_slug']) + + + def backwards(self, orm): + + # Removing index on 'Book', fields ['dc_slug'] + db.delete_index('catalogue_book', ['dc_slug']) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': '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['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), + 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/src/catalogue/migrations/0009_auto__add_field_book__on_track.py b/src/catalogue/migrations/0009_auto__add_field_book__on_track.py new file mode 100644 index 00000000..f0509c42 --- /dev/null +++ b/src/catalogue/migrations/0009_auto__add_field_book__on_track.py @@ -0,0 +1,128 @@ +# 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._on_track' + db.add_column('catalogue_book', '_on_track', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'Book._on_track' + db.delete_column('catalogue_book', '_on_track') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': '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['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), + 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/src/catalogue/migrations/0010_auto__add_field_book_dc_cover_image.py b/src/catalogue/migrations/0010_auto__add_field_book_dc_cover_image.py new file mode 100644 index 00000000..aebbed95 --- /dev/null +++ b/src/catalogue/migrations/0010_auto__add_field_book_dc_cover_image.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + depends_on = ( + ("cover", "0001_initial"), + ) + + def forwards(self, orm): + # Adding field 'Book.dc_cover_image' + db.add_column('catalogue_book', 'dc_cover_image', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cover.Image'], null=True, on_delete=models.SET_NULL, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Book.dc_cover_image' + db.delete_column('catalogue_book', 'dc_cover_image_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', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': '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['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}), + 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + '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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + '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'}) + }, + 'cover.image': { + 'Meta': {'object_name': 'Image'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'download_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}), + 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['catalogue'] \ No newline at end of file diff --git a/src/catalogue/migrations/0011_auto__add_project__add_field_book_project.py b/src/catalogue/migrations/0011_auto__add_project__add_field_book_project.py new file mode 100644 index 00000000..6f30cb4f --- /dev/null +++ b/src/catalogue/migrations/0011_auto__add_project__add_field_book_project.py @@ -0,0 +1,159 @@ +# -*- coding: 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 'Project' + db.create_table(u'catalogue_project', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), + ('notes', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + )) + db.send_create_signal('catalogue', ['Project']) + + # Adding field 'Book.project' + db.add_column(u'catalogue_book', 'project', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Project'], null=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting model 'Project' + db.delete_table(u'catalogue_project') + + # Deleting field 'Book.project' + db.delete_column(u'catalogue_book', 'project_id') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'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': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + u'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['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + u'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', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.project': { + 'Meta': {'ordering': "['name']", 'object_name': 'Project'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + u'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'}), + u'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'}) + }, + u'cover.image': { + 'Meta': {'object_name': 'Image'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'download_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}), + 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['catalogue'] \ No newline at end of file diff --git a/src/catalogue/migrations/0012_auto__add_imagepublishrecord__add_imagechange__add_unique_imagechange_.py b/src/catalogue/migrations/0012_auto__add_imagepublishrecord__add_imagechange__add_unique_imagechange_.py new file mode 100644 index 00000000..599e103e --- /dev/null +++ b/src/catalogue/migrations/0012_auto__add_imagepublishrecord__add_imagechange__add_unique_imagechange_.py @@ -0,0 +1,270 @@ +# -*- coding: 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 'ImagePublishRecord' + db.create_table(u'catalogue_imagepublishrecord', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('image', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.Image'])), + ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('change', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.ImageChange'])), + )) + db.send_create_signal('catalogue', ['ImagePublishRecord']) + + # Adding model 'ImageChange' + db.create_table(u'catalogue_imagechange', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), + ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), + ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), + ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['catalogue.ImageChange'])), + ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['catalogue.ImageChange'])), + ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)), + ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('tree', self.gf('django.db.models.fields.related.ForeignKey')(related_name='change_set', to=orm['catalogue.Image'])), + ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + )) + db.send_create_signal('catalogue', ['ImageChange']) + + # Adding M2M table for field tags on 'ImageChange' + db.create_table(u'catalogue_imagechange_tags', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('imagechange', models.ForeignKey(orm['catalogue.imagechange'], null=False)), + ('imagetag', models.ForeignKey(orm['catalogue.imagetag'], null=False)) + )) + db.create_unique(u'catalogue_imagechange_tags', ['imagechange_id', 'imagetag_id']) + + # Adding unique constraint on 'ImageChange', fields ['tree', 'revision'] + db.create_unique(u'catalogue_imagechange', ['tree_id', 'revision']) + + # Adding model 'ImageTag' + db.create_table(u'catalogue_imagetag', ( + (u'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')(max_length=64, unique=True, null=True, blank=True)), + ('ordering', self.gf('django.db.models.fields.IntegerField')()), + )) + db.send_create_signal('catalogue', ['ImageTag']) + + # Adding model 'Image' + db.create_table(u'catalogue_image', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), + ('image', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=50)), + ('public', self.gf('django.db.models.fields.BooleanField')(default=True, db_index=True)), + ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('_new_publishable', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('_published', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('_changed', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ImageTag'], null=True, blank=True)), + ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['catalogue.ImageChange'], null=True, blank=True)), + ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_image', null=True, to=orm['auth.User'])), + )) + db.send_create_signal('catalogue', ['Image']) + + + def backwards(self, orm): + # Removing unique constraint on 'ImageChange', fields ['tree', 'revision'] + db.delete_unique(u'catalogue_imagechange', ['tree_id', 'revision']) + + # Deleting model 'ImagePublishRecord' + db.delete_table(u'catalogue_imagepublishrecord') + + # Deleting model 'ImageChange' + db.delete_table(u'catalogue_imagechange') + + # Removing M2M table for field tags on 'ImageChange' + db.delete_table('catalogue_imagechange_tags') + + # Deleting model 'ImageTag' + db.delete_table(u'catalogue_imagetag') + + # Deleting model 'Image' + db.delete_table(u'catalogue_image') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'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': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + u'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['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + u'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', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.image': { + 'Meta': {'ordering': "['title']", 'object_name': 'Image'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_image'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ImageChange']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ImageTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.imagechange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ImageChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'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['catalogue.ImageChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ImageChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ImageTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Image']"}) + }, + 'catalogue.imagepublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'ImagePublishRecord'}, + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ImageChange']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Image']"}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + 'catalogue.imagetag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ImageTag'}, + u'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', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.project': { + 'Meta': {'ordering': "['name']", 'object_name': 'Project'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + u'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'}), + u'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'}) + }, + u'cover.image': { + 'Meta': {'object_name': 'Image'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['catalogue'] \ No newline at end of file diff --git a/src/catalogue/migrations/0013_auto__add_field_image_project.py b/src/catalogue/migrations/0013_auto__add_field_image_project.py new file mode 100644 index 00000000..6ae3564f --- /dev/null +++ b/src/catalogue/migrations/0013_auto__add_field_image_project.py @@ -0,0 +1,196 @@ +# -*- coding: 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 'Image.project' + db.add_column(u'catalogue_image', 'project', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Project'], null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Image.project' + db.delete_column(u'catalogue_image', 'project_id') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'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': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + u'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['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'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['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + u'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', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.image': { + 'Meta': {'ordering': "['title']", 'object_name': 'Image'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_image'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ImageChange']", 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True', 'blank': 'True'}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ImageTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.imagechange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ImageChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'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['catalogue.ImageChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ImageChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ImageTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Image']"}) + }, + 'catalogue.imagepublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'ImagePublishRecord'}, + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ImageChange']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Image']"}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + 'catalogue.imagetag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ImageTag'}, + u'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', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.project': { + 'Meta': {'ordering': "['name']", 'object_name': 'Project'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + u'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'}), + u'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'}) + }, + u'cover.image': { + 'Meta': {'object_name': 'Image'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['catalogue'] \ No newline at end of file diff --git a/src/catalogue/migrations/__init__.py b/src/catalogue/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/catalogue/models/__init__.py b/src/catalogue/models/__init__.py new file mode 100755 index 00000000..d0015c79 --- /dev/null +++ b/src/catalogue/models/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from catalogue.models.project import Project +from catalogue.models.chunk import Chunk +from catalogue.models.image import Image +from catalogue.models.publish_log import (BookPublishRecord, + ChunkPublishRecord, ImagePublishRecord) +from catalogue.models.book import Book +from catalogue.models.listeners import * + +from django.contrib.auth.models import User as AuthUser + +class User(AuthUser): + class Meta: + proxy = True + + def __unicode__(self): + return "%s %s" % (self.first_name, self.last_name) diff --git a/src/catalogue/models/book.py b/src/catalogue/models/book.py new file mode 100755 index 00000000..1fcd05ac --- /dev/null +++ b/src/catalogue/models/book.py @@ -0,0 +1,452 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.contrib.sites.models import Site +from django.db import models, transaction +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings +from slugify import slugify + + +import apiclient +from catalogue.helpers import cached_in_field, GalleryMerger +from catalogue.models import BookPublishRecord, ChunkPublishRecord, Project +from catalogue.signals import post_publish +from catalogue.tasks import refresh_instance, book_content_updated +from catalogue.xml_tools import compile_text, split_xml +from cover.models import Image +import os +import shutil +import re + +class Book(models.Model): + """ A document edited on the wiki """ + + title = models.CharField(_('title'), max_length=255, db_index=True) + slug = models.SlugField(_('slug'), max_length=128, unique=True, db_index=True) + public = models.BooleanField(_('public'), default=True, db_index=True) + gallery = models.CharField(_('scan gallery name'), max_length=255, blank=True) + project = models.ForeignKey(Project, null=True, blank=True) + + #wl_slug = models.CharField(_('title'), max_length=255, null=True, db_index=True, editable=False) + parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children", editable=False) + parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True, editable=False) + + # Cache + _short_html = models.TextField(null=True, blank=True, editable=False) + _single = models.NullBooleanField(editable=False, db_index=True) + _new_publishable = models.NullBooleanField(editable=False) + _published = models.NullBooleanField(editable=False) + _on_track = models.IntegerField(null=True, blank=True, db_index=True, editable=False) + dc_cover_image = models.ForeignKey(Image, blank=True, null=True, + db_index=True, on_delete=models.SET_NULL, editable=False) + dc_slug = models.CharField(max_length=128, null=True, blank=True, + editable=False, db_index=True) + + class NoTextError(BaseException): + pass + + class Meta: + app_label = 'catalogue' + ordering = ['title', 'slug'] + verbose_name = _('book') + verbose_name_plural = _('books') + + + # Representing + # ============ + + def __iter__(self): + return iter(self.chunk_set.all()) + + def __getitem__(self, chunk): + return self.chunk_set.all()[chunk] + + def __len__(self): + return self.chunk_set.count() + + def __nonzero__(self): + """ + Necessary so that __len__ isn't used for bool evaluation. + """ + return True + + def __unicode__(self): + return self.title + + @models.permalink + def get_absolute_url(self): + return ("catalogue_book", [self.slug]) + + def correct_about(self): + return "http://%s%s" % ( + Site.objects.get_current().domain, + self.get_absolute_url() + ) + + def gallery_path(self): + return os.path.join(settings.MEDIA_ROOT, settings.IMAGE_DIR, self.gallery) + + def gallery_url(self): + return '%s%s%s/' % (settings.MEDIA_URL, settings.IMAGE_DIR, self.gallery) + + # Creating & manipulating + # ======================= + + def accessible(self, request): + return self.public or request.user.is_authenticated() + + @classmethod + @transaction.atomic + def create(cls, creator, text, *args, **kwargs): + b = cls.objects.create(*args, **kwargs) + b.chunk_set.all().update(creator=creator) + b[0].commit(text, author=creator) + return b + + def add(self, *args, **kwargs): + """Add a new chunk at the end.""" + return self.chunk_set.reverse()[0].split(*args, **kwargs) + + @classmethod + @transaction.atomic + def import_xml_text(cls, text=u'', previous_book=None, + commit_args=None, **kwargs): + """Imports a book from XML, splitting it into chunks as necessary.""" + texts = split_xml(text) + if previous_book: + instance = previous_book + else: + instance = cls(**kwargs) + instance.save() + + # if there are more parts, set the rest to empty strings + book_len = len(instance) + for i in range(book_len - len(texts)): + texts.append((u'pusta część %d' % (i + 1), u'')) + + i = 0 + for i, (title, text) in enumerate(texts): + if not title: + title = u'część %d' % (i + 1) + + slug = slugify(title) + + if i < book_len: + chunk = instance[i] + chunk.slug = slug[:50] + chunk.title = title[:255] + chunk.save() + else: + chunk = instance.add(slug, title) + + chunk.commit(text, **commit_args) + + return instance + + def make_chunk_slug(self, proposed): + """ + Finds a chunk slug not yet used in the book. + """ + slugs = set(c.slug for c in self) + i = 1 + new_slug = proposed[:50] + while new_slug in slugs: + new_slug = "%s_%d" % (proposed[:45], i) + i += 1 + return new_slug + + @transaction.atomic + def append(self, other, slugs=None, titles=None): + """Add all chunks of another book to self.""" + assert self != other + + number = self[len(self) - 1].number + 1 + len_other = len(other) + single = len_other == 1 + + if slugs is not None: + assert len(slugs) == len_other + if titles is not None: + assert len(titles) == len_other + if slugs is None: + slugs = [slugify(t) for t in titles] + + for i, chunk in enumerate(other): + # move chunk to new book + chunk.book = self + chunk.number = number + + if titles is None: + # try some title guessing + if other.title.startswith(self.title): + other_title_part = other.title[len(self.title):].lstrip(' /') + else: + other_title_part = other.title + + if single: + # special treatment for appending one-parters: + # just use the guessed title and original book slug + chunk.title = other_title_part + if other.slug.startswith(self.slug): + chunk.slug = other.slug[len(self.slug):].lstrip('-_') + else: + chunk.slug = other.slug + else: + chunk.title = ("%s, %s" % (other_title_part, chunk.title))[:255] + else: + chunk.slug = slugs[i] + chunk.title = titles[i] + + chunk.slug = self.make_chunk_slug(chunk.slug) + chunk.save() + number += 1 + assert not other.chunk_set.exists() + + gm = GalleryMerger(self.gallery, other.gallery) + self.gallery = gm.merge() + + # and move the gallery starts + if gm.was_merged: + for chunk in self[len(self) - len_other:]: + old_start = chunk.gallery_start or 1 + chunk.gallery_start = old_start + gm.dest_size - gm.num_deleted + chunk.save() + + other.delete() + + + @transaction.atomic + def prepend_history(self, other): + """Prepend history from all the other book's chunks to own.""" + assert self != other + + for i in range(len(self), len(other)): + title = u"pusta część %d" % i + chunk = self.add(slugify(title), title) + chunk.commit('') + + for i in range(len(other)): + self[i].prepend_history(other[0]) + + assert not other.chunk_set.exists() + other.delete() + + def split(self): + """Splits all the chunks into separate books.""" + self.title + for chunk in self: + book = Book.objects.create(title=chunk.title, slug=chunk.slug, + public=self.public, gallery=self.gallery) + book[0].delete() + chunk.book = book + chunk.number = 1 + chunk.save() + assert not self.chunk_set.exists() + self.delete() + + # State & cache + # ============= + + def last_published(self): + try: + return self.publish_log.all()[0].timestamp + except IndexError: + return None + + def assert_publishable(self): + assert self.chunk_set.exists(), _('No chunks in the book.') + try: + changes = self.get_current_changes(publishable=True) + except self.NoTextError: + raise AssertionError(_('Not all chunks have publishable revisions.')) + + from librarian import NoDublinCore, ParseError, ValidationError + + try: + bi = self.wldocument(changes=changes, strict=True).book_info + except ParseError, e: + raise AssertionError(_('Invalid XML') + ': ' + unicode(e)) + except NoDublinCore: + raise AssertionError(_('No Dublin Core found.')) + except ValidationError, e: + raise AssertionError(_('Invalid Dublin Core') + ': ' + unicode(e)) + + valid_about = self.correct_about() + assert bi.about == valid_about, _("rdf:about is not") + " " + valid_about + + def publishable_error(self): + try: + return self.assert_publishable() + except AssertionError, e: + return e + else: + return None + + def hidden(self): + return self.slug.startswith('.') + + def is_new_publishable(self): + """Checks if book is ready for publishing. + + Returns True if there is a publishable version newer than the one + already published. + + """ + new_publishable = False + if not self.chunk_set.exists(): + return False + for chunk in self: + change = chunk.publishable() + if not change: + return False + if not new_publishable and not change.publish_log.exists(): + new_publishable = True + return new_publishable + new_publishable = cached_in_field('_new_publishable')(is_new_publishable) + + def is_published(self): + return self.publish_log.exists() + published = cached_in_field('_published')(is_published) + + def get_on_track(self): + if self.published: + return -1 + stages = [ch.stage.ordering if ch.stage is not None else 0 + for ch in self] + if not len(stages): + return 0 + return min(stages) + on_track = cached_in_field('_on_track')(get_on_track) + + def is_single(self): + return len(self) == 1 + single = cached_in_field('_single')(is_single) + + @cached_in_field('_short_html') + def short_html(self): + return render_to_string('catalogue/book_list/book.html', {'book': self}) + + def book_info(self, publishable=True): + try: + book_xml = self.materialize(publishable=publishable) + except self.NoTextError: + pass + else: + from librarian.dcparser import BookInfo + from librarian import NoDublinCore, ParseError, ValidationError + try: + return BookInfo.from_bytes(book_xml.encode('utf-8')) + except (self.NoTextError, ParseError, NoDublinCore, ValidationError): + return None + + def refresh_dc_cache(self): + update = { + 'dc_slug': None, + 'dc_cover_image': None, + } + + info = self.book_info() + if info is not None: + update['dc_slug'] = info.url.slug + if info.cover_source: + try: + image = Image.objects.get(pk=int(info.cover_source.rstrip('/').rsplit('/', 1)[-1])) + except: + pass + else: + if info.cover_source == image.get_full_url(): + update['dc_cover_image'] = image + Book.objects.filter(pk=self.pk).update(**update) + + def touch(self): + # this should only really be done when text or publishable status changes + book_content_updated.delay(self) + + update = { + "_new_publishable": self.is_new_publishable(), + "_published": self.is_published(), + "_single": self.is_single(), + "_on_track": self.get_on_track(), + "_short_html": None, + } + Book.objects.filter(pk=self.pk).update(**update) + refresh_instance(self) + + def refresh(self): + """This should be done offline.""" + self.short_html + self.single + self.new_publishable + self.published + + # Materializing & publishing + # ========================== + + def get_current_changes(self, publishable=True): + """ + Returns a list containing one Change for every Chunk in the Book. + Takes the most recent revision (publishable, if set). + Throws an error, if a proper revision is unavailable for a Chunk. + """ + if publishable: + changes = [chunk.publishable() for chunk in self] + else: + changes = [chunk.head for chunk in self if chunk.head is not None] + if None in changes: + raise self.NoTextError('Some chunks have no available text.') + return changes + + def materialize(self, publishable=False, changes=None): + """ + Get full text of the document compiled from chunks. + Takes the current versions of all texts + or versions most recently tagged for publishing, + or a specified iterable changes. + """ + if changes is None: + changes = self.get_current_changes(publishable) + return compile_text(change.materialize() for change in changes) + + def wldocument(self, publishable=True, changes=None, + parse_dublincore=True, strict=False): + from catalogue.ebook_utils import RedakcjaDocProvider + from librarian.parser import WLDocument + + return WLDocument.from_bytes( + self.materialize(publishable=publishable, changes=changes).encode('utf-8'), + provider=RedakcjaDocProvider(publishable=publishable), + parse_dublincore=parse_dublincore, + strict=strict) + + def publish(self, user, fake=False, host=None, days=0, beta=False): + """ + Publishes a book on behalf of a (local) user. + """ + self.assert_publishable() + changes = self.get_current_changes(publishable=True) + if not fake: + book_xml = self.materialize(changes=changes) + data = {"book_xml": book_xml, "days": days} + if host: + data['gallery_url'] = host + self.gallery_url() + apiclient.api_call(user, "books/", data, beta=beta) + if not beta: + # record the publish + br = BookPublishRecord.objects.create(book=self, user=user) + for c in changes: + ChunkPublishRecord.objects.create(book_record=br, change=c) + if not self.public and days == 0: + self.public = True + self.save() + if self.public and days > 0: + self.public = False + self.save() + post_publish.send(sender=br) + + def latex_dir(self): + doc = self.wldocument() + return doc.latex_dir(cover=True, ilustr_path=self.gallery_path()) diff --git a/src/catalogue/models/chunk.py b/src/catalogue/models/chunk.py new file mode 100755 index 00000000..fc3a9eae --- /dev/null +++ b/src/catalogue/models/chunk.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.conf import settings +from django.db import models +from django.db.utils import IntegrityError +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _ +from catalogue.helpers import cached_in_field +from catalogue.managers import VisibleManager +from catalogue.tasks import refresh_instance +from dvcs import models as dvcs_models + + +class Chunk(dvcs_models.Document): + """ An editable chunk of text. Every Book text is divided into chunks. """ + REPO_PATH = settings.CATALOGUE_REPO_PATH + + book = models.ForeignKey('Book', editable=False, verbose_name=_('book')) + number = models.IntegerField(_('number')) + title = models.CharField(_('title'), max_length=255, blank=True) + slug = models.SlugField(_('slug')) + gallery_start = models.IntegerField(_('gallery start'), null=True, blank=True, default=1) + + # cache + _short_html = models.TextField(null=True, blank=True, editable=False) + _hidden = models.NullBooleanField(editable=False) + _changed = models.NullBooleanField(editable=False) + + # managers + objects = models.Manager() + visible_objects = VisibleManager() + + class Meta: + app_label = 'catalogue' + unique_together = [['book', 'number'], ['book', 'slug']] + ordering = ['number'] + verbose_name = _('chunk') + verbose_name_plural = _('chunks') + permissions = [('can_pubmark', 'Can mark for publishing')] + + # Representing + # ============ + + def __unicode__(self): + return "%d:%d: %s" % (self.book_id, self.number, self.title) + + @models.permalink + def get_absolute_url(self): + return "wiki_editor", [self.book.slug, self.slug] + + def pretty_name(self, book_length=None): + title = self.book.title + if self.title: + title += ", %s" % self.title + if book_length > 1: + title += " (%d/%d)" % (self.number, book_length) + return title + + # Creating and manipulation + # ========================= + + def split(self, slug, title='', **kwargs): + """ Create an empty chunk after this one """ + self.book.chunk_set.filter(number__gt=self.number).update( + number=models.F('number')+1) + new_chunk = None + while not new_chunk: + new_slug = self.book.make_chunk_slug(slug) + try: + new_chunk = self.book.chunk_set.create( + number=self.number+1, + slug=new_slug[:50], title=title[:255], **kwargs) + except IntegrityError: + pass + return new_chunk + + @classmethod + def get(cls, book_slug, chunk_slug=None): + if chunk_slug is None: + return cls.objects.get(book__slug=book_slug, number=1) + else: + return cls.objects.get(book__slug=book_slug, slug=chunk_slug) + + # State & cache + # ============= + + def new_publishable(self): + change = self.publishable() + if not change: + return False + return not change.publish_log.exists() + + def is_changed(self): + if self.head is None: + return False + return not self.head.publishable + changed = cached_in_field('_changed')(is_changed) + + def is_hidden(self): + return self.book.hidden() + hidden = cached_in_field('_hidden')(is_hidden) + + @cached_in_field('_short_html') + def short_html(self): + return render_to_string( + 'catalogue/book_list/chunk.html', {'chunk': self}) + + def touch(self): + update = { + "_changed": self.is_changed(), + "_hidden": self.is_hidden(), + "_short_html": None, + } + Chunk.objects.filter(pk=self.pk).update(**update) + refresh_instance(self) + + def refresh(self): + """This should be done offline.""" + self.changed + self.hidden + self.short_html diff --git a/src/catalogue/models/image.py b/src/catalogue/models/image.py new file mode 100755 index 00000000..646dd0ae --- /dev/null +++ b/src/catalogue/models/image.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.conf import settings +from django.contrib.sites.models import Site +from django.db import models +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _ +from catalogue.helpers import cached_in_field +from catalogue.models import Project +from catalogue.tasks import refresh_instance +from dvcs import models as dvcs_models + + +class Image(dvcs_models.Document): + """ An editable chunk of text. Every Book text is divided into chunks. """ + REPO_PATH = settings.CATALOGUE_IMAGE_REPO_PATH + + image = models.FileField(_('image'), upload_to='catalogue/images') + title = models.CharField(_('title'), max_length=255, blank=True) + slug = models.SlugField(_('slug'), unique=True) + public = models.BooleanField(_('public'), default=True, db_index=True) + project = models.ForeignKey(Project, null=True, blank=True) + + # cache + _short_html = models.TextField(null=True, blank=True, editable=False) + _new_publishable = models.NullBooleanField(editable=False) + _published = models.NullBooleanField(editable=False) + _changed = models.NullBooleanField(editable=False) + + class Meta: + app_label = 'catalogue' + ordering = ['title'] + verbose_name = _('image') + verbose_name_plural = _('images') + permissions = [('can_pubmark_image', 'Can mark images for publishing')] + + # Representing + # ============ + + def __unicode__(self): + return self.title + + @models.permalink + def get_absolute_url(self): + return ("catalogue_image", [self.slug]) + + def correct_about(self): + return ["http://%s%s" % ( + Site.objects.get_current().domain, + self.get_absolute_url() + ), + "http://%s%s" % ( + 'obrazy.redakcja.wolnelektury.pl', + self.get_absolute_url() + )] + + # State & cache + # ============= + + def last_published(self): + try: + return self.publish_log.all()[0].timestamp + except IndexError: + return None + + def assert_publishable(self): + from librarian.picture import WLPicture + from librarian import NoDublinCore, ParseError, ValidationError + + class SelfImageStore(object): + def path(self_, slug, mime_type): + """Returns own file object. Ignores slug ad mime_type.""" + return open(self.image.path) + + publishable = self.publishable() + assert publishable, _("There is no publishable revision") + picture_xml = publishable.materialize() + + try: + picture = WLPicture.from_bytes( + picture_xml.encode('utf-8'), + image_store=SelfImageStore) + except ParseError, e: + raise AssertionError(_('Invalid XML') + ': ' + str(e)) + except NoDublinCore: + raise AssertionError(_('No Dublin Core found.')) + except ValidationError, e: + raise AssertionError(_('Invalid Dublin Core') + ': ' + str(e)) + + valid_about = self.correct_about() + assert picture.picture_info.about in valid_about, \ + _("rdf:about is not") + " " + valid_about[0] + + def publishable_error(self): + try: + return self.assert_publishable() + except AssertionError, e: + return e + else: + return None + + def accessible(self, request): + return self.public or request.user.is_authenticated() + + def is_new_publishable(self): + change = self.publishable() + if not change: + return False + return not change.publish_log.exists() + new_publishable = cached_in_field('_new_publishable')(is_new_publishable) + + def is_published(self): + return self.publish_log.exists() + published = cached_in_field('_published')(is_published) + + def is_changed(self): + if self.head is None: + return False + return not self.head.publishable + changed = cached_in_field('_changed')(is_changed) + + @cached_in_field('_short_html') + def short_html(self): + return render_to_string( + 'catalogue/image_short.html', {'image': self}) + + def refresh(self): + """This should be done offline.""" + self.short_html + self.single + self.new_publishable + self.published + + def touch(self): + update = { + "_changed": self.is_changed(), + "_short_html": None, + "_new_publishable": self.is_new_publishable(), + "_published": self.is_published(), + } + Image.objects.filter(pk=self.pk).update(**update) + refresh_instance(self) + + def refresh(self): + """This should be done offline.""" + self.changed + self.short_html + + + # Publishing + # ========== + + def publish(self, user): + """Publishes the picture on behalf of a (local) user.""" + from base64 import b64encode + import apiclient + from catalogue.signals import post_publish + + self.assert_publishable() + change = self.publishable() + picture_xml = change.materialize() + picture_data = open(self.image.path).read() + apiclient.api_call(user, "pictures/", { + "picture_xml": picture_xml, + "picture_image_data": b64encode(picture_data), + }) + # record the publish + log = self.publish_log.create(user=user, change=change) + post_publish.send(sender=log) diff --git a/src/catalogue/models/listeners.py b/src/catalogue/models/listeners.py new file mode 100755 index 00000000..1cfac276 --- /dev/null +++ b/src/catalogue/models/listeners.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.contrib.auth.models import User +from django.db import models +from catalogue.models import (Book, Chunk, Image, BookPublishRecord, + ImagePublishRecord) +from catalogue.signals import post_publish +from dvcs.signals import post_publishable + + +def book_changed(sender, instance, created, **kwargs): + instance.touch() + for c in instance: + c.touch() +models.signals.post_save.connect(book_changed, sender=Book) + + +def chunk_changed(sender, instance, created, **kwargs): + instance.book.touch() + instance.touch() +models.signals.post_save.connect(chunk_changed, sender=Chunk) + + +def image_changed(sender, instance, created, **kwargs): + instance.touch() +models.signals.post_save.connect(image_changed, sender=Image) + + +def user_changed(sender, instance, *args, **kwargs): + if 'last_login' in (kwargs.get('update_fields') or {}): + # Quick hack - this change seems to result from logging user in so just ignore it. + return + books = set() + for c in instance.chunk_set.all(): + books.add(c.book) + c.touch() + for b in books: + b.touch() +models.signals.post_save.connect(user_changed, sender=User) + + +def publish_listener(sender, *args, **kwargs): + if isinstance(sender, BookPublishRecord): + sender.book.touch() + for c in sender.book: + c.touch() + elif isinstance(sender, ImagePublishRecord): + sender.image.touch() +post_publish.connect(publish_listener) + + +def chunk_publishable_listener(sender, *args, **kwargs): + sender.tree.touch() + if isinstance(sender.tree, Chunk): + sender.tree.book.touch() +post_publishable.connect(chunk_publishable_listener) + +def publishable_listener(sender, *args, **kwargs): + sender.tree.touch() +post_publishable.connect(publishable_listener, sender=Image) + + +def listener_create(sender, instance, created, **kwargs): + if created: + instance.chunk_set.create(number=1, slug='1') +models.signals.post_save.connect(listener_create, sender=Book) + diff --git a/src/catalogue/models/project.py b/src/catalogue/models/project.py new file mode 100755 index 00000000..eb951021 --- /dev/null +++ b/src/catalogue/models/project.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class Project(models.Model): + """ A project, tracked for funding purposes. """ + + name = models.CharField(_('name'), max_length=255, unique=True) + notes = models.TextField(_('notes'), blank=True, null=True) + + class Meta: + app_label = 'catalogue' + ordering = ['name'] + verbose_name = _('project') + verbose_name_plural = _('projects') + + def __unicode__(self): + return self.name diff --git a/src/catalogue/models/publish_log.py b/src/catalogue/models/publish_log.py new file mode 100755 index 00000000..7a8e2f9e --- /dev/null +++ b/src/catalogue/models/publish_log.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.contrib.auth.models import User +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from catalogue.models import Chunk, Image + + +class BookPublishRecord(models.Model): + """ + A record left after publishing a Book. + """ + + book = models.ForeignKey('Book', verbose_name=_('book'), related_name='publish_log') + timestamp = models.DateTimeField(_('time'), auto_now_add=True) + user = models.ForeignKey(User, verbose_name=_('user')) + + class Meta: + app_label = 'catalogue' + ordering = ['-timestamp'] + verbose_name = _('book publish record') + verbose_name_plural = _('book publish records') + + +class ChunkPublishRecord(models.Model): + """ + BookPublishRecord details for each Chunk. + """ + + book_record = models.ForeignKey(BookPublishRecord, verbose_name=_('book publish record')) + change = models.ForeignKey(Chunk.change_model, related_name='publish_log', verbose_name=_('change')) + + class Meta: + app_label = 'catalogue' + verbose_name = _('chunk publish record') + verbose_name_plural = _('chunk publish records') + + +class ImagePublishRecord(models.Model): + """A record left after publishing an Image.""" + + image = models.ForeignKey(Image, verbose_name=_('image'), related_name='publish_log') + timestamp = models.DateTimeField(_('time'), auto_now_add=True) + user = models.ForeignKey(User, verbose_name=_('user')) + change = models.ForeignKey(Image.change_model, related_name='publish_log', verbose_name=_('change')) + + class Meta: + app_label = 'catalogue' + ordering = ['-timestamp'] + verbose_name = _('image publish record') + verbose_name_plural = _('image publish records') diff --git a/src/catalogue/signals.py b/src/catalogue/signals.py new file mode 100644 index 00000000..62ca5145 --- /dev/null +++ b/src/catalogue/signals.py @@ -0,0 +1,3 @@ +from django.dispatch import Signal + +post_publish = Signal() diff --git a/src/catalogue/tasks.py b/src/catalogue/tasks.py new file mode 100644 index 00000000..9507c412 --- /dev/null +++ b/src/catalogue/tasks.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from celery.task import task +from django.utils import translation + + +@task(ignore_result=True) +def _refresh_by_pk(cls, pk, language=None): + prev_language = translation.get_language() + if language: + translation.activate(language) + try: + cls._default_manager.get(pk=pk).refresh() + except cls.DoesNotExist: + pass + finally: + translation.activate(prev_language) + + +def refresh_instance(instance): + _refresh_by_pk.delay(type(instance), instance.pk, translation.get_language()) + + +@task(ignore_result=True) +def book_content_updated(book): + book.refresh_dc_cache() diff --git a/src/catalogue/templates/catalogue/active_users_list.html b/src/catalogue/templates/catalogue/active_users_list.html new file mode 100755 index 00000000..f711b605 --- /dev/null +++ b/src/catalogue/templates/catalogue/active_users_list.html @@ -0,0 +1,20 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + + +{% block titleextra %}{% trans "Active users" %}{% endblock %} + + +{% block content %} + +

    + {% trans "Active users since" %} {{ since }} +

    + +
      +{% for email, names, count in users %} +
    • {% for name in names %}{{ name }}, {% endfor %}{{ email }} ({{ count }})
    • +{% endfor %} +
    + +{% endblock content %} diff --git a/src/catalogue/templates/catalogue/activity.html b/src/catalogue/templates/catalogue/activity.html new file mode 100755 index 00000000..3bb8afb2 --- /dev/null +++ b/src/catalogue/templates/catalogue/activity.html @@ -0,0 +1,19 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} +{% load wall %} + + +{% block titleextra %}{% trans "Activity" %}{% endblock %} + + +{% block content %} + +

    < + {% trans "Activity" %}: {{ day }} + {% if next_day %} + > + {% endif %} +

    + + {% day_wall day %} +{% endblock content %} diff --git a/src/catalogue/templates/catalogue/base.html b/src/catalogue/templates/catalogue/base.html new file mode 100644 index 00000000..8577f104 --- /dev/null +++ b/src/catalogue/templates/catalogue/base.html @@ -0,0 +1,54 @@ +{% load pipeline i18n %} +{% load catalogue %} + + + + + + {% stylesheet 'catalogue' %} + {% block title %}{% block titleextra %}{% endblock %} :: + {% trans "Platforma Redakcyjna" %}{% endblock title %} + {% block add_css %}{% endblock %} + + + +
    + + + + + +
    + {% main_tabs %} +
    + + + {% include "registration/head_login.html" %} + + +
    +
    + +
    + +{% block content %} +
    + {% block leftcolumn %} + {% endblock leftcolumn %} +
    +
    + {% block rightcolumn %} + {% endblock rightcolumn %} +
    +{% endblock content %} + +
    + + + +{% javascript 'catalogue' %} +{% block add_js %}{% endblock %} +{% block extrabody %} +{% endblock %} + + diff --git a/src/catalogue/templates/catalogue/book_append_to.html b/src/catalogue/templates/catalogue/book_append_to.html new file mode 100755 index 00000000..c1ecc290 --- /dev/null +++ b/src/catalogue/templates/catalogue/book_append_to.html @@ -0,0 +1,16 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + +{% block titleextra %}{% trans "Append book" %}{% endblock %} + +{% block leftcolumn %} +
    + {% csrf_token %} + {{ form.as_p }} + +

    +
    +{% endblock leftcolumn %} + +{% block rightcolumn %} +{% endblock rightcolumn %} diff --git a/src/catalogue/templates/catalogue/book_detail.html b/src/catalogue/templates/catalogue/book_detail.html new file mode 100755 index 00000000..4712edf3 --- /dev/null +++ b/src/catalogue/templates/catalogue/book_detail.html @@ -0,0 +1,103 @@ +{% extends "catalogue/base.html" %} +{% load book_list i18n %} + + +{% block titleextra %}{{ book.title }}{% endblock %} + + +{% block content %} + + +

    {{ book.title }}

    + + +{% if editable %}
    {% csrf_token %}{% endif %} + + {{ form.as_table }} + {% if editable %} + + {% endif %} +
    +{% if editable %}
    {% endif %} + +{% if editable %} + {% if book.gallery %} +

    {% trans "Edit gallery" %}

    + {% endif %} + +

    {% trans "Append to other book" %}

    +{% endif %} + + +
    + +

    {% trans "Chunks" %}

    + + + {% for chunk in book %} + {{ chunk.short_html|safe }} + {% endfor %} +
    + +
    + + + +
    + + +

    {% trans "Publication" %}

    + +
    + +{% if book.dc_cover_image %} + {{ book.dc_cover_image }} +{% endif %} +
    + +

    {% trans "Last published" %}: + {% if book.last_published %} + {{ book.last_published }} + {% else %} + — + {% endif %} +

    + +{% if publishable %} +

    + {% trans "Full XML" %}
    + {% trans "HTML version" %}
    + {% trans "TXT version" %}
    + {% trans "PDF version" %}
    + {% trans "PDF version for mobiles" %}
    + {% trans "EPUB version" %}
    + {% trans "MOBI version" %}
    +

    + + {% if user.is_authenticated %} + +
    {% csrf_token %} + {{ publish_options_form.as_p }} + + + +
    + {% else %} + {% trans "Log in to publish." %} + {% endif %} +{% else %} +

    {% trans "This book can't be published yet, because:" %}

    +
    • {{ publishable_error }}
    +{% endif %} + +
    +
    + + +{% endblock content %} diff --git a/src/catalogue/templates/catalogue/book_edit.html b/src/catalogue/templates/catalogue/book_edit.html new file mode 100755 index 00000000..43fe0ea9 --- /dev/null +++ b/src/catalogue/templates/catalogue/book_edit.html @@ -0,0 +1,18 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + + +{% block titleextra %}{% trans "Edit book" %}{% endblock %} + + +{% block leftcolumn %} +
    + {% csrf_token %} + {{ form.as_p }} + +

    +
    +{% endblock leftcolumn %} + +{% block rightcolumn %} +{% endblock rightcolumn %} diff --git a/src/catalogue/templates/catalogue/book_html.html b/src/catalogue/templates/catalogue/book_html.html new file mode 100755 index 00000000..518811ee --- /dev/null +++ b/src/catalogue/templates/catalogue/book_html.html @@ -0,0 +1,29 @@ +{% load i18n %} + + + + + {{ book.title }} + + + +
    + {#% book_info book %#} +
    + + + {{ html|safe }} + + + diff --git a/src/catalogue/templates/catalogue/book_list/book.html b/src/catalogue/templates/catalogue/book_list/book.html new file mode 100755 index 00000000..f6a0fcd2 --- /dev/null +++ b/src/catalogue/templates/catalogue/book_list/book.html @@ -0,0 +1,40 @@ +{% load i18n %} +{% load username from common_tags %} + +{% if book.single %} + {% with book.0 as chunk %} + + + [B] + [c] + + {{ book.title }} + {% if chunk.stage %} + {{ chunk.stage }} + {% else %}– + {% endif %} + {% if chunk.user %}{{ chunk.user|username }}{% endif %} + + {% if chunk.published %}P{% endif %} + {% if book.new_publishable %}p{% endif %} + {% if chunk.changed %}+{% endif %} + + {{ book.project.name }} + + {% endwith %} +{% else %} + + + [B] + + {{ book.title }} + + + + {% if book.published %}P{% endif %} + {% if book.new_publishable %}p{% endif %} + + {{ book.project.name }} + +{% endif %} diff --git a/src/catalogue/templates/catalogue/book_list/book_list.html b/src/catalogue/templates/catalogue/book_list/book_list.html new file mode 100755 index 00000000..e238827b --- /dev/null +++ b/src/catalogue/templates/catalogue/book_list/book_list.html @@ -0,0 +1,114 @@ +{% load i18n %} +{% load pagination_tags %} +{% load username from common_tags %} + + +
    + + +{% if not viewed_user %} + +{% endif %} + + + +
    + + + + + + + + + + + {% if not viewed_user %} + + {% else %} + + {% endif %} + + + + + + + + {% with cnt=books|length %} + {% autopaginate books 100 %} + + {% for item in books %} + {% with item.book as book %} + {{ book.short_html|safe }} + {% if not book.single %} + {% for chunk in item.chunks %} + {{ chunk.short_html|safe }} + {% endfor %} + {% endif %} + {% endwith %} + {% endfor %} + + + {% endwith %} +
    + + +
    + +
    +
    + {% paginate %} + {% blocktrans count c=cnt %}{{c}} book{% plural %}{{c}} books{% endblocktrans %}
    +{% if not books %} +

    {% trans "No books found." %}

    +{% endif %} + + + + diff --git a/src/catalogue/templates/catalogue/book_list/chunk.html b/src/catalogue/templates/catalogue/book_list/chunk.html new file mode 100755 index 00000000..0d218954 --- /dev/null +++ b/src/catalogue/templates/catalogue/book_list/chunk.html @@ -0,0 +1,27 @@ +{% load i18n %} +{% load username from common_tags %} + + + + + [c] + + {{ chunk.number }}. + {{ chunk.title }} + {% if chunk.stage %} + {{ chunk.stage }} + {% else %} + – + {% endif %} + {% if chunk.user %} + + {{ chunk.user|username }} + {% else %} + + {% endif %} + + + {% if chunk.new_publishable %}p{% endif %} + {% if chunk.changed %}+{% endif %} + + diff --git a/src/catalogue/templates/catalogue/book_text.html b/src/catalogue/templates/catalogue/book_text.html new file mode 100644 index 00000000..2e484483 --- /dev/null +++ b/src/catalogue/templates/catalogue/book_text.html @@ -0,0 +1,28 @@ +{% load i18n pipeline %} + + + + + {% trans "Redakcja" %} :: {{ book.title }} + {% stylesheet 'book' %} + + {% javascript 'book' %} + + + + + {{ html|safe }} + + diff --git a/src/catalogue/templates/catalogue/chunk_add.html b/src/catalogue/templates/catalogue/chunk_add.html new file mode 100755 index 00000000..f813b6f3 --- /dev/null +++ b/src/catalogue/templates/catalogue/chunk_add.html @@ -0,0 +1,20 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + + +{% block titleextra %}{% trans "Split chunk" %}{% endblock %} + + +{% block content %} +

    {% trans "Split chunk" %}

    + +
    + {% csrf_token %} + + + + {{ form.as_table }} + +
    {% trans "Insert empty chunk after" %}:{{ chunk.pretty_name }}
    +
    +{% endblock content %} diff --git a/src/catalogue/templates/catalogue/chunk_edit.html b/src/catalogue/templates/catalogue/chunk_edit.html new file mode 100755 index 00000000..20062265 --- /dev/null +++ b/src/catalogue/templates/catalogue/chunk_edit.html @@ -0,0 +1,24 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + + +{% block titleextra %}{% trans "Chunk settings" %}{% endblock %} + + +{% block content %} +

    {% trans "Chunk settings" %}

    + +
    + {% csrf_token %} + + + {{ form.as_table}} + +
    {% trans "Book" %}:{{ chunk.book }} ({{ chunk.number }}/{{ chunk.book|length }})
    + +
    + + +

    {% trans "Split chunk" %}

    + +{% endblock content %} diff --git a/src/catalogue/templates/catalogue/document_create_missing.html b/src/catalogue/templates/catalogue/document_create_missing.html new file mode 100644 index 00000000..aa2ce06c --- /dev/null +++ b/src/catalogue/templates/catalogue/document_create_missing.html @@ -0,0 +1,18 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + + +{% block titleextra %}{% trans "Create a new book" %}{% endblock %} + + +{% block content %} +

    {% trans "Create a new book" %}

    + +
    + {% csrf_token %} + + {{ form.as_table}} + +
    +
    +{% endblock content %} diff --git a/src/catalogue/templates/catalogue/document_list.html b/src/catalogue/templates/catalogue/document_list.html new file mode 100644 index 00000000..fe3598e1 --- /dev/null +++ b/src/catalogue/templates/catalogue/document_list.html @@ -0,0 +1,20 @@ +{% extends "catalogue/base.html" %} + +{% load i18n %} +{% load catalogue book_list %} +{% load pipeline %} + +{% block titleextra %}{% trans "Book list" %}{% endblock %} + + +{% block add_js %} +{% javascript 'book_list' %} +{% endblock %} + +{% block add_css %} +{% stylesheet 'book_list' %} +{% endblock %} + +{% block content %} + {% book_list %} +{% endblock content %} diff --git a/src/catalogue/templates/catalogue/document_upload.html b/src/catalogue/templates/catalogue/document_upload.html new file mode 100644 index 00000000..009d1540 --- /dev/null +++ b/src/catalogue/templates/catalogue/document_upload.html @@ -0,0 +1,72 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + + +{% block titleextra %}{% trans "Bulk document upload" %}{% endblock %} + + +{% block leftcolumn %} + + +

    {% trans "Bulk documents upload" %}

    + +

    +{% trans "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with .xml will be ignored." %} +

    + +
    +{% csrf_token %} +{{ form.as_p }} +

    +
    + +
    + +{% if error_list %} + +

    {% trans "There have been some errors. No files have been added to the repository." %} +

    {% trans "Offending files" %}

    +
      + {% for filename, title, error in error_list %} +
    • {{ title }} ({{ filename }}): {{ error }}
    • + {% endfor %} +
    + + {% if ok_list %} +

    {% trans "Correct files" %}

    +
      + {% for filename, slug, title in ok_list %} +
    • {{ title }} ({{ filename }})
    • + {% endfor %} +
    + {% endif %} + +{% else %} + + {% if ok_list %} +

    {% trans "Files have been successfully uploaded to the repository." %}

    +

    {% trans "Uploaded files" %}

    +
      + {% for filename, slug, title in ok_list %} +
    • {{ title }} ({{ filename }})
    • + {% endfor %} +
    + {% endif %} +{% endif %} + +{% if skipped_list %} +

    {% trans "Skipped files" %}

    +

    {% trans "Files skipped due to no .xml extension" %}

    +
      + {% for filename in skipped_list %} +
    • {{ filename }}
    • + {% endfor %} +
    +{% endif %} + + +{% endblock leftcolumn %} + + +{% block rightcolumn %} +{% endblock rightcolumn %} diff --git a/src/catalogue/templates/catalogue/image_detail.html b/src/catalogue/templates/catalogue/image_detail.html new file mode 100755 index 00000000..8ad2a63f --- /dev/null +++ b/src/catalogue/templates/catalogue/image_detail.html @@ -0,0 +1,71 @@ +{% extends "catalogue/base.html" %} +{% load book_list i18n %} + + +{% block titleextra %}{{ object.title }}{% endblock %} + + +{% block content %} + + +

    {{ object.title }}

    + + +{% if editable %}
    {% csrf_token %}{% endif %} + + {{ form.as_table }} + {% if editable %} + + {% endif %} +
    +{% if editable %}
    {% endif %} + + + +
    +

    {% trans "Editor" %}

    + +

    {% trans "Proceed to the editor." %}

    +
    + + + +
    + + +

    {% trans "Publication" %}

    + +

    {% trans "Last published" %}: + {% if object.last_published %} + {{ object.last_published }} + {% else %} + — + {% endif %} +

    + +{% if publishable %} + {% if user.is_authenticated %} + +
    {% csrf_token %} + + + +
    + {% else %} + {% trans "Log in to publish." %} + {% endif %} +{% else %} +

    {% trans "This book can't be published yet, because:" %}

    +
    • {{ publishable_error }}
    +{% endif %} + +
    + + +{% endblock content %} diff --git a/src/catalogue/templates/catalogue/image_list.html b/src/catalogue/templates/catalogue/image_list.html new file mode 100755 index 00000000..4ce1668a --- /dev/null +++ b/src/catalogue/templates/catalogue/image_list.html @@ -0,0 +1,22 @@ +{% extends "catalogue/base.html" %} + +{% load i18n %} +{% load catalogue book_list %} +{% load pipeline %} + + +{% block titleextra %}{% trans "Image list" %}{% endblock %} + + +{% block add_js %} +{% javascript 'book_list' %} +{% endblock %} + +{% block add_css %} +{% stylesheet 'book_list' %} +{% endblock %} + + +{% block content %} + {% image_list %} +{% endblock content %} diff --git a/src/catalogue/templates/catalogue/image_short.html b/src/catalogue/templates/catalogue/image_short.html new file mode 100755 index 00000000..c7ff77b7 --- /dev/null +++ b/src/catalogue/templates/catalogue/image_short.html @@ -0,0 +1,21 @@ +{% load i18n %} +{% load username from common_tags %} + + + + [B] + + {{ image.title }} + {% if image.stage %} + {{ image.stage }} + {% else %}– + {% endif %} + {% if image.user %}{{ image.user|username }}{% endif %} + + {% if image.published %}P{% endif %} + {% if image.new_publishable %}p{% endif %} + {% if image.changed %}+{% endif %} + + {{ image.project.name }} + diff --git a/src/catalogue/templates/catalogue/image_table.html b/src/catalogue/templates/catalogue/image_table.html new file mode 100755 index 00000000..e6caedda --- /dev/null +++ b/src/catalogue/templates/catalogue/image_table.html @@ -0,0 +1,100 @@ +{% load i18n %} +{% load pagination_tags %} +{% load username from common_tags %} + + + +
    + + +{% if not viewed_user %} + +{% endif %} + + +
    + + + + + + + + + {% if not viewed_user %} + + {% endif %} + + + + + + + + {% with cnt=objects|length %} + {% autopaginate objects 100 %} + + {% for item in objects %} + {{ item.short_html|safe }} + {% endfor %} + + + {% endwith %} +
    +
    + +
    +
    + {% paginate %} + {% blocktrans count c=cnt %}{{c}} image{% plural %}{{c}} images{% endblocktrans %}
    +{% if not objects %} +

    {% trans "No images found." %}

    +{% endif %} + + + + diff --git a/src/catalogue/templates/catalogue/main_tabs.html b/src/catalogue/templates/catalogue/main_tabs.html new file mode 100755 index 00000000..82321cc4 --- /dev/null +++ b/src/catalogue/templates/catalogue/main_tabs.html @@ -0,0 +1,3 @@ +{% for tab in tabs %} + {{ tab.caption }} +{% endfor %} diff --git a/src/catalogue/templates/catalogue/mark_final.html b/src/catalogue/templates/catalogue/mark_final.html new file mode 100644 index 00000000..9ed740c0 --- /dev/null +++ b/src/catalogue/templates/catalogue/mark_final.html @@ -0,0 +1,16 @@ +{% extends "catalogue/base.html" %} + +{% block titleextra %}Oznacz książki{% endblock %} + + +{% block leftcolumn %} + +

    Oznacz książki

    + +
    + {% csrf_token %} + {{ form.as_p }} + +
    + +{% endblock leftcolumn %} \ No newline at end of file diff --git a/src/catalogue/templates/catalogue/mark_final_completed.html b/src/catalogue/templates/catalogue/mark_final_completed.html new file mode 100644 index 00000000..1b37c836 --- /dev/null +++ b/src/catalogue/templates/catalogue/mark_final_completed.html @@ -0,0 +1,12 @@ +{% extends "catalogue/base.html" %} + +{% block titleextra %}Oznaczono książki{% endblock %} + + +{% block leftcolumn %} + +

    Oznaczono książki

    + +

    Książki zostały oznaczone.

    + +{% endblock leftcolumn %} \ No newline at end of file diff --git a/src/catalogue/templates/catalogue/my_page.html b/src/catalogue/templates/catalogue/my_page.html new file mode 100755 index 00000000..d3347503 --- /dev/null +++ b/src/catalogue/templates/catalogue/my_page.html @@ -0,0 +1,41 @@ +{% extends "catalogue/base.html" %} + +{% load i18n %} +{% load catalogue book_list wall %} +{% load pipeline %} + +{% block add_js %} +{% javascript 'book_list' %} +{% endblock %} + +{% block add_css %} +{% stylesheet 'book_list' %} +{% endblock %} + +{% block titleextra %}{% trans "My page" %}{% endblock %} + + +{% block leftcolumn %} + {% book_list request.user %} +{% endblock leftcolumn %} + +{% block rightcolumn %} +
    +

    {% trans "Your last edited documents" %}

    +
      + {% for edit_url, item in last_books %} +
    1. {{ item.title }}
      ({{ item.time|date:"H:i:s, d/m/Y" }})
    2. + {% endfor %} +
    +
    + +

    {% trans "Recent activity for" %} {{ request.user|nice_name }}

    + {% wall request.user 10 %} +{% endblock rightcolumn %} diff --git a/src/catalogue/templates/catalogue/upload_pdf.html b/src/catalogue/templates/catalogue/upload_pdf.html new file mode 100755 index 00000000..265b84ad --- /dev/null +++ b/src/catalogue/templates/catalogue/upload_pdf.html @@ -0,0 +1,20 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + + +{% block titleextra %}{% trans "PDF file upload" %}{% endblock %} + + +{% block content %} + + +

    {% trans "PDF file upload" %}

    + +
    +{% csrf_token %} +{{ form.as_p }} +

    +
    + + +{% endblock content %} diff --git a/src/catalogue/templates/catalogue/user_list.html b/src/catalogue/templates/catalogue/user_list.html new file mode 100755 index 00000000..460a38b9 --- /dev/null +++ b/src/catalogue/templates/catalogue/user_list.html @@ -0,0 +1,23 @@ +{% extends "catalogue/base.html" %} + +{% load i18n %} +{% load username from common_tags %} + + +{% block titleextra %}{% trans "Users" %}{% endblock %} + + +{% block leftcolumn %} + +

    {% trans "Users" %}

    + + + +{% endblock leftcolumn %} diff --git a/src/catalogue/templates/catalogue/user_page.html b/src/catalogue/templates/catalogue/user_page.html new file mode 100755 index 00000000..4be4ca3e --- /dev/null +++ b/src/catalogue/templates/catalogue/user_page.html @@ -0,0 +1,18 @@ +{% extends "catalogue/base.html" %} + +{% load i18n %} +{% load catalogue book_list wall %} + + +{% block titleextra %}{{ viewed_user|nice_name }}{% endblock %} + + +{% block leftcolumn %} +

    {{ viewed_user|nice_name }}

    + {% book_list viewed_user %} +{% endblock leftcolumn %} + +{% block rightcolumn %} +

    {% trans "Recent activity for" %} {{ viewed_user|nice_name }}

    + {% wall viewed_user 10 %} +{% endblock rightcolumn %} diff --git a/src/catalogue/templates/catalogue/wall.html b/src/catalogue/templates/catalogue/wall.html new file mode 100755 index 00000000..a107dfa8 --- /dev/null +++ b/src/catalogue/templates/catalogue/wall.html @@ -0,0 +1,37 @@ +{% load i18n %} +{% load gravatar %} +{% load email %} +{% load username from common_tags %} + +
      +{% for item in wall %} +
    • +
      + {% if item.get_email %} + Avatar +
      + {% endif %} +
      + +
      {{ item.timestamp }}
      +

      {{ item.header }}

      + {{ item.title }} +
      {% trans "user" %}: + {% if item.user %} + + {{ item.user|username }} + <{{ item.user.email|email_link }}> + {% else %} + {{ item.user_name }} + {% if item.email %} + <{{ item.email|email_link }}> + {% endif %} + ({% trans "not logged in" %}) + {% endif %} +
      {{ item.summary|linebreaksbr }} +
    • +{% empty %} +
    • {% trans "No activity recorded." %}
    • +{% endfor %} +
    diff --git a/src/catalogue/templatetags/__init__.py b/src/catalogue/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/catalogue/templatetags/book_list.py b/src/catalogue/templatetags/book_list.py new file mode 100755 index 00000000..9ac996b8 --- /dev/null +++ b/src/catalogue/templatetags/book_list.py @@ -0,0 +1,209 @@ +from __future__ import absolute_import + +from re import split +from django.db.models import Q, Count +from django import template +from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import User +from catalogue.models import Chunk, Image, Project + +register = template.Library() + + +class ChunksList(object): + def __init__(self, chunk_qs): + #self.chunk_qs = chunk_qs#.annotate( + #book_length=Count('book__chunk')).select_related( + #'book')#, 'stage__name', + #'user') + self.chunk_qs = chunk_qs.select_related('book__hidden') + + self.book_qs = chunk_qs.values('book_id') + + def __getitem__(self, key): + if isinstance(key, slice): + return self.get_slice(key) + elif isinstance(key, int): + return self.get_slice(slice(key, key+1))[0] + else: + raise TypeError('Unsupported list index. Must be a slice or an int.') + + def __len__(self): + return self.book_qs.count() + + def get_slice(self, slice_): + book_ids = [x['book_id'] for x in self.book_qs[slice_]] + chunk_qs = self.chunk_qs.filter(book__in=book_ids) + + chunks_list = [] + book = None + for chunk in chunk_qs: + if chunk.book != book: + book = chunk.book + chunks_list.append(ChoiceChunks(book, [chunk])) + else: + chunks_list[-1].chunks.append(chunk) + return chunks_list + + +class ChoiceChunks(object): + """ + Associates the given chunks iterable for a book. + """ + + chunks = None + + def __init__(self, book, chunks): + self.book = book + self.chunks = chunks + + +def foreign_filter(qs, value, filter_field, model, model_field='slug', unset='-'): + if value == unset: + return qs.filter(**{filter_field: None}) + if not value: + return qs + try: + obj = model._default_manager.get(**{model_field: value}) + except model.DoesNotExist: + return qs.none() + else: + return qs.filter(**{filter_field: obj}) + + +def search_filter(qs, value, filter_fields): + if not value: + return qs + q = Q(**{"%s__icontains" % filter_fields[0]: value}) + for field in filter_fields[1:]: + q |= Q(**{"%s__icontains" % field: value}) + return qs.filter(q) + + +_states = [ + ('publishable', _('publishable'), Q(book___new_publishable=True)), + ('changed', _('changed'), Q(_changed=True)), + ('published', _('published'), Q(book___published=True)), + ('unpublished', _('unpublished'), Q(book___published=False)), + ('empty', _('empty'), Q(head=None)), + ] +_states_options = [s[:2] for s in _states] +_states_dict = dict([(s[0], s[2]) for s in _states]) + + +def document_list_filter(request, **kwargs): + + def arg_or_GET(field): + return kwargs.get(field, request.GET.get(field)) + + if arg_or_GET('all'): + chunks = Chunk.objects.all() + else: + chunks = Chunk.visible_objects.all() + + chunks = chunks.order_by('book__title', 'book', 'number') + + if not request.user.is_authenticated(): + chunks = chunks.filter(book__public=True) + + state = arg_or_GET('status') + if state in _states_dict: + chunks = chunks.filter(_states_dict[state]) + + chunks = foreign_filter(chunks, arg_or_GET('user'), 'user', User, 'username') + chunks = foreign_filter(chunks, arg_or_GET('stage'), 'stage', Chunk.tag_model, 'slug') + chunks = search_filter(chunks, arg_or_GET('title'), ['book__title', 'title']) + chunks = foreign_filter(chunks, arg_or_GET('project'), 'book__project', Project, 'pk') + return chunks + + +@register.inclusion_tag('catalogue/book_list/book_list.html', takes_context=True) +def book_list(context, user=None): + request = context['request'] + + if user: + filters = {"user": user} + new_context = {"viewed_user": user} + else: + filters = {} + new_context = { + "users": User.objects.annotate( + count=Count('chunk')).filter(count__gt=0).order_by( + '-count', 'last_name', 'first_name'), + "other_users": User.objects.annotate( + count=Count('chunk')).filter(count=0).order_by( + 'last_name', 'first_name'), + } + + new_context.update({ + "filters": True, + "request": request, + "books": ChunksList(document_list_filter(request, **filters)), + "stages": Chunk.tag_model.objects.all(), + "states": _states_options, + "projects": Project.objects.all(), + }) + + return new_context + + + +_image_states = [ + ('publishable', _('publishable'), Q(_new_publishable=True)), + ('changed', _('changed'), Q(_changed=True)), + ('published', _('published'), Q(_published=True)), + ('unpublished', _('unpublished'), Q(_published=False)), + ('empty', _('empty'), Q(head=None)), + ] +_image_states_options = [s[:2] for s in _image_states] +_image_states_dict = dict([(s[0], s[2]) for s in _image_states]) + +def image_list_filter(request, **kwargs): + + def arg_or_GET(field): + return kwargs.get(field, request.GET.get(field)) + + images = Image.objects.all() + + if not request.user.is_authenticated(): + images = images.filter(public=True) + + state = arg_or_GET('status') + if state in _image_states_dict: + images = images.filter(_image_states_dict[state]) + + images = foreign_filter(images, arg_or_GET('user'), 'user', User, 'username') + images = foreign_filter(images, arg_or_GET('stage'), 'stage', Image.tag_model, 'slug') + images = search_filter(images, arg_or_GET('title'), ['title', 'title']) + images = foreign_filter(images, arg_or_GET('project'), 'project', Project, 'pk') + return images + + +@register.inclusion_tag('catalogue/image_table.html', takes_context=True) +def image_list(context, user=None): + request = context['request'] + + if user: + filters = {"user": user} + new_context = {"viewed_user": user} + else: + filters = {} + new_context = { + "users": User.objects.annotate( + count=Count('image')).filter(count__gt=0).order_by( + '-count', 'last_name', 'first_name'), + "other_users": User.objects.annotate( + count=Count('image')).filter(count=0).order_by( + 'last_name', 'first_name'), + } + + new_context.update({ + "filters": True, + "request": request, + "objects": image_list_filter(request, **filters), + "stages": Image.tag_model.objects.all(), + "states": _image_states_options, + "projects": Project.objects.all(), + }) + + return new_context diff --git a/src/catalogue/templatetags/catalogue.py b/src/catalogue/templatetags/catalogue.py new file mode 100644 index 00000000..07c5cf9d --- /dev/null +++ b/src/catalogue/templatetags/catalogue.py @@ -0,0 +1,46 @@ +from __future__ import absolute_import + +from django.core.urlresolvers import reverse +from django import template +from django.utils.translation import ugettext as _ + +register = template.Library() + + +class Tab(object): + slug = None + caption = None + url = None + + def __init__(self, slug, caption, url): + self.slug = slug + self.caption = caption + self.url = url + + +@register.inclusion_tag("catalogue/main_tabs.html", takes_context=True) +def main_tabs(context): + active = getattr(context['request'], 'catalogue_active_tab', None) + + tabs = [] + user = context['user'] + tabs.append(Tab('my', _('My page'), reverse("catalogue_user"))) + + tabs.append(Tab('activity', _('Activity'), reverse("catalogue_activity"))) + tabs.append(Tab('all', _('All'), reverse("catalogue_document_list"))) + tabs.append(Tab('images', _('Images'), reverse("catalogue_image_list"))) + tabs.append(Tab('users', _('Users'), reverse("catalogue_users"))) + + if user.has_perm('catalogue.add_book'): + tabs.append(Tab('create', _('Add'), reverse("catalogue_create_missing"))) + tabs.append(Tab('upload', _('Upload'), reverse("catalogue_upload"))) + + tabs.append(Tab('cover', _('Covers'), reverse("cover_image_list"))) + + return {"tabs": tabs, "active_tab": active} + + +@register.filter +def nice_name(user): + return user.get_full_name() or user.username + diff --git a/src/catalogue/templatetags/common_tags.py b/src/catalogue/templatetags/common_tags.py new file mode 100755 index 00000000..7f5d0e9f --- /dev/null +++ b/src/catalogue/templatetags/common_tags.py @@ -0,0 +1,6 @@ +from django import template +register = template.Library() + +@register.filter +def username(user): + return ("%s %s" % (user.first_name, user.last_name)).lstrip() or user.username diff --git a/src/catalogue/templatetags/set_get_parameter.py b/src/catalogue/templatetags/set_get_parameter.py new file mode 100755 index 00000000..b3d44d73 --- /dev/null +++ b/src/catalogue/templatetags/set_get_parameter.py @@ -0,0 +1,46 @@ +from re import split + +from django import template + +register = template.Library() + + +""" +In template: + {% set_get_paramater param1='const_value',param2=,param3=variable %} +results with changes to query string: + param1 is set to `const_value' string + param2 is unset, if exists, + param3 is set to the value of variable in context + +Using 'django.core.context_processors.request' is required. + +""" + + +class SetGetParameter(template.Node): + def __init__(self, values): + self.values = values + + def render(self, context): + request = template.Variable('request').resolve(context) + params = request.GET.copy() + for key, value in self.values.items(): + if value == '': + if key in params: + del(params[key]) + else: + params[key] = template.Variable(value).resolve(context) + return '?%s' % params.urlencode() + + +@register.tag +def set_get_parameter(parser, token): + parts = split(r'\s+', token.contents, 2) + + values = {} + for pair in parts[1].split(','): + s = pair.split('=') + values[s[0]] = s[1] + + return SetGetParameter(values) diff --git a/src/catalogue/templatetags/wall.py b/src/catalogue/templatetags/wall.py new file mode 100755 index 00000000..d000421a --- /dev/null +++ b/src/catalogue/templatetags/wall.py @@ -0,0 +1,210 @@ +from __future__ import absolute_import + +from datetime import timedelta +from django.db.models import Q +from django.core.urlresolvers import reverse +from django.contrib.comments.models import Comment +from django import template +from django.utils.translation import ugettext as _ + +from catalogue.models import Chunk, BookPublishRecord, Image, ImagePublishRecord + +register = template.Library() + + +class WallItem(object): + title = '' + summary = '' + url = '' + timestamp = '' + user = None + user_name = '' + 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(user=None, max_len=None, day=None): + qs = Chunk.change_model.objects.order_by('-created_at') + qs = qs.select_related('author', 'tree', 'tree__book__title') + if user is not None: + qs = qs.filter(Q(author=user) | Q(tree__user=user)) + if max_len is not None: + qs = qs[:max_len] + if day is not None: + next_day = day + timedelta(1) + qs = qs.filter(created_at__gte=day, created_at__lt=next_day) + for item in qs: + tag = 'stage' if item.tags.count() else 'change' + chunk = item.tree + w = WallItem(tag) + if user and item.author != user: + w.header = _('Related edit') + else: + w.header = _('Edit') + 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.user_name = item.author_name + w.email = item.author_email + yield w + + +def image_changes_wall(user=None, max_len=None, day=None): + qs = Image.change_model.objects.order_by('-created_at') + qs = qs.select_related('author', 'tree', 'tree__title') + if user is not None: + qs = qs.filter(Q(author=user) | Q(tree__user=user)) + if max_len is not None: + qs = qs[:max_len] + if day is not None: + next_day = day + timedelta(1) + qs = qs.filter(created_at__gte=day, created_at__lt=next_day) + for item in qs: + tag = 'stage' if item.tags.count() else 'change' + image = item.tree + w = WallItem(tag) + if user and item.author != user: + w.header = _('Related edit') + else: + w.header = _('Edit') + w.title = image.title + w.summary = item.description + w.url = reverse('wiki_img_editor', + args=[image.slug]) + '?diff=%d' % item.revision + w.timestamp = item.created_at + w.user = item.author + w.user_name = item.author_name + w.email = item.author_email + yield w + + + +# TODO: marked for publishing + + +def published_wall(user=None, max_len=None, day=None): + qs = BookPublishRecord.objects.select_related('book__title') + if user: + # TODO: published my book + qs = qs.filter(Q(user=user)) + if max_len is not None: + qs = qs[:max_len] + if day is not None: + next_day = day + timedelta(1) + qs = qs.filter(timestamp__gte=day, timestamp__lt=next_day) + for item in qs: + w = WallItem('publish') + w.header = _('Publication') + w.title = item.book.title + w.timestamp = item.timestamp + w.url = item.book.get_absolute_url() + w.user = item.user + w.email = item.user.email + yield w + + +def image_published_wall(user=None, max_len=None, day=None): + qs = ImagePublishRecord.objects.select_related('image__title') + if user: + # TODO: published my book + qs = qs.filter(Q(user=user)) + if max_len is not None: + qs = qs[:max_len] + if day is not None: + next_day = day + timedelta(1) + qs = qs.filter(timestamp__gte=day, timestamp__lt=next_day) + for item in qs: + w = WallItem('publish') + w.header = _('Publication') + w.title = item.image.title + w.timestamp = item.timestamp + w.url = item.image.get_absolute_url() + w.user = item.user + w.email = item.user.email + yield w + + +def comments_wall(user=None, max_len=None, day=None): + qs = Comment.objects.filter(is_public=True).select_related().order_by('-submit_date') + if user: + # TODO: comments concerning my books + qs = qs.filter(Q(user=user)) + if max_len is not None: + qs = qs[:max_len] + if day is not None: + next_day = day + timedelta(1) + qs = qs.filter(submit_date__gte=day, submit_date__lt=next_day) + for item in qs: + w = WallItem('comment') + w.header = _('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 + ui = item.userinfo + w.email = item.email + w.user_name = item.name + yield w + + +def big_wall(walls, max_len=None): + """ + Takes some WallItem iterators and zips them into one big wall. + Input iterators must already be sorted by timestamp. + """ + subwalls = [] + for w in walls: + try: + subwalls.append([next(w), w]) + except StopIteration: + pass + + if max_len is None: + max_len = -1 + 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("catalogue/wall.html", takes_context=True) +def wall(context, user=None, max_len=100): + return { + "request": context['request'], + "STATIC_URL": context['STATIC_URL'], + "wall": big_wall([ + changes_wall(user, max_len), + published_wall(user, max_len), + image_changes_wall(user, max_len), + image_published_wall(user, max_len), + comments_wall(user, max_len), + ], max_len)} + +@register.inclusion_tag("catalogue/wall.html", takes_context=True) +def day_wall(context, day): + return { + "request": context['request'], + "STATIC_URL": context['STATIC_URL'], + "wall": big_wall([ + changes_wall(day=day), + published_wall(day=day), + image_changes_wall(day=day), + image_published_wall(day=day), + comments_wall(day=day), + ])} diff --git a/src/catalogue/test_utils.py b/src/catalogue/test_utils.py new file mode 100644 index 00000000..2b085450 --- /dev/null +++ b/src/catalogue/test_utils.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +"""Testing utilities.""" + +from os.path import abspath, dirname, join + + +def get_fixture(path): + f_path = join(dirname(abspath(__file__)), 'tests/files', path) + with open(f_path) as f: + return unicode(f.read(), 'utf-8') diff --git a/src/catalogue/tests/__init__.py b/src/catalogue/tests/__init__.py new file mode 100644 index 00000000..533a6c53 --- /dev/null +++ b/src/catalogue/tests/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from catalogue.tests.book import * +from catalogue.tests.gallery import * +from catalogue.tests.publish import * +from catalogue.tests.xml_updater import * diff --git a/src/catalogue/tests/book.py b/src/catalogue/tests/book.py new file mode 100644 index 00000000..df6f3b4f --- /dev/null +++ b/src/catalogue/tests/book.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +"""Tests for manipulating books in the catalogue.""" + +from django.test import TestCase +from django.contrib.auth.models import User +from catalogue.models import Book + + +class ManipulationTests(TestCase): + + def setUp(self): + self.user = User.objects.create(username='tester') + self.book1 = Book.create(self.user, 'book 1', slug='book1') + self.book2 = Book.create(self.user, 'book 2', slug='book2') + + def test_append(self): + self.book1.append(self.book2) + self.assertEqual(Book.objects.all().count(), 1) + self.assertEqual(len(self.book1), 2) + + def test_append_to_self(self): + with self.assertRaises(AssertionError): + self.book1.append(Book.objects.get(pk=self.book1.pk)) + self.assertEqual(Book.objects.all().count(), 2) + self.assertEqual(len(self.book1), 1) + + def test_prepend_history(self): + self.book1.prepend_history(self.book2) + self.assertEqual(Book.objects.all().count(), 1) + self.assertEqual(len(self.book1), 1) + self.assertEqual(self.book1.materialize(), 'book 1') + + def test_prepend_history_to_self(self): + with self.assertRaises(AssertionError): + self.book1.prepend_history(self.book1) + self.assertEqual(Book.objects.all().count(), 2) + self.assertEqual(self.book1.materialize(), 'book 1') + self.assertEqual(self.book2.materialize(), 'book 2') + + def test_split_book(self): + self.book1.chunk_set.create(number=2, title='Second chunk', + slug='book3') + self.book1[1].commit('I survived!') + self.assertEqual(len(self.book1), 2) + self.book1.split() + self.assertEqual(set([b.slug for b in Book.objects.all()]), + set(['book2', '1', 'book3'])) + self.assertEqual( + Book.objects.get(slug='book3').materialize(), + 'I survived!') diff --git a/src/catalogue/tests/files/chunk1.xml b/src/catalogue/tests/files/chunk1.xml new file mode 100755 index 00000000..6a75580a --- /dev/null +++ b/src/catalogue/tests/files/chunk1.xml @@ -0,0 +1,42 @@ + + + + + +Mickiewicz, Adam +Do M*** +Fundacja Nowoczesna Polska +Romantyzm +Liryka +Wiersz + +http://wolnelektury.pl/katalog/lektura/sonety-odeskie-do-m +http://www.polona.pl/Content/2222 +Mickiewicz, Adam (1798-1855), Poezje, tom 1 (Wiersze młodzieńcze - Ballady i romanse - Wiersze do r. 1824), Krakowska Spółdzielnia Wydawnicza, wyd. 2 zwiększone, Kraków, 1922 + +Domena publiczna - Adam Mickiewicz zm. 1855 +1926 +xml +text +text +2007-09-06 +pol + + + + +Adam Mickiewicz +Sonety odeskie +Do M*** + +Wiérsz napisany w roku 1822 + + +Precz z moich oczu!... posłucham od razu,/ +Precz z mego serca!... i serce posłucha,/ +Precz z méj pamięci!... Nie! tego rozkazu/ +Moja i twoja pamięć nie posłucha. + + + + diff --git a/src/catalogue/tests/files/chunk2.xml b/src/catalogue/tests/files/chunk2.xml new file mode 100755 index 00000000..63a243e1 --- /dev/null +++ b/src/catalogue/tests/files/chunk2.xml @@ -0,0 +1,11 @@ + + + +Jak cień tém dłuższy, gdy padnie z daleka,/ +Tém szerzéj koło żałobne roztoczy,/ +Tak moja postać, im daléj ucieka,/ +Tém grubszym kirem twą pamięć pomroczy. + + + + diff --git a/src/catalogue/tests/files/expected.xml b/src/catalogue/tests/files/expected.xml new file mode 100755 index 00000000..ff225a03 --- /dev/null +++ b/src/catalogue/tests/files/expected.xml @@ -0,0 +1,49 @@ + + + + + +Mickiewicz, Adam +Do M*** +Fundacja Nowoczesna Polska +Romantyzm +Liryka +Wiersz + +http://wolnelektury.pl/katalog/lektura/sonety-odeskie-do-m +http://www.polona.pl/Content/2222 +Mickiewicz, Adam (1798-1855), Poezje, tom 1 (Wiersze młodzieńcze - Ballady i romanse - Wiersze do r. 1824), Krakowska Spółdzielnia Wydawnicza, wyd. 2 zwiększone, Kraków, 1922 + +Domena publiczna - Adam Mickiewicz zm. 1855 +1926 +xml +text +text +2007-09-06 +pol + + + + +Adam Mickiewicz +Sonety odeskie +Do M*** + +Wiérsz napisany w roku 1822 + + +Precz z moich oczu!... posłucham od razu,/ +Precz z mego serca!... i serce posłucha,/ +Precz z méj pamięci!... Nie! tego rozkazu/ +Moja i twoja pamięć nie posłucha. + + + +Jak cień tém dłuższy, gdy padnie z daleka,/ +Tém szerzéj koło żałobne roztoczy,/ +Tak moja postać, im daléj ucieka,/ +Tém grubszym kirem twą pamięć pomroczy. + + + + diff --git a/src/catalogue/tests/gallery.py b/src/catalogue/tests/gallery.py new file mode 100644 index 00000000..4b8ea3f4 --- /dev/null +++ b/src/catalogue/tests/gallery.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +"""Tests for galleries of scans.""" + +from os.path import join, basename, exists +from os import makedirs, listdir +from django.test import TestCase +from django.contrib.auth.models import User +from catalogue.models import Book +from tempfile import mkdtemp +from django.conf import settings + + +class GalleryAppendTests(TestCase): + def setUp(self): + self.user = User.objects.create(username='tester') + self.book1 = Book.create(self.user, 'book 1', slug='book1') + self.book1.chunk_set.create(number=2, title='Second chunk', + slug='book1-2') + c=self.book1[1] + c.gallery_start=3 + + self.scandir = join(settings.MEDIA_ROOT, settings.IMAGE_DIR) + if not exists(self.scandir): + makedirs(self.scandir) + + def make_gallery(self, book, files): + d = mkdtemp('gallery', dir=self.scandir) + for named, cont in files.items(): + f = open(join(d, named), 'w') + f.write(cont) + f.close() + book.gallery = basename(d) + + + def test_both_indexed(self): + self.book2 = Book.create(self.user, 'book 2', slug='book2') + self.book2.chunk_set.create(number=2, title='Second chunk of second book', + slug='book2-2') + + c = self.book2[1] + c.gallery_start = 3 + c.save() + + print "gallery starts:",self.book2[0].gallery_start, self.book2[1].gallery_start + + self.make_gallery(self.book1, { + '1-0001_1l' : 'aa', + '1-0001_2r' : 'bb', + '1-0002_1l' : 'cc', + '1-0002_2r' : 'dd', + }) + + self.make_gallery(self.book2, { + '1-0001_1l' : 'dd', # the same, should not be moved + '1-0001_2r' : 'ff', + '2-0002_1l' : 'gg', + '2-0002_2r' : 'hh', + }) + + self.book1.append(self.book2) + + files = listdir(join(self.scandir, self.book1.gallery)) + files.sort() + print files + self.assertEqual(files, [ + '1-0001_1l', + '1-0001_2r', + '1-0002_1l', + '1-0002_2r', + # '2-0001_1l', + '2-0001_2r', + '3-0002_1l', + '3-0002_2r', + ]) + + self.assertEqual((4, 6), (self.book1[2].gallery_start, self.book1[3].gallery_start)) + + + def test_none_indexed(self): + self.book2 = Book.create(self.user, 'book 2', slug='book2') + self.make_gallery(self.book1, { + '0001_1l' : 'aa', + '0001_2r' : 'bb', + '0002_1l' : 'cc', + '0002_2r' : 'dd', + }) + + self.make_gallery(self.book2, { + '0001_1l' : 'ee', + '0001_2r' : 'ff', + '0002_1l' : 'gg', + '0002_2r' : 'hh', + }) + + self.book1.append(self.book2) + + files = listdir(join(self.scandir, self.book1.gallery)) + files.sort() + print files + self.assertEqual(files, [ + '0-0001_1l', + '0-0001_2r', + '0-0002_1l', + '0-0002_2r', + '1-0001_1l', + '1-0001_2r', + '1-0002_1l', + '1-0002_2r', + ]) + + + def test_none_indexed(self): + import nose.tools + self.book2 = Book.create(self.user, 'book 2', slug='book2') + self.make_gallery(self.book1, { + '1-0001_1l' : 'aa', + '1-0001_2r' : 'bb', + '1002_1l' : 'cc', + '1002_2r' : 'dd', + }) + + self.make_gallery(self.book2, { + '0001_1l' : 'ee', + '0001_2r' : 'ff', + '0002_1l' : 'gg', + '0002_2r' : 'hh', + }) + + self.book1.append(self.book2) + + files = listdir(join(self.scandir, self.book1.gallery)) + files.sort() + print files + self.assertEqual(files, [ + '0-1-0001_1l', + '0-1-0001_2r', + '0-1002_1l', + '0-1002_2r', + '1-0001_1l', + '1-0001_2r', + '1-0002_1l', + '1-0002_2r', + ]) + diff --git a/src/catalogue/tests/publish.py b/src/catalogue/tests/publish.py new file mode 100644 index 00000000..93e02daa --- /dev/null +++ b/src/catalogue/tests/publish.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +"""Tests for the publishing process.""" + +from catalogue.test_utils import get_fixture + +from mock import patch +from django.test import TestCase +from django.contrib.auth.models import User +from catalogue.models import Book + + +class PublishTests(TestCase): + def setUp(self): + self.user = User.objects.create(username='tester') + self.text1 = get_fixture('chunk1.xml') + self.book = Book.create(self.user, self.text1, slug='test-book') + + @patch('apiclient.api_call') + def test_unpublishable(self, api_call): + with self.assertRaises(AssertionError): + self.book.publish(self.user) + + @patch('apiclient.api_call') + def test_publish(self, api_call): + self.book[0].head.set_publishable(True) + self.book.publish(self.user) + api_call.assert_called_with(self.user, 'books/', {"book_xml": self.text1, "days": 0}, beta=False) + + @patch('apiclient.api_call') + def test_publish_multiple(self, api_call): + self.book[0].head.set_publishable(True) + self.book[0].split(slug='part-2') + self.book[1].commit(get_fixture('chunk2.xml')) + self.book[1].head.set_publishable(True) + self.book.publish(self.user) + api_call.assert_called_with(self.user, 'books/', {"book_xml": get_fixture('expected.xml'), "days": 0}, beta=False) diff --git a/src/catalogue/tests/xml_updater.py b/src/catalogue/tests/xml_updater.py new file mode 100644 index 00000000..9fb5a4a0 --- /dev/null +++ b/src/catalogue/tests/xml_updater.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +"""XmlUpdater tests.""" + +from catalogue.test_utils import get_fixture +from django.test import TestCase +from django.contrib.auth.models import User +from catalogue.models import Book +from catalogue.management import XmlUpdater +from librarian import DCNS + + +class XmlUpdaterTests(TestCase): + class SimpleUpdater(XmlUpdater): + @XmlUpdater.fixes_elements('.//' + DCNS('title')) + def fix_title(element, **kwargs): + element.text = element.text + " fixed" + return True + + def setUp(self): + self.user = User.objects.create(username='tester') + text = get_fixture('chunk1.xml') + Book.create(self.user, text, slug='test-book') + self.title = "Do M***" + + def test_xml_updater(self): + self.SimpleUpdater().run(self.user) + self.assertEqual( + Book.objects.get(slug='test-book').wldocument( + publishable=False).book_info.title, + self.title + " fixed" + ) diff --git a/src/catalogue/urls.py b/src/catalogue/urls.py new file mode 100644 index 00000000..e81c0a36 --- /dev/null +++ b/src/catalogue/urls.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 +from django.conf.urls import patterns, url +from django.contrib.auth.decorators import permission_required +from django.views.generic import RedirectView +from catalogue.feeds import PublishTrackFeed +from catalogue.views import GalleryView + + +urlpatterns = patterns('catalogue.views', + url(r'^$', RedirectView.as_view(url='catalogue/')), + + url(r'^images/$', 'image_list', name='catalogue_image_list'), + url(r'^image/(?P[^/]+)/$', 'image', name="catalogue_image"), + url(r'^image/(?P[^/]+)/publish$', 'publish_image', + name="catalogue_publish_image"), + + url(r'^catalogue/$', 'document_list', name='catalogue_document_list'), + url(r'^user/$', 'my', name='catalogue_user'), + url(r'^user/(?P[^/]+)/$', 'user', name='catalogue_user'), + url(r'^users/$', 'users', name='catalogue_users'), + url(r'^activity/$', 'activity', name='catalogue_activity'), + url(r'^activity/(?P\d{4}-\d{2}-\d{2})/$', + 'activity', name='catalogue_activity'), + + url(r'^upload/$', + 'upload', name='catalogue_upload'), + + url(r'^create/(?P[^/]*)/', + 'create_missing', name='catalogue_create_missing'), + url(r'^create/', + 'create_missing', name='catalogue_create_missing'), + + url(r'^book/(?P[^/]+)/publish$', 'publish', name="catalogue_publish"), + + url(r'^book/(?P[^/]+)/$', 'book', name="catalogue_book"), + url(r'^book/(?P[^/]+)/gallery/$', + permission_required('catalogue.change_book')(GalleryView.as_view()), + name="catalogue_book_gallery"), + url(r'^book/(?P[^/]+)/xml$', 'book_xml', name="catalogue_book_xml"), + url(r'^book/dc/(?P[^/]+)/xml$', 'book_xml_dc', name="catalogue_book_xml_dc"), + url(r'^book/(?P[^/]+)/txt$', 'book_txt', name="catalogue_book_txt"), + url(r'^book/(?P[^/]+)/html$', 'book_html', name="catalogue_book_html"), + url(r'^book/(?P[^/]+)/epub$', 'book_epub', name="catalogue_book_epub"), + url(r'^book/(?P[^/]+)/mobi$', 'book_mobi', name="catalogue_book_mobi"), + url(r'^book/(?P[^/]+)/pdf$', 'book_pdf', name="catalogue_book_pdf"), + url(r'^book/(?P[^/]+)/pdf-mobile$', 'book_pdf', kwargs={'mobile': True}, name="catalogue_book_pdf_mobile"), + + url(r'^chunk_add/(?P[^/]+)/(?P[^/]+)/$', + 'chunk_add', name="catalogue_chunk_add"), + url(r'^chunk_edit/(?P[^/]+)/(?P[^/]+)/$', + 'chunk_edit', name="catalogue_chunk_edit"), + url(r'^book_append/(?P[^/]+)/$', + 'book_append', name="catalogue_book_append"), + url(r'^chunk_mass_edit', + 'chunk_mass_edit', name='catalogue_chunk_mass_edit'), + url(r'^image_mass_edit', + 'image_mass_edit', name='catalogue_image_mass_edit'), + + url(r'^track/(?P[^/]*)/$', PublishTrackFeed()), + url(r'^active/$', 'active_users_list', name='active_users_list'), + + url(r'^mark-final/$', 'mark_final', name='mark_final'), + url(r'^mark-final-completed/$', 'mark_final_completed', name='mark_final_completed'), +) diff --git a/src/catalogue/views.py b/src/catalogue/views.py new file mode 100644 index 00000000..f6208056 --- /dev/null +++ b/src/catalogue/views.py @@ -0,0 +1,672 @@ +# -*- coding: utf-8 -*- +from collections import defaultdict +from datetime import datetime, date, timedelta +import logging +import os +from StringIO import StringIO +from urllib import unquote +from urlparse import urlsplit, urlunsplit + +from django.conf import settings +from django.contrib import auth +from django.contrib.auth.models import User +from django.contrib.auth.decorators import login_required, permission_required +from django.core.urlresolvers import reverse +from django.db.models import Count, Q +from django.db import transaction +from django import http +from django.http import Http404, HttpResponse, HttpResponseForbidden +from django.http.response import HttpResponseRedirect +from django.shortcuts import get_object_or_404, render +from django.utils.encoding import iri_to_uri +from django.utils.http import urlquote_plus +from django.utils.translation import ugettext_lazy as _ +from django.views.decorators.http import require_POST +from django_cas.decorators import user_passes_test + +from apiclient import NotAuthorizedError +from catalogue import forms +from catalogue import helpers +from catalogue.helpers import active_tab +from catalogue.models import (Book, Chunk, Image, BookPublishRecord, + ChunkPublishRecord, ImagePublishRecord, Project) +from fileupload.views import UploadView + +# +# Quick hack around caching problems, TODO: use ETags +# +from django.views.decorators.cache import never_cache + +logger = logging.getLogger("fnp.catalogue") + + +@active_tab('all') +@never_cache +def document_list(request): + return render(request, 'catalogue/document_list.html') + + +@active_tab('images') +@never_cache +def image_list(request, user=None): + return render(request, 'catalogue/image_list.html') + + +@never_cache +def user(request, username): + user = get_object_or_404(User, username=username) + return render(request, 'catalogue/user_page.html', {"viewed_user": user}) + + +@login_required +@active_tab('my') +@never_cache +def my(request): + last_books = sorted(request.session.get("wiki_last_books", {}).items(), + key=lambda x: x[1]['time'], reverse=True) + for k, v in last_books: + v['time'] = datetime.fromtimestamp(v['time']) + return render(request, 'catalogue/my_page.html', { + 'last_books': last_books, + "logout_to": '/', + }) + + +@active_tab('users') +def users(request): + return render(request, 'catalogue/user_list.html', { + 'users': User.objects.all().annotate(count=Count('chunk')).order_by( + '-count', 'last_name', 'first_name'), + }) + + +@active_tab('activity') +def activity(request, isodate=None): + today = date.today() + try: + day = helpers.parse_isodate(isodate) + except ValueError: + day = today + + if day > today: + raise Http404 + if day != today: + next_day = day + timedelta(1) + prev_day = day - timedelta(1) + + return render(request, 'catalogue/activity.html', locals()) + + +@never_cache +def logout_then_redirect(request): + auth.logout(request) + return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?=')) + + +@permission_required('catalogue.add_book') +@active_tab('create') +def create_missing(request, slug=None): + if slug is None: + slug = '' + slug = slug.replace(' ', '-') + + if request.method == "POST": + form = forms.DocumentCreateForm(request.POST, request.FILES) + if form.is_valid(): + + if request.user.is_authenticated(): + creator = request.user + else: + creator = None + book = Book.create( + text=form.cleaned_data['text'], + creator=creator, + slug=form.cleaned_data['slug'], + title=form.cleaned_data['title'], + gallery=form.cleaned_data['gallery'], + ) + + return http.HttpResponseRedirect(reverse("catalogue_book", args=[book.slug])) + else: + form = forms.DocumentCreateForm(initial={ + "slug": slug, + "title": slug.replace('-', ' ').title(), + "gallery": slug, + }) + + return render(request, "catalogue/document_create_missing.html", { + "slug": slug, + "form": form, + + "logout_to": '/', + }) + + +@permission_required('catalogue.add_book') +@active_tab('upload') +def upload(request): + if request.method == "POST": + form = forms.DocumentsUploadForm(request.POST, request.FILES) + if form.is_valid(): + from slugify import slugify + + if request.user.is_authenticated(): + creator = request.user + else: + creator = None + + zip = form.cleaned_data['zip'] + skipped_list = [] + ok_list = [] + error_list = [] + slugs = {} + existing = [book.slug for book in Book.objects.all()] + for filename in zip.namelist(): + if filename[-1] == '/': + continue + title = os.path.basename(filename)[:-4] + slug = slugify(title) + if not (slug and filename.endswith('.xml')): + skipped_list.append(filename) + elif slug in slugs: + error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug]))) + elif slug in existing: + error_list.append((filename, slug, _('Slug already used in repository.'))) + else: + try: + zip.read(filename).decode('utf-8') # test read + ok_list.append((filename, slug, title)) + except UnicodeDecodeError: + error_list.append((filename, title, _('File should be UTF-8 encoded.'))) + slugs[slug] = filename + + if not error_list: + for filename, slug, title in ok_list: + book = Book.create( + text=zip.read(filename).decode('utf-8'), + creator=creator, + slug=slug, + title=title, + ) + + return render(request, "catalogue/document_upload.html", { + "form": form, + "ok_list": ok_list, + "skipped_list": skipped_list, + "error_list": error_list, + + "logout_to": '/', + }) + else: + form = forms.DocumentsUploadForm() + + return render(request, "catalogue/document_upload.html", { + "form": form, + + "logout_to": '/', + }) + + +def serve_xml(request, book, slug): + if not book.accessible(request): + return HttpResponseForbidden("Not authorized.") + xml = book.materialize(publishable=True) + response = http.HttpResponse(xml, content_type='application/xml') + response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug + return response + + +@never_cache +def book_xml(request, slug): + book = get_object_or_404(Book, slug=slug) + return serve_xml(request, book, slug) + + +@never_cache +def book_xml_dc(request, slug): + book = get_object_or_404(Book, dc_slug=slug) + return serve_xml(request, book, slug) + + +@never_cache +def book_txt(request, slug): + book = get_object_or_404(Book, slug=slug) + if not book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + doc = book.wldocument() + text = doc.as_text().get_bytes() + response = http.HttpResponse(text, content_type='text/plain') + response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug + return response + + +@never_cache +def book_html(request, slug): + book = get_object_or_404(Book, slug=slug) + if not book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + doc = book.wldocument(parse_dublincore=False) + html = doc.as_html(options={'gallery': "'%s'" % book.gallery_url()}) + + html = html.get_bytes() if html is not None else '' + # response = http.HttpResponse(html, content_type='text/html') + # return response + # book_themes = {} + # for fragment in book.fragments.all().iterator(): + # for theme in fragment.tags.filter(category='theme').iterator(): + # book_themes.setdefault(theme, []).append(fragment) + + # book_themes = book_themes.items() + # book_themes.sort(key=lambda s: s[0].sort_key) + return render(request, 'catalogue/book_text.html', locals()) + + +@never_cache +def book_pdf(request, slug, mobile=False): + book = get_object_or_404(Book, slug=slug) + if not book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + # TODO: move to celery + doc = book.wldocument() + # TODO: error handling + customizations = ['26pt', 'nothemes', 'nomargins', 'notoc'] if mobile else None + pdf_file = doc.as_pdf(cover=True, ilustr_path=book.gallery_path(), customizations=customizations) + from catalogue.ebook_utils import serve_file + return serve_file(pdf_file.get_filename(), + book.slug + '.pdf', 'application/pdf') + + +@never_cache +def book_epub(request, slug): + book = get_object_or_404(Book, slug=slug) + if not book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + # TODO: move to celery + doc = book.wldocument() + # TODO: error handling + epub = doc.as_epub(ilustr_path=book.gallery_path()).get_bytes() + response = HttpResponse(content_type='application/epub+zip') + response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.epub' + response.write(epub) + return response + + +@never_cache +def book_mobi(request, slug): + book = get_object_or_404(Book, slug=slug) + if not book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + # TODO: move to celery + doc = book.wldocument() + # TODO: error handling + mobi = doc.as_mobi(ilustr_path=book.gallery_path()).get_bytes() + response = HttpResponse(content_type='application/x-mobipocket-ebook') + response['Content-Disposition'] = 'attachment; filename=%s' % book.slug + '.mobi' + response.write(mobi) + return response + + +@never_cache +def revision(request, slug, chunk=None): + try: + doc = Chunk.get(slug, chunk) + except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist): + raise Http404 + if not doc.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + return http.HttpResponse(str(doc.revision())) + + +def book(request, slug): + book = get_object_or_404(Book, slug=slug) + if not book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + if request.user.has_perm('catalogue.change_book'): + if request.method == "POST": + form = forms.BookForm(request.POST, instance=book) + if form.is_valid(): + form.save() + return http.HttpResponseRedirect(book.get_absolute_url()) + else: + form = forms.BookForm(instance=book) + publish_options_form = forms.PublishOptionsForm() + editable = True + else: + form = forms.ReadonlyBookForm(instance=book) + publish_options_form = forms.PublishOptionsForm() + editable = False + + publish_error = book.publishable_error() + publishable = publish_error is None + + return render(request, "catalogue/book_detail.html", { + "book": book, + "publishable": publishable, + "publishable_error": publish_error, + "form": form, + "publish_options_form": publish_options_form, + "editable": editable, + }) + + +def image(request, slug): + image = get_object_or_404(Image, slug=slug) + if not image.accessible(request): + return HttpResponseForbidden("Not authorized.") + + if request.user.has_perm('catalogue.change_image'): + if request.method == "POST": + form = forms.ImageForm(request.POST, instance=image) + if form.is_valid(): + form.save() + return http.HttpResponseRedirect(image.get_absolute_url()) + else: + form = forms.ImageForm(instance=image) + editable = True + else: + form = forms.ReadonlyImageForm(instance=image) + editable = False + + publish_error = image.publishable_error() + publishable = publish_error is None + + return render(request, "catalogue/image_detail.html", { + "object": image, + "publishable": publishable, + "publishable_error": publish_error, + "form": form, + "editable": editable, + }) + + +@permission_required('catalogue.add_chunk') +def chunk_add(request, slug, chunk): + try: + doc = Chunk.get(slug, chunk) + except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist): + raise Http404 + if not doc.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + if request.method == "POST": + form = forms.ChunkAddForm(request.POST, instance=doc) + if form.is_valid(): + if request.user.is_authenticated(): + creator = request.user + else: + creator = None + doc.split(creator=creator, + slug=form.cleaned_data['slug'], + title=form.cleaned_data['title'], + gallery_start=form.cleaned_data['gallery_start'], + user=form.cleaned_data['user'], + stage=form.cleaned_data['stage'] + ) + + return http.HttpResponseRedirect(doc.book.get_absolute_url()) + else: + form = forms.ChunkAddForm(initial={ + "slug": str(doc.number + 1), + "title": "cz. %d" % (doc.number + 1, ), + }) + + return render(request, "catalogue/chunk_add.html", { + "chunk": doc, + "form": form, + }) + + +@login_required +def chunk_edit(request, slug, chunk): + try: + doc = Chunk.get(slug, chunk) + except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist): + raise Http404 + if not doc.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + if request.method == "POST": + form = forms.ChunkForm(request.POST, instance=doc) + if form.is_valid(): + form.save() + go_next = request.GET.get('next', None) + if go_next: + go_next = urlquote_plus(unquote(iri_to_uri(go_next)), safe='/?=&') + else: + go_next = doc.book.get_absolute_url() + return http.HttpResponseRedirect(go_next) + else: + form = forms.ChunkForm(instance=doc) + + referer = request.META.get('HTTP_REFERER') + if referer: + parts = urlsplit(referer) + parts = ['', ''] + list(parts[2:]) + go_next = urlquote_plus(urlunsplit(parts)) + else: + go_next = '' + + return render(request, "catalogue/chunk_edit.html", { + "chunk": doc, + "form": form, + "go_next": go_next, + }) + + +@transaction.atomic +@login_required +@require_POST +def chunk_mass_edit(request): + ids = map(int, filter(lambda i: i.strip()!='', request.POST.get('ids').split(','))) + chunks = map(lambda i: Chunk.objects.get(id=i), ids) + + stage = request.POST.get('stage') + if stage: + try: + stage = Chunk.tag_model.objects.get(slug=stage) + except Chunk.DoesNotExist, e: + stage = None + + for c in chunks: c.stage = stage + + username = request.POST.get('user') + logger.info("username: %s" % username) + logger.info(request.POST) + if username: + try: + user = User.objects.get(username=username) + except User.DoesNotExist, e: + user = None + + for c in chunks: c.user = user + + project_id = request.POST.get('project') + if project_id: + try: + project = Project.objects.get(pk=int(project_id)) + except (Project.DoesNotExist, ValueError), e: + project = None + for c in chunks: + book = c.book + book.project = project + book.save() + + for c in chunks: c.save() + + return HttpResponse("", content_type="text/plain") + + +@transaction.atomic +@login_required +@require_POST +def image_mass_edit(request): + ids = map(int, filter(lambda i: i.strip()!='', request.POST.get('ids').split(','))) + images = map(lambda i: Image.objects.get(id=i), ids) + + stage = request.POST.get('stage') + if stage: + try: + stage = Image.tag_model.objects.get(slug=stage) + except Image.DoesNotExist, e: + stage = None + + for c in images: c.stage = stage + + username = request.POST.get('user') + logger.info("username: %s" % username) + logger.info(request.POST) + if username: + try: + user = User.objects.get(username=username) + except User.DoesNotExist, e: + user = None + + for c in images: c.user = user + + project_id = request.POST.get('project') + if project_id: + try: + project = Project.objects.get(pk=int(project_id)) + except (Project.DoesNotExist, ValueError), e: + project = None + for c in images: + c.project = project + + for c in images: c.save() + + return HttpResponse("", content_type="text/plain") + + +@permission_required('catalogue.change_book') +def book_append(request, slug): + book = get_object_or_404(Book, slug=slug) + if not book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + if request.method == "POST": + form = forms.BookAppendForm(book, request.POST) + if form.is_valid(): + append_to = form.cleaned_data['append_to'] + append_to.append(book) + return http.HttpResponseRedirect(append_to.get_absolute_url()) + else: + form = forms.BookAppendForm(book) + return render(request, "catalogue/book_append_to.html", { + "book": book, + "form": form, + + "logout_to": '/', + }) + + +@require_POST +@login_required +def publish(request, slug): + form = forms.PublishOptionsForm(request.POST) + if form.is_valid(): + days = form.cleaned_data['days'] + beta = form.cleaned_data['beta'] + else: + days = 0 + beta = False + book = get_object_or_404(Book, slug=slug) + if not book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + try: + protocol = 'https://' if request.is_secure() else 'http://' + book.publish(request.user, host=protocol + request.get_host(), days=days, beta=beta) + except NotAuthorizedError: + return http.HttpResponseRedirect(reverse('apiclient_oauth' if not beta else 'apiclient_beta_oauth')) + except BaseException, e: + return http.HttpResponse(repr(e)) + else: + return http.HttpResponseRedirect(book.get_absolute_url()) + + +@require_POST +@login_required +def publish_image(request, slug): + image = get_object_or_404(Image, slug=slug) + if not image.accessible(request): + return HttpResponseForbidden("Not authorized.") + + try: + image.publish(request.user) + except NotAuthorizedError: + return http.HttpResponseRedirect(reverse('apiclient_oauth')) + except BaseException, e: + return http.HttpResponse(e) + else: + return http.HttpResponseRedirect(image.get_absolute_url()) + + +class GalleryView(UploadView): + def get_object(self, request, slug): + book = get_object_or_404(Book, slug=slug) + if not book.gallery: + raise Http404 + return book + + def breadcrumbs(self): + return [ + (_('books'), reverse('catalogue_document_list')), + (self.object.title, self.object.get_absolute_url()), + (_('scan gallery'),), + ] + + def get_directory(self): + return "%s%s/" % (settings.IMAGE_DIR, self.object.gallery) + + +def active_users_list(request): + since = date(date.today().year, 1, 1) + by_user = defaultdict(lambda: 0) + by_email = defaultdict(lambda: 0) + names_by_email = defaultdict(set) + for change_model in (Chunk.change_model, Image.change_model): + for c in change_model.objects.filter( + created_at__gte=since).order_by( + 'author', 'author_email', 'author_name').values( + 'author', 'author_name', 'author_email').annotate( + c=Count('author'), ce=Count('author_email')).distinct(): + if c['author']: + by_user[c['author']] += c['c'] + else: + by_email[c['author_email']] += c['ce'] + if c['author_name'].strip(): + names_by_email[c['author_email']].add(c['author_name']) + for user in User.objects.filter(pk__in=by_user): + by_email[user.email] += by_user[user.pk] + names_by_email[user.email].add("%s %s" % (user.first_name, user.last_name)) + + active_users = [] + for email, count in by_email.items(): + active_users.append((email, names_by_email[email], count)) + active_users.sort(key=lambda x: -x[2]) + return render(request, 'catalogue/active_users_list.html', { + 'users': active_users, + 'since': since, + }) + + +@user_passes_test(lambda u: u.is_superuser) +def mark_final(request): + if request.method == 'POST': + form = forms.MarkFinalForm(data=request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('mark_final_completed')) + else: + form = forms.MarkFinalForm() + return render(request, 'catalogue/mark_final.html', {'form': form}) + + +def mark_final_completed(request): + return render(request, 'catalogue/mark_final_completed.html') diff --git a/src/catalogue/xml_tools.py b/src/catalogue/xml_tools.py new file mode 100644 index 00000000..242714b6 --- /dev/null +++ b/src/catalogue/xml_tools.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +from copy import deepcopy +import re + +from lxml import etree +from catalogue.constants import TRIM_BEGIN, TRIM_END, MASTERS + +RE_TRIM_BEGIN = re.compile("^$" % TRIM_BEGIN, re.M) +RE_TRIM_END = re.compile("^$" % TRIM_END, re.M) + + +class ParseError(BaseException): + pass + + +def _trim(text, trim_begin=True, trim_end=True): + """ + Cut off everything before RE_TRIM_BEGIN and after RE_TRIM_END, so + that eg. one big XML file can be compiled from many small XML files. + """ + if trim_begin: + text = RE_TRIM_BEGIN.split(text, maxsplit=1)[-1] + if trim_end: + text = RE_TRIM_END.split(text, maxsplit=1)[0] + return text + + +def compile_text(parts): + """ + Compiles full text from an iterable of parts, + trimming where applicable. + """ + texts = [] + trim_begin = False + text = '' + for next_text in parts: + if not next_text: + continue + if text: + # trim the end, because there's more non-empty text + # don't trim beginning, if `text' is the first non-empty part + texts.append(_trim(text, trim_begin=trim_begin)) + trim_begin = True + text = next_text + # don't trim the end, because there's no more text coming after `text' + # only trim beginning if it's not still the first non-empty + texts.append(_trim(text, trim_begin=trim_begin, trim_end=False)) + return "".join(texts) + + +def add_trim_begin(text): + trim_tag = etree.Comment(TRIM_BEGIN) + e = etree.fromstring(text) + for master in e[::-1]: + if master.tag in MASTERS: + break + if master.tag not in MASTERS: + raise ParseError('No master tag found!') + + master.insert(0, trim_tag) + trim_tag.tail = '\n\n\n' + (master.text or '') + master.text = '\n' + return unicode(etree.tostring(e, encoding="utf-8"), 'utf-8') + + +def add_trim_end(text): + trim_tag = etree.Comment(TRIM_END) + e = etree.fromstring(text) + for master in e[::-1]: + if master.tag in MASTERS: + break + if master.tag not in MASTERS: + raise ParseError('No master tag found!') + + master.append(trim_tag) + trim_tag.tail = '\n' + prev = trim_tag.getprevious() + if prev is not None: + prev.tail = (prev.tail or '') + '\n\n\n' + else: + master.text = (master.text or '') + '\n\n\n' + return unicode(etree.tostring(e, encoding="utf-8"), 'utf-8') + + +def split_xml(text): + """Splits text into chapters. + + All this stuff really must go somewhere else. + + """ + src = etree.fromstring(text) + chunks = [] + + splitter = u'naglowek_rozdzial' + parts = src.findall('.//naglowek_rozdzial') + while parts: + # copy the document + copied = deepcopy(src) + + element = parts[-1] + + # find the chapter's title + name_elem = deepcopy(element) + for tag in 'extra', 'motyw', 'pa', 'pe', 'pr', 'pt', 'uwaga': + for a in name_elem.findall('.//' + tag): + a.text='' + del a[:] + name = etree.tostring(name_elem, method='text', encoding='utf-8').strip() + + # in the original, remove everything from the start of the last chapter + parent = element.getparent() + del parent[parent.index(element):] + element, parent = parent, parent.getparent() + while parent is not None: + del parent[parent.index(element) + 1:] + element, parent = parent, parent.getparent() + + # in the copy, remove everything before the last chapter + element = copied.findall('.//naglowek_rozdzial')[-1] + parent = element.getparent() + while parent is not None: + parent.text = None + while parent[0] is not element: + del parent[0] + element, parent = parent, parent.getparent() + chunks[:0] = [[name, + unicode(etree.tostring(copied, encoding='utf-8'), 'utf-8') + ]] + + parts = src.findall('.//naglowek_rozdzial') + + chunks[:0] = [[u'początek', + unicode(etree.tostring(src, encoding='utf-8'), 'utf-8') + ]] + + for ch in chunks[1:]: + ch[1] = add_trim_begin(ch[1]) + for ch in chunks[:-1]: + ch[1] = add_trim_end(ch[1]) + + return chunks diff --git a/src/cover/__init__.py b/src/cover/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cover/forms.py b/src/cover/forms.py new file mode 100755 index 00000000..513bdefb --- /dev/null +++ b/src/cover/forms.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from StringIO import StringIO + +from django import forms +from django.conf import settings +from django.utils.translation import ugettext_lazy as _, ugettext +from cover.models import Image +from django.utils.text import mark_safe +from PIL import Image as PILImage + +from cover.utils import get_flickr_data, FlickrError, URLOpener + + +class ImageAddForm(forms.ModelForm): + class Meta: + model = Image + + def __init__(self, *args, **kwargs): + super(ImageAddForm, self).__init__(*args, **kwargs) + self.fields['file'].required = False + + def clean_download_url(self): + cl = self.cleaned_data['download_url'] or None + if cl is not None: + try: + img = Image.objects.get(download_url=cl) + except Image.DoesNotExist: + pass + else: + raise forms.ValidationError(mark_safe( + ugettext('Image already in repository.') + % {'url': img.get_absolute_url()})) + return cl + + def clean_source_url(self): + source_url = self.cleaned_data['source_url'] or None + if source_url is not None: + same_source = Image.objects.filter(source_url=source_url) + if same_source: + raise forms.ValidationError(mark_safe( + ugettext('Image already in repository' + % same_source.first().get_absolute_url()))) + return source_url + + def clean(self): + cleaned_data = super(ImageAddForm, self).clean() + download_url = cleaned_data.get('download_url', None) + uploaded_file = cleaned_data.get('file', None) + if not download_url and not uploaded_file: + raise forms.ValidationError(ugettext('No image specified')) + if download_url: + image_data = URLOpener().open(download_url).read() + width, height = PILImage.open(StringIO(image_data)).size + else: + width, height = PILImage.open(uploaded_file.file).size + min_width, min_height = settings.MIN_COVER_SIZE + if width < min_width or height < min_height: + raise forms.ValidationError(ugettext('Image too small: %sx%s, minimal dimensions %sx%s') % + (width, height, min_width, min_height)) + return cleaned_data + + +class ImageEditForm(forms.ModelForm): + """Form used for editing a Book.""" + class Meta: + model = Image + exclude = ['download_url'] + + def clean(self): + cleaned_data = super(ImageEditForm, self).clean() + uploaded_file = cleaned_data.get('file', None) + width, height = PILImage.open(uploaded_file.file).size + min_width, min_height = settings.MIN_COVER_SIZE + if width < min_width or height < min_height: + raise forms.ValidationError(ugettext('Image too small: %sx%s, minimal dimensions %sx%s') % + (width, height, min_width, min_height)) + + +class ReadonlyImageEditForm(ImageEditForm): + """Form used for not editing an Image.""" + + def __init__(self, *args, **kwargs): + super(ReadonlyImageEditForm, self).__init__(*args, **kwargs) + for field in self.fields.values(): + field.widget.attrs.update({"readonly": True}) + + def save(self, *args, **kwargs): + raise AssertionError("ReadonlyImageEditForm should not be saved.") + + +class FlickrForm(forms.Form): + source_url = forms.URLField(label=_('Flickr URL')) + + def clean_source_url(self): + url = self.cleaned_data['source_url'] + try: + flickr_data = get_flickr_data(url) + except FlickrError as e: + raise forms.ValidationError(e) + for field_name in ('license_url', 'license_name', 'author', 'title', 'download_url'): + self.cleaned_data[field_name] = flickr_data[field_name] + return flickr_data['source_url'] diff --git a/src/cover/locale/pl/LC_MESSAGES/django.mo b/src/cover/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 00000000..1c297fe0 Binary files /dev/null and b/src/cover/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/cover/locale/pl/LC_MESSAGES/django.po b/src/cover/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 00000000..67547cec --- /dev/null +++ b/src/cover/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,100 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-02-25 13:29+0100\n" +"PO-Revision-Date: 2014-02-25 13:30+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 1.5.4\n" + +#: forms.py:29 +#, python-format +msgid "Image already in repository." +msgstr "Obraz jest już w repozytorium." + +#: forms.py:60 +msgid "Flickr URL" +msgstr "URL z Flickra" + +#: models.py:19 +msgid "title" +msgstr "tytuł" + +#: models.py:20 +msgid "author" +msgstr "autor" + +#: models.py:21 +msgid "license name" +msgstr "nazwa licencji" + +#: models.py:22 +msgid "license URL" +msgstr "URL licencji" + +#: models.py:23 +msgid "source URL" +msgstr "URL źródła" + +#: models.py:24 +msgid "image download URL" +msgstr "URL pliku do pobrania" + +#: models.py:25 +msgid "file" +msgstr "plik" + +#: models.py:28 +msgid "cover image" +msgstr "obrazek na okładkę" + +#: models.py:29 +msgid "cover images" +msgstr "obrazki na okładki" + +#: templates/cover/add_image.html:33 templates/cover/add_image.html.py:62 +msgid "Add image" +msgstr "Dodaj obrazek" + +#: templates/cover/add_image.html:40 +msgid "Load from Flickr" +msgstr "Pobierz z Flickra" + +#: templates/cover/image_detail.html:7 +msgid "Cover image" +msgstr "Obrazek na okładkę" + +#: templates/cover/image_detail.html:23 +msgid "source" +msgstr "źródło" + +#: templates/cover/image_detail.html:35 +msgid "Change" +msgstr "Zmień" + +#: templates/cover/image_detail.html:41 +msgid "Used in:" +msgstr "Użyte w:" + +#: templates/cover/image_detail.html:49 +msgid "None" +msgstr "Brak" + +#: templates/cover/image_list.html:7 +msgid "Cover images" +msgstr "Obrazki na okładki" + +#: templates/cover/image_list.html:10 +msgid "Add new" +msgstr "Dodaj nowy" diff --git a/src/cover/management/__init__.py b/src/cover/management/__init__.py new file mode 100644 index 00000000..d3841244 --- /dev/null +++ b/src/cover/management/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# diff --git a/src/cover/management/commands/__init__.py b/src/cover/management/commands/__init__.py new file mode 100644 index 00000000..d3841244 --- /dev/null +++ b/src/cover/management/commands/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# diff --git a/src/cover/management/commands/refresh_covers.py b/src/cover/management/commands/refresh_covers.py new file mode 100644 index 00000000..cc0ef31c --- /dev/null +++ b/src/cover/management/commands/refresh_covers.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +import urllib2 as urllib +from optparse import make_option + +from django.core.files.base import ContentFile +from django.core.management import BaseCommand + +from cover.models import Image +from cover.utils import get_flickr_data, URLOpener, FlickrError + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--from', dest='from_id', type=int, default=1), + ) + + def handle(self, *args, **options): + from_id = options.get('from_id', 1) + images = Image.objects.filter(id__gte=from_id).exclude(book=None).order_by('id') + images = images.filter(source_url__contains='flickr.com').exclude(download_url__endswith='_o.jpg') + for image in images: + print image.id + try: + flickr_data = get_flickr_data(image.source_url) + print flickr_data + except FlickrError as e: + print 'Flickr analysis failed: %s' % e + else: + flickr_url = flickr_data['download_url'] + if flickr_url != image.download_url: + same_url = Image.objects.filter(download_url=flickr_url) + if same_url: + print 'Download url already present in image %s' % same_url.get().id + continue + try: + t = URLOpener().open(flickr_url).read() + except urllib.URLError: + print 'Broken download url' + except IOError: + print 'Connection failed' + else: + image.download_url = flickr_url + image.file.save(image.file.name, ContentFile(t)) + image.save() diff --git a/src/cover/migrations/0001_initial.py b/src/cover/migrations/0001_initial.py new file mode 100644 index 00000000..f31c405f --- /dev/null +++ b/src/cover/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# -*- coding: 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 'Image' + db.create_table('cover_image', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('author', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('license_name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('license_url', self.gf('django.db.models.fields.URLField')(max_length=255, blank=True)), + ('source_url', self.gf('django.db.models.fields.URLField')(max_length=200)), + ('download_url', self.gf('django.db.models.fields.URLField')(unique=True, max_length=200)), + ('file', self.gf('django.db.models.fields.files.ImageField')(max_length=100)), + )) + db.send_create_signal('cover', ['Image']) + + + def backwards(self, orm): + # Deleting model 'Image' + db.delete_table('cover_image') + + + models = { + 'cover.image': { + 'Meta': {'object_name': 'Image'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'download_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}), + 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['cover'] \ No newline at end of file diff --git a/src/cover/migrations/0002_auto__chg_field_image_download_url.py b/src/cover/migrations/0002_auto__chg_field_image_download_url.py new file mode 100644 index 00000000..8a64c39d --- /dev/null +++ b/src/cover/migrations/0002_auto__chg_field_image_download_url.py @@ -0,0 +1,34 @@ +# -*- coding: 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): + + # Changing field 'Image.download_url' + db.alter_column(u'cover_image', 'download_url', self.gf('django.db.models.fields.URLField')(max_length=200, unique=True, null=True)) + + def backwards(self, orm): + + # User chose to not deal with backwards NULL issues for 'Image.download_url' + raise RuntimeError("Cannot reverse this migration. 'Image.download_url' and its values cannot be restored.") + + models = { + u'cover.image': { + 'Meta': {'object_name': 'Image'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True'}), + 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['cover'] \ No newline at end of file diff --git a/src/cover/migrations/0003_auto__chg_field_image_source_url.py b/src/cover/migrations/0003_auto__chg_field_image_source_url.py new file mode 100644 index 00000000..98951e35 --- /dev/null +++ b/src/cover/migrations/0003_auto__chg_field_image_source_url.py @@ -0,0 +1,34 @@ +# -*- coding: 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): + + # Changing field 'Image.source_url' + db.alter_column(u'cover_image', 'source_url', self.gf('django.db.models.fields.URLField')(max_length=200, null=True)) + + def backwards(self, orm): + + # User chose to not deal with backwards NULL issues for 'Image.source_url' + raise RuntimeError("Cannot reverse this migration. 'Image.source_url' and its values cannot be restored.") + + models = { + u'cover.image': { + 'Meta': {'object_name': 'Image'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True'}), + 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}), + 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['cover'] \ No newline at end of file diff --git a/src/cover/migrations/__init__.py b/src/cover/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cover/models.py b/src/cover/models.py new file mode 100644 index 00000000..d83dad39 --- /dev/null +++ b/src/cover/models.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.core.files.base import ContentFile +from django.core.files.storage import FileSystemStorage +from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.utils.translation import ugettext_lazy as _ +from django.contrib.sites.models import Site +from cover.utils import URLOpener + + +class OverwriteStorage(FileSystemStorage): + + def get_available_name(self, name, max_length=None): + self.delete(name) + return name + + +class Image(models.Model): + title = models.CharField(max_length=255, verbose_name=_('title')) + author = models.CharField(max_length=255, verbose_name=_('author')) + license_name = models.CharField(max_length=255, verbose_name=_('license name')) + license_url = models.URLField(max_length=255, blank=True, verbose_name=_('license URL')) + source_url = models.URLField(verbose_name=_('source URL'), null=True, blank=True) + download_url = models.URLField(unique=True, verbose_name=_('image download URL'), null=True, blank=True) + file = models.ImageField( + upload_to='cover/image', storage=OverwriteStorage(), editable=True, verbose_name=_('file')) + + class Meta: + verbose_name = _('cover image') + verbose_name_plural = _('cover images') + + def __unicode__(self): + return u"%s - %s" % (self.author, self.title) + + @models.permalink + def get_absolute_url(self): + return 'cover_image', [self.id] + + def get_full_url(self): + return "http://%s%s" % (Site.objects.get_current().domain, self.get_absolute_url()) + + +@receiver(post_save, sender=Image) +def download_image(sender, instance, **kwargs): + if instance.pk and not instance.file: + t = URLOpener().open(instance.download_url).read() + instance.file.save("%d.jpg" % instance.pk, ContentFile(t)) diff --git a/src/cover/templates/cover/add_image.html b/src/cover/templates/cover/add_image.html new file mode 100755 index 00000000..293c1001 --- /dev/null +++ b/src/cover/templates/cover/add_image.html @@ -0,0 +1,69 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + +{% block titleextra %}{% trans "Add image" %}{% endblock %} + +{% block add_js %} + {{block.super}} + +{% endblock %} + +{% block content %} +

    {% trans "Add image" %}

    + + +
    {% csrf_token %} + + + {{ ff.as_table }} + +
    +
    + +
    {% csrf_token %} +{{ form.non_field_errors }} + + {% for field in form %} + {% if field.name != 'download_url' and field.name != 'file' %} + + + + + {% endif %} + {% endfor %} + + + + + + + + +
    {{field.errors}} {{field.label}}{{field}}
    {{ form.download_url.errors }} {{form.download_url.label}}{{form.download_url}}{{ form.file.errors }} Lub {{form.file.label}}{{form.file}}
    +
    + + +{% endblock %} diff --git a/src/cover/templates/cover/image_detail.html b/src/cover/templates/cover/image_detail.html new file mode 100755 index 00000000..db9b176f --- /dev/null +++ b/src/cover/templates/cover/image_detail.html @@ -0,0 +1,59 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} +{% load thumbnail %} +{% load build_absolute_uri from fnp_common %} + +{% block titleextra %}{% trans "Cover image" %}{% endblock %} + +{% block content %} +

    {% trans "Cover image" %}

    + +
    + + + +
    {{ object.title }} by {{ object.author }}, + {% if object.license_url %}{% endif %} + {{ object.license_name }} + {% if object.license_url %}{% endif %} + +
    {% trans "source" %}: {{ object.download_url }} +
    + + +{% if editable %} +
    + {% csrf_token %} +{% endif %} + + {{ form.as_table }} + {% if editable %} + + {% endif %} +
    +{% if editable %}
    {% endif %} + + +

    {% trans "Used in:" %}

    +{% if object.book_set %} +
      + {% for book in object.book_set.all %} +
    • {{ book }}
    • + {% endfor %} +
    +{% else %} +

    {% trans "None" %}

    +{% endif %} + + + +{% endblock %} diff --git a/src/cover/templates/cover/image_list.html b/src/cover/templates/cover/image_list.html new file mode 100755 index 00000000..50443edd --- /dev/null +++ b/src/cover/templates/cover/image_list.html @@ -0,0 +1,31 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} +{% load thumbnail pagination_tags %} + +{% block titleextra %}{% trans "Cover images" %}{% endblock %} + +{% block content %} +

    {% trans "Cover images" %}

    + +{% if can_add %} + {% trans "Add new" %} +{% endif %} + +
      +{% autopaginate object_list 100 %} +{% for image in object_list %} + + + +
      + {{ image }}
      +{% endfor %} +{% paginate %} +
    + +{% endblock %} diff --git a/src/cover/tests.py b/src/cover/tests.py new file mode 100644 index 00000000..be8d0033 --- /dev/null +++ b/src/cover/tests.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from nose.tools import * +from django.test import TestCase +from cover.forms import FlickrForm + + +class FlickrTests(TestCase): + def test_flickr(self): + form = FlickrForm({"source_url": "https://www.flickr.com/photos/rczajka/6941928577/in/photostream"}) + self.assertTrue(form.is_valid()) + self.assertEqual(form.cleaned_data['source_url'], "https://www.flickr.com/photos/rczajka/6941928577/") + self.assertEqual(form.cleaned_data['author'], "Radek Czajka@Flickr") + self.assertEqual(form.cleaned_data['title'], u"Pirate Stańczyk") + self.assertEqual(form.cleaned_data['license_name'], "CC BY 2.0") + self.assertEqual(form.cleaned_data['license_url'], "https://creativecommons.org/licenses/by/2.0/") + self.assertTrue('.staticflickr.com' in form.cleaned_data['download_url']) diff --git a/src/cover/urls.py b/src/cover/urls.py new file mode 100644 index 00000000..1146f62d --- /dev/null +++ b/src/cover/urls.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.conf.urls import patterns, url + + +urlpatterns = patterns('cover.views', + url(r'^preview/$', 'preview_from_xml', name='cover_preview'), + url(r'^preview/(?P[^/]+)/$', 'preview', name='cover_preview'), + url(r'^preview/(?P[^/]+)/(?P[^/]+)/$', + 'preview', name='cover_preview'), + url(r'^preview/(?P[^/]+)/(?P[^/]+)/(?P\d+)/$', + 'preview', name='cover_preview'), + + url(r'^image/$', 'image_list', name='cover_image_list'), + url(r'^image/(?P\d+)/?$', 'image', name='cover_image'), + url(r'^image/(?P\d+)/file/', 'image_file', name='cover_file'), + url(r'^add_image/$', 'add_image', name='cover_add_image'), +) diff --git a/src/cover/utils.py b/src/cover/utils.py new file mode 100755 index 00000000..51aee190 --- /dev/null +++ b/src/cover/utils.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +import json +import re +from urllib import FancyURLopener + +from django.contrib.sites.models import Site + + +class URLOpener(FancyURLopener): + @property + def version(self): + return 'FNP Redakcja (http://%s)' % Site.objects.get_current() + + +class FlickrError(Exception): + pass + + +def get_flickr_data(url): + m = re.match(r'(https?://)?(www\.|secure\.)?flickr\.com/photos/(?P[^/]+)/(?P\d+)/?', url) + if not m: + raise FlickrError("It doesn't look like Flickr URL.") + author_slug, img_id = m.group('author'), m.group('img') + base_url = "https://www.flickr.com/photos/%s/%s/" % (author_slug, img_id) + try: + html = URLOpener().open(url).read().decode('utf-8') + except IOError: + raise FlickrError('Error reading page') + match = re.search(r']* rel="license ', html) + if not match: + raise FlickrError('License not found.') + else: + license_url = match.group(1) + re_license = re.compile(r'https?://creativecommons.org/licenses/([^/]*)/([^/]*)/.*') + m = re_license.match(license_url) + if not m: + re_pd = re.compile(r'https?://creativecommons.org/publicdomain/([^/]*)/([^/]*)/.*') + m = re_pd.match(license_url) + if not m: + raise FlickrError('License does not look like CC: %s' % license_url) + if m.group(1).lower() == 'zero': + license_name = 'Public domain (CC0 %s)' % m.group(2) + else: + license_name = 'Public domain' + else: + license_name = 'CC %s %s' % (m.group(1).upper(), m.group(2)) + m = re.search(r']* class="owner-name [^>]*>([^<]*)<', html) + if m: + author = "%s@Flickr" % m.group(1) + else: + raise FlickrError('Error reading author name.') + m = re.search(r']*>(.*?)', html, re.S) + if not m: + raise FlickrError('Error reading image title.') + title = m.group(1).strip() + m = re.search(r'modelExport: (\{.*\})', html) + try: + assert m + download_url = 'https:' + json.loads(m.group(1))['main']['photo-models'][0]['sizes']['o']['url'] + except (AssertionError, ValueError, IndexError, KeyError): + raise FlickrError('Error reading image URL.') + return { + 'source_url': base_url, + 'license_url': license_url, + 'license_name': license_name, + 'author': author, + 'title': title, + 'download_url': download_url, + } diff --git a/src/cover/views.py b/src/cover/views.py new file mode 100644 index 00000000..3f2c46fa --- /dev/null +++ b/src/cover/views.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +import os.path +from django.conf import settings +from django.contrib.auth.decorators import permission_required +from django.http import HttpResponse, HttpResponseRedirect, Http404 +from django.shortcuts import get_object_or_404, render +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_POST +from catalogue.helpers import active_tab +from catalogue.models import Chunk +from cover.models import Image +from cover import forms + +PREVIEW_SIZE = (216, 300) + + +def preview(request, book, chunk=None, rev=None): + """Creates a cover image. + + If chunk and rev number are given, use version from given revision. + If rev is not given, use publishable version. + """ + from PIL import Image + from librarian.cover import make_cover + from librarian.dcparser import BookInfo + + chunk = Chunk.get(book, chunk) + if rev is not None: + try: + revision = chunk.at_revision(rev) + except Chunk.change_model.DoesNotExist: + raise Http404 + else: + revision = chunk.publishable() + if revision is None: + raise Http404 + xml = revision.materialize().encode('utf-8') + + try: + info = BookInfo.from_bytes(xml) + except: + return HttpResponseRedirect(os.path.join(settings.STATIC_URL, "img/sample_cover.png")) + cover = make_cover(info) + response = HttpResponse(content_type=cover.mime_type()) + img = cover.image().resize(PREVIEW_SIZE, Image.ANTIALIAS) + img.save(response, cover.format) + return response + + +@csrf_exempt +@require_POST +def preview_from_xml(request): + from hashlib import sha1 + from PIL import Image + from os import makedirs + from lxml import etree + from librarian.cover import make_cover + from librarian.dcparser import BookInfo + + xml = request.POST['xml'] + try: + info = BookInfo.from_bytes(xml.encode('utf-8')) + except: + return HttpResponse(os.path.join(settings.STATIC_URL, "img/sample_cover.png")) + coverid = sha1(etree.tostring(info.to_etree())).hexdigest() + cover = make_cover(info) + + cover_dir = 'cover/preview' + try: + makedirs(os.path.join(settings.MEDIA_ROOT, cover_dir)) + except OSError: + pass + fname = os.path.join(cover_dir, "%s.%s" % (coverid, cover.ext())) + img = cover.image().resize(PREVIEW_SIZE, Image.ANTIALIAS) + img.save(os.path.join(settings.MEDIA_ROOT, fname)) + return HttpResponse(os.path.join(settings.MEDIA_URL, fname)) + + +@active_tab('cover') +def image(request, pk): + img = get_object_or_404(Image, pk=pk) + + if request.user.has_perm('cover.change_image'): + if request.method == "POST": + form = forms.ImageEditForm(request.POST, request.FILES, instance=img) + if form.is_valid(): + form.save() + return HttpResponseRedirect(img.get_absolute_url()) + else: + form = forms.ImageEditForm(instance=img) + editable = True + else: + form = forms.ReadonlyImageEditForm(instance=img) + editable = False + + return render(request, "cover/image_detail.html", { + "object": Image.objects.get(id=img.id), + "form": form, + "editable": editable, + }) + + +def image_file(request, pk): + img = get_object_or_404(Image, pk=pk) + return HttpResponseRedirect(img.file.url) + + +@active_tab('cover') +def image_list(request): + return render(request, "cover/image_list.html", { + 'object_list': Image.objects.all(), + 'can_add': request.user.has_perm('cover.add_image'), + }) + + +@permission_required('cover.add_image') +@active_tab('cover') +def add_image(request): + form = ff = None + if request.method == 'POST': + if request.POST.get('form_id') == 'flickr': + ff = forms.FlickrForm(request.POST) + if ff.is_valid(): + form = forms.ImageAddForm(ff.cleaned_data) + else: + form = forms.ImageAddForm(request.POST, request.FILES) + if form.is_valid(): + obj = form.save() + return HttpResponseRedirect(obj.get_absolute_url()) + if form is None: + form = forms.ImageAddForm() + if ff is None: + ff = forms.FlickrForm() + return render(request, 'cover/add_image.html', { + 'form': form, + 'ff': ff, + }) diff --git a/src/dvcs/__init__.py b/src/dvcs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/dvcs/locale/pl/LC_MESSAGES/django.mo b/src/dvcs/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 00000000..dfd85c25 Binary files /dev/null and b/src/dvcs/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/dvcs/locale/pl/LC_MESSAGES/django.po b/src/dvcs/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 00000000..c0365d56 --- /dev/null +++ b/src/dvcs/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,124 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-12-14 15:25+0100\n" +"PO-Revision-Date: 2011-12-14 15:27+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" + +#: models.py:19 +msgid "name" +msgstr "nazwa" + +#: models.py:20 +msgid "slug" +msgstr "slug" + +#: models.py:22 +msgid "ordering" +msgstr "kolejność" + +#: models.py:70 +msgid "author" +msgstr "autor" + +#: models.py:71 +msgid "author name" +msgstr "imię i nazwisko autora" + +#: models.py:73 +#: models.py:77 +msgid "Used if author is not set." +msgstr "Używane, gdy nie jest ustawiony autor." + +#: models.py:75 +msgid "author email" +msgstr "e-mail autora" + +#: models.py:79 +msgid "revision" +msgstr "rewizja" + +#: models.py:83 +msgid "parent" +msgstr "rodzic" + +#: models.py:88 +msgid "merge parent" +msgstr "drugi rodzic" + +#: models.py:91 +msgid "description" +msgstr "opis" + +#: models.py:94 +msgid "publishable" +msgstr "do publikacji" + +#: models.py:176 +msgid "tag" +msgstr "tag" + +#: models.py:176 +#: models.py:178 +#: models.py:194 +#: models.py:196 +msgid "for:" +msgstr "dla:" + +#: models.py:178 +#: models.py:202 +msgid "tags" +msgstr "tagi" + +#: models.py:194 +msgid "change" +msgstr "zmiana" + +#: models.py:196 +msgid "changes" +msgstr "zmiany" + +#: models.py:201 +msgid "document" +msgstr "dokument" + +#: models.py:203 +msgid "data" +msgstr "dane" + +#: models.py:217 +msgid "stage" +msgstr "etap" + +#: models.py:225 +msgid "head" +msgstr "głowica" + +#: models.py:226 +msgid "This document's current head." +msgstr "Aktualna wersja dokumentu." + +#: models.py:230 +msgid "creator" +msgstr "utworzył" + +#: models.py:245 +msgid "user" +msgstr "użytkownik" + +#: models.py:245 +msgid "Work assignment." +msgstr "Przypisanie pracy użytkownikowi." + diff --git a/src/dvcs/models.py b/src/dvcs/models.py new file mode 100644 index 00000000..24bdeb3a --- /dev/null +++ b/src/dvcs/models.py @@ -0,0 +1,332 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from datetime import datetime +import os.path + +from django.contrib.auth.models import User +from django.core.files.base import ContentFile +from django.db import models, transaction +from django.db.models.base import ModelBase +from django.utils.translation import string_concat, ugettext_lazy as _ +from mercurial import simplemerge + +from django.conf import settings +from dvcs.signals import post_commit, post_publishable +from dvcs.storage import GzipFileSystemStorage + + +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: + abstract = True + 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 = {} + + def get_next(self): + """ + Returns the next tag - stage to work on. + Returns None for the last stage. + """ + try: + return type(self).objects.filter(ordering__gt=self.ordering)[0] + except IndexError: + return None + +models.signals.pre_save.connect(Tag.listener_changed, sender=Tag) + + +def data_upload_to(instance, filename): + return "%d/%d" % (instance.tree.pk, instance.pk) + + +class Change(models.Model): + """ + Single document change related to previous change. The "parent" + argument points to the version against which this change has been + recorded. Initial text will have a null parent. + + Data file contains a gzipped text of the document. + """ + author = models.ForeignKey(User, null=True, blank=True, verbose_name=_('author')) + author_name = models.CharField( + _('author name'), max_length=128, null=True, blank=True, help_text=_("Used if author is not set.")) + author_email = models.CharField( + _('author email'), max_length=128, null=True, blank=True, help_text=_("Used if author is not set.")) + revision = models.IntegerField(_('revision'), db_index=True) + + parent = models.ForeignKey( + 'self', null=True, blank=True, default=None, verbose_name=_('parent'), related_name="children") + + merge_parent = models.ForeignKey( + 'self', null=True, blank=True, default=None, verbose_name=_('merge parent'), related_name="merge_children") + + description = models.TextField(_('description'), blank=True, default='') + created_at = models.DateTimeField(editable=False, db_index=True, default=datetime.now) + publishable = models.BooleanField(_('publishable'), default=False) + + class Meta: + abstract = True + ordering = ('created_at',) + unique_together = ['tree', 'revision'] + + def __unicode__(self): + return u"Id: %r, Tree %r, Parent %r, Data: %s" % (self.id, self.tree_id, self.parent_id, self.data) + + def author_str(self): + if self.author: + return "%s %s <%s>" % ( + self.author.first_name, + self.author.last_name, + self.author.email) + else: + return "%s <%s>" % ( + self.author_name, + self.author_email + ) + + def save(self, *args, **kwargs): + """ + take the next available revision number if none yet + """ + if self.revision is None: + tree_rev = self.tree.revision() + if tree_rev is None: + self.revision = 1 + else: + self.revision = tree_rev + 1 + return super(Change, self).save(*args, **kwargs) + + def materialize(self): + f = self.data.storage.open(self.data) + text = f.read() + f.close() + return unicode(text, 'utf-8') + + def merge_with(self, other, author=None, + author_name=None, author_email=None, + description=u"Automatic merge."): + """Performs an automatic merge after straying commits.""" + assert self.tree_id == other.tree_id # same tree + if other.parent_id == self.pk: + # immediate child - fast forward + return other + + local = self.materialize().encode('utf-8') + base = other.parent.materialize().encode('utf-8') + remote = other.materialize().encode('utf-8') + + merge = simplemerge.Merge3Text(base, local, remote) + result = ''.join(merge.merge_lines()) + merge_node = self.children.create( + merge_parent=other, tree=self.tree, + author=author, + author_name=author_name, + author_email=author_email, + description=description) + merge_node.data.save('', ContentFile(result)) + return merge_node + + def revert(self, **kwargs): + """ commit this version of a doc as new head """ + self.tree.commit(text=self.materialize(), **kwargs) + + def set_publishable(self, publishable): + self.publishable = publishable + self.save() + post_publishable.send(sender=self, publishable=publishable) + + +def create_tag_model(model): + name = model.__name__ + 'Tag' + + class Meta(Tag.Meta): + app_label = model._meta.app_label + verbose_name = string_concat( + _("tag"), " ", _("for:"), " ", model._meta.verbose_name) + verbose_name_plural = string_concat( + _("tags"), " ", _("for:"), " ", model._meta.verbose_name) + + attrs = { + '__module__': model.__module__, + 'Meta': Meta, + } + return type(name, (Tag,), attrs) + + +def create_change_model(model): + name = model.__name__ + 'Change' + repo = GzipFileSystemStorage(location=model.REPO_PATH) + + class Meta(Change.Meta): + app_label = model._meta.app_label + verbose_name = string_concat( + _("change"), " ", _("for:"), " ", model._meta.verbose_name) + verbose_name_plural = string_concat( + _("changes"), " ", _("for:"), " ", model._meta.verbose_name) + + attrs = { + '__module__': model.__module__, + 'tree': models.ForeignKey(model, related_name='change_set', verbose_name=_('document')), + 'tags': models.ManyToManyField(model.tag_model, verbose_name=_('tags'), related_name='change_set'), + 'data': models.FileField(_('data'), upload_to=data_upload_to, storage=repo), + 'Meta': Meta, + } + return type(name, (Change,), attrs) + + +class DocumentMeta(ModelBase): + """Metaclass for Document models.""" + def __new__(cls, name, bases, attrs): + + model = super(DocumentMeta, cls).__new__(cls, name, bases, attrs) + if not model._meta.abstract: + # create a real Tag object and `stage' fk + model.tag_model = create_tag_model(model) + models.ForeignKey(model.tag_model, verbose_name=_('stage'), + null=True, blank=True).contribute_to_class(model, 'stage') + + # create real Change model and `head' fk + model.change_model = create_change_model(model) + + models.ForeignKey( + model.change_model, null=True, blank=True, default=None, + verbose_name=_('head'), help_text=_("This document's current head."), + editable=False).contribute_to_class(model, 'head') + + models.ForeignKey( + User, null=True, blank=True, editable=False, + verbose_name=_('creator'), related_name="created_%s" % name.lower() + ).contribute_to_class(model, 'creator') + + return model + + +class Document(models.Model): + """File in repository. Subclass it to use version control in your app.""" + + __metaclass__ = DocumentMeta + + # default repository path + REPO_PATH = os.path.join(settings.MEDIA_ROOT, 'dvcs') + + user = models.ForeignKey(User, null=True, blank=True, verbose_name=_('user'), help_text=_('Work assignment.')) + + class Meta: + abstract = True + + def __unicode__(self): + return u"{0}, HEAD: {1}".format(self.id, self.head_id) + + def materialize(self, change=None): + if self.head is None: + return u'' + if change is None: + change = self.head + elif not isinstance(change, Change): + change = self.change_set.get(pk=change) + return change.materialize() + + def commit(self, text, author=None, author_name=None, author_email=None, publishable=False, **kwargs): + """Commits a new revision. + + This will automatically merge the commit into the main branch, + if parent is not document's head. + + :param unicode text: new version of the document + :param parent: parent revision (head, if not specified) + :type parent: Change or None + :param User author: the commiter + :param unicode author_name: commiter name (if ``author`` not specified) + :param unicode author_email: commiter e-mail (if ``author`` not specified) + :param Tag[] tags: list of tags to apply to the new commit + :param bool publishable: set new commit as ready to publish + :returns: new head + """ + if 'parent' not in kwargs: + parent = self.head + else: + parent = kwargs['parent'] + if parent is not None and not isinstance(parent, Change): + parent = self.change_set.objects.get(pk=kwargs['parent']) + + tags = kwargs.get('tags', []) + if tags: + # set stage to next tag after the commited one + self.stage = max(tags, key=lambda t: t.ordering).get_next() + + change = self.change_set.create( + author=author, author_name=author_name, author_email=author_email, + description=kwargs.get('description', ''), publishable=publishable, parent=parent) + + change.tags = tags + change.data.save('', ContentFile(text.encode('utf-8'))) + change.save() + + if self.head: + # merge new change as new head + self.head = self.head.merge_with(change, author=author, + author_name=author_name, + author_email=author_email) + else: + self.head = change + self.save() + + post_commit.send(sender=self.head) + + return self.head + + def history(self): + return self.change_set.all().order_by('revision') + + def revision(self): + rev = self.change_set.aggregate( + models.Max('revision'))['revision__max'] + return rev + + def at_revision(self, rev): + """Returns a Change with given revision number.""" + return self.change_set.get(revision=rev) + + def publishable(self): + changes = self.history().filter(publishable=True) + if changes.exists(): + return changes.order_by('-revision')[0] + else: + return None + + @transaction.atomic + def prepend_history(self, other): + """Takes over the the other document's history and prepends to own.""" + + assert self != other + other_revs = other.change_set.all().count() + # workaround for a non-atomic UPDATE in SQLITE + self.change_set.all().update(revision=0-models.F('revision')) + self.change_set.all().update(revision=other_revs - models.F('revision')) + other.change_set.all().update(tree=self) + assert not other.change_set.exists() + other.delete() diff --git a/src/dvcs/signals.py b/src/dvcs/signals.py new file mode 100755 index 00000000..5da075be --- /dev/null +++ b/src/dvcs/signals.py @@ -0,0 +1,4 @@ +from django.dispatch import Signal + +post_commit = Signal() +post_publishable = Signal(providing_args=['publishable']) diff --git a/src/dvcs/storage.py b/src/dvcs/storage.py new file mode 100755 index 00000000..6bb5b595 --- /dev/null +++ b/src/dvcs/storage.py @@ -0,0 +1,18 @@ +from zlib import compress, decompress + +from django.core.files.base import ContentFile, File +from django.core.files.storage import FileSystemStorage + + +class GzipFileSystemStorage(FileSystemStorage): + def _open(self, name, mode='rb'): + """TODO: This is good for reading; what about writing?""" + f = open(self.path(name), 'rb') + text = f.read() + f.close() + return ContentFile(decompress(text)) + + def _save(self, name, content): + content = ContentFile(compress(content.read())) + + return super(GzipFileSystemStorage, self)._save(name, content) diff --git a/src/dvcs/tests/__init__.py b/src/dvcs/tests/__init__.py new file mode 100755 index 00000000..868f00a3 --- /dev/null +++ b/src/dvcs/tests/__init__.py @@ -0,0 +1,178 @@ +from nose.tools import * +from django.test import TestCase +from dvcs.models import Document + + +class ADocument(Document): + class Meta: + app_label = 'dvcs' + + +class DocumentModelTests(TestCase): + + def assertTextEqual(self, given, expected): + return self.assertEqual(given, expected, + "Expected '''%s'''\n differs from text: '''%s'''" % (expected, given) + ) + + def test_empty_file(self): + doc = ADocument.objects.create() + self.assertTextEqual(doc.materialize(), u"") + + def test_single_commit(self): + doc = ADocument.objects.create() + doc.commit(text=u"Ala ma kota", description="Commit #1") + self.assertTextEqual(doc.materialize(), u"Ala ma kota") + + def test_chained_commits(self): + doc = ADocument.objects.create() + text1 = u""" + Line #1 + Line #2 is cool + """ + text2 = u""" + Line #1 + Line #2 is hot + """ + text3 = u""" + Line #1 + ... is hot + Line #3 ate Line #2 + """ + + c1 = doc.commit(description="Commit #1", text=text1) + c2 = doc.commit(description="Commit #2", text=text2) + c3 = doc.commit(description="Commit #3", text=text3) + + self.assertTextEqual(doc.materialize(), text3) + self.assertTextEqual(doc.materialize(change=c3), text3) + self.assertTextEqual(doc.materialize(change=c2), text2) + self.assertTextEqual(doc.materialize(change=c1), text1) + + def test_parallel_commit_noconflict(self): + doc = ADocument.objects.create() + text1 = u""" + Line #1 + Line #2 + """ + text2 = u""" + Line #1 is hot + Line #2 + """ + text3 = u""" + Line #1 + Line #2 + Line #3 + """ + text_merged = u""" + Line #1 is hot + Line #2 + Line #3 + """ + + base = doc.commit(description="Commit #1", text=text1) + c1 = doc.commit(description="Commit #2", text=text2) + commits = doc.change_set.count() + c2 = doc.commit(description="Commit #3", text=text3, parent=base) + self.assertEqual(doc.change_set.count(), commits + 2, + u"Parallel commits should create an additional merge commit") + self.assertTextEqual(doc.materialize(), text_merged) + + def test_parallel_commit_conflict(self): + doc = ADocument.objects.create() + text1 = u""" + Line #1 + Line #2 + Line #3 + """ + text2 = u""" + Line #1 + Line #2 is hot + Line #3 + """ + text3 = u""" + Line #1 + Line #2 is cool + Line #3 + """ + text_merged = u""" + Line #1 +<<<<<<< + Line #2 is hot +======= + Line #2 is cool +>>>>>>> + Line #3 + """ + base = doc.commit(description="Commit #1", text=text1) + c1 = doc.commit(description="Commit #2", text=text2) + commits = doc.change_set.count() + c2 = doc.commit(description="Commit #3", text=text3, parent=base) + self.assertEqual(doc.change_set.count(), commits + 2, + u"Parallel commits should create an additional merge commit") + self.assertTextEqual(doc.materialize(), text_merged) + + + def test_multiple_parallel_commits(self): + text_a1 = u""" + Line #1 + + Line #2 + + Line #3 + """ + text_a2 = u""" + Line #1 * + + Line #2 + + Line #3 + """ + text_b1 = u""" + Line #1 + + Line #2 ** + + Line #3 + """ + text_c1 = u""" + Line #1 + + Line #2 + + Line #3 *** + """ + text_merged = u""" + Line #1 * + + Line #2 ** + + Line #3 *** + """ + + + doc = ADocument.objects.create() + c1 = doc.commit(description="Commit A1", text=text_a1) + c2 = doc.commit(description="Commit A2", text=text_a2, parent=c1) + c3 = doc.commit(description="Commit B1", text=text_b1, parent=c1) + c4 = doc.commit(description="Commit C1", text=text_c1, parent=c1) + self.assertTextEqual(doc.materialize(), text_merged) + + + def test_prepend_history(self): + doc1 = ADocument.objects.create() + doc2 = ADocument.objects.create() + doc1.commit(text='Commit 1') + doc2.commit(text='Commit 2') + doc2.prepend_history(doc1) + self.assertEqual(ADocument.objects.all().count(), 1) + self.assertTextEqual(doc2.at_revision(1).materialize(), 'Commit 1') + self.assertTextEqual(doc2.materialize(), 'Commit 2') + + def test_prepend_to_self(self): + doc = ADocument.objects.create() + doc.commit(text='Commit 1') + with self.assertRaises(AssertionError): + doc.prepend_history(doc) + self.assertTextEqual(doc.materialize(), 'Commit 1') + diff --git a/src/email_mangler/__init__.py b/src/email_mangler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/email_mangler/locale/pl/LC_MESSAGES/django.mo b/src/email_mangler/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 00000000..ed20bfb8 Binary files /dev/null and b/src/email_mangler/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/email_mangler/locale/pl/LC_MESSAGES/django.po b/src/email_mangler/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 00000000..046b8835 --- /dev/null +++ b/src/email_mangler/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,27 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-11-30 14:27+0100\n" +"PO-Revision-Date: 2011-11-30 14:27+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" + +#: templatetags/email.py:17 +msgid "at" +msgstr "na" + +#: templatetags/email.py:18 +msgid "dot" +msgstr "kropka" + diff --git a/src/email_mangler/models.py b/src/email_mangler/models.py new file mode 100644 index 00000000..e69de29b diff --git a/src/email_mangler/templatetags/__init__.py b/src/email_mangler/templatetags/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/src/email_mangler/templatetags/email.py b/src/email_mangler/templatetags/email.py new file mode 100755 index 00000000..376117a8 --- /dev/null +++ b/src/email_mangler/templatetags/email.py @@ -0,0 +1,25 @@ +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext as _ +from django import template + +register = template.Library() + + +@register.filter +def email_link(email): + email_safe = escape(email) + try: + name, domain = email_safe.split('@', 1) + except ValueError: + return email + + at = escape(_('at')) + dot = escape(_('dot')) + mangled = "%s %s %s" % (name, at, (' %s ' % dot).join(domain.split('.'))) + return mark_safe("%(mangled)s" % { + 'name': name.encode('rot13'), + 'domain': domain.encode('rot13'), + 'mangled': mangled, + }) diff --git a/src/fileupload/__init__.py b/src/fileupload/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/fileupload/forms.py b/src/fileupload/forms.py new file mode 100644 index 00000000..f5e10699 --- /dev/null +++ b/src/fileupload/forms.py @@ -0,0 +1,4 @@ +from django import forms + +class UploadForm(forms.Form): + files = forms.FileField() diff --git a/src/fileupload/locale/pl/LC_MESSAGES/django.mo b/src/fileupload/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 00000000..8fdb9c48 Binary files /dev/null and b/src/fileupload/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/fileupload/locale/pl/LC_MESSAGES/django.po b/src/fileupload/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 00000000..a4b60990 --- /dev/null +++ b/src/fileupload/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,39 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-03-07 16:27+0100\n" +"PO-Revision-Date: 2013-03-07 16:27+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: templates/fileupload/picture_form.html:18 +msgid "Browse:" +msgstr "Przeglądanie:" + +#: templates/fileupload/picture_form.html:35 +msgid "Add files..." +msgstr "Dodaj pliki..." + +#: templates/fileupload/picture_form.html:40 +msgid "Start upload" +msgstr "Zacznij wysyłać" + +#: templates/fileupload/picture_form.html:44 +msgid "Cancel upload" +msgstr "Anuluj wysyłanie" + +#: templates/fileupload/picture_form.html:48 +msgid "Delete" +msgstr "Usuń" + diff --git a/src/fileupload/models.py b/src/fileupload/models.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/fileupload/models.py @@ -0,0 +1 @@ + diff --git a/src/fileupload/static/fileupload/css/bootstrap-image-gallery.min.css b/src/fileupload/static/fileupload/css/bootstrap-image-gallery.min.css new file mode 100644 index 00000000..a2bffbfc --- /dev/null +++ b/src/fileupload/static/fileupload/css/bootstrap-image-gallery.min.css @@ -0,0 +1,21 @@ +@charset 'UTF-8'; +.modal-gallery{width:auto;max-height:none;} +.modal-gallery .modal-body{max-height:none;} +.modal-gallery .modal-title{display:inline-block;max-height:54px;overflow:hidden;} +.modal-gallery .modal-image{position:relative;margin:auto;min-width:128px;min-height:128px;overflow:hidden;cursor:pointer;} +.modal-gallery .modal-image:hover:before,.modal-gallery .modal-image:hover:after{content:'‹';position:absolute;top:50%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);z-index:1;} +.modal-gallery .modal-image:hover:after{content:'›';left:auto;right:15px;} +.modal-single .modal-image:hover:before,.modal-single .modal-image:hover:after{display:none;} +.modal-loading .modal-image{background:url(../img/loading.gif) center no-repeat;} +.modal-gallery.fade .modal-image{-webkit-transition:width 0.15s ease, height 0.15s ease;-moz-transition:width 0.15s ease, height 0.15s ease;-ms-transition:width 0.15s ease, height 0.15s ease;-o-transition:width 0.15s ease, height 0.15s ease;transition:width 0.15s ease, height 0.15s ease;} +.modal-gallery .modal-image *{position:absolute;top:0;opacity:0;filter:alpha(opacity=0);} +.modal-gallery.fade .modal-image *{-webkit-transition:opacity 0.5s linear;-moz-transition:opacity 0.5s linear;-ms-transition:opacity 0.5s linear;-o-transition:opacity 0.5s linear;transition:opacity 0.5s linear;} +.modal-gallery .modal-image *.in{opacity:1;filter:alpha(opacity=100);} +.modal-fullscreen{border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;background:transparent;overflow:hidden;} +.modal-fullscreen.modal-loading{border:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.modal-fullscreen .modal-body{padding:0;} +.modal-fullscreen .modal-header,.modal-fullscreen .modal-footer{position:absolute;top:0;right:0;left:0;background:transparent;border:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;opacity:0;z-index:2000;} +.modal-fullscreen .modal-footer{top:auto;bottom:0;} +.modal-fullscreen .close,.modal-fullscreen .modal-title{color:#fff;text-shadow:0 0 2px rgba(33, 33, 33, 0.8);} +.modal-fullscreen .modal-header:hover,.modal-fullscreen .modal-footer:hover{opacity:1;} +@media (max-width:480px){.modal-gallery .btn span{display:none;}} diff --git a/src/fileupload/static/fileupload/css/bootstrap.min.css b/src/fileupload/static/fileupload/css/bootstrap.min.css new file mode 100644 index 00000000..99995247 --- /dev/null +++ b/src/fileupload/static/fileupload/css/bootstrap.min.css @@ -0,0 +1,766 @@ +article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} +audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} +audio:not([controls]){display:none;} +a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +a:hover,a:active{outline:0;} +sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;} +sup{top:-0.5em;} +sub{bottom:-0.25em;} +img{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;} +button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;} +button,input{*overflow:visible;line-height:normal;} +button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;} +button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} +input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;} +input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +.clearfix{*zoom:1;} +.clearfix:before,.clearfix:after{display:table;content:"";} +.clearfix:after{clear:both;} +.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;} +.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} +.row{margin-left:-20px;*zoom:1;} +.row:before,.row:after{display:table;content:"";} +.row:after{clear:both;} +[class*="span"]{float:left;margin-left:20px;} +.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.span12{width:940px;} +.span11{width:860px;} +.span10{width:780px;} +.span9{width:700px;} +.span8{width:620px;} +.span7{width:540px;} +.span6{width:460px;} +.span5{width:380px;} +.span4{width:300px;} +.span3{width:220px;} +.span2{width:140px;} +.span1{width:60px;} +.offset12{margin-left:980px;} +.offset11{margin-left:900px;} +.offset10{margin-left:820px;} +.offset9{margin-left:740px;} +.offset8{margin-left:660px;} +.offset7{margin-left:580px;} +.offset6{margin-left:500px;} +.offset5{margin-left:420px;} +.offset4{margin-left:340px;} +.offset3{margin-left:260px;} +.offset2{margin-left:180px;} +.offset1{margin-left:100px;} +.row-fluid{width:100%;*zoom:1;} +.row-fluid:before,.row-fluid:after{display:table;content:"";} +.row-fluid:after{clear:both;} +.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.127659574%;*margin-left:2.0744680846382977%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} +.row-fluid [class*="span"]:first-child{margin-left:0;} +.row-fluid .span12{width:99.99999998999999%;*width:99.94680850063828%;} +.row-fluid .span11{width:91.489361693%;*width:91.4361702036383%;} +.row-fluid .span10{width:82.97872339599999%;*width:82.92553190663828%;} +.row-fluid .span9{width:74.468085099%;*width:74.4148936096383%;} +.row-fluid .span8{width:65.95744680199999%;*width:65.90425531263828%;} +.row-fluid .span7{width:57.446808505%;*width:57.3936170156383%;} +.row-fluid .span6{width:48.93617020799999%;*width:48.88297871863829%;} +.row-fluid .span5{width:40.425531911%;*width:40.3723404216383%;} +.row-fluid .span4{width:31.914893614%;*width:31.8617021246383%;} +.row-fluid .span3{width:23.404255317%;*width:23.3510638276383%;} +.row-fluid .span2{width:14.89361702%;*width:14.8404255306383%;} +.row-fluid .span1{width:6.382978723%;*width:6.329787233638298%;} +.container{margin-right:auto;margin-left:auto;*zoom:1;} +.container:before,.container:after{display:table;content:"";} +.container:after{clear:both;} +.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;} +.container-fluid:before,.container-fluid:after{display:table;content:"";} +.container-fluid:after{clear:both;} +p{margin:0 0 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;} +p small{font-size:11px;color:#999999;} +.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;} +.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;} +.page-header h1{line-height:1;} +ul,ol{padding:0;margin:0 0 9px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +ul{list-style:disc;} +ol{list-style:decimal;} +li{line-height:18px;} +ul.unstyled,ol.unstyled{margin-left:0;list-style:none;} +dl{margin-bottom:18px;} +dt,dd{line-height:18px;} +dt{font-weight:bold;line-height:17px;} +dd{margin-left:9px;} +.dl-horizontal dt{float:left;width:120px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap;} +.dl-horizontal dd{margin-left:130px;} +hr{margin:18px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;} +strong{font-weight:bold;} +em{font-style:italic;} +.muted{color:#999999;} +abbr[title]{cursor:help;border-bottom:1px dotted #ddd;} +abbr.initialism{font-size:90%;text-transform:uppercase;} +blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;} +blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;} +blockquote small{display:block;line-height:18px;color:#999999;} +blockquote small:before{content:'\2014 \00A0';} +blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;} +blockquote.pull-right p,blockquote.pull-right small{text-align:right;} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +address{display:block;margin-bottom:18px;font-style:normal;line-height:18px;} +small{font-size:100%;} +cite{font-style:normal;} +code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;} +pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.025px;line-height:18px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +pre.prettyprint{margin-bottom:18px;} +pre code{padding:0;color:inherit;background-color:transparent;border:0;} +.pre-scrollable{max-height:340px;overflow-y:scroll;} +form{margin:0 0 18px;} +fieldset{padding:0;margin:0;border:0;} +legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #eee;} +legend small{font-size:13.5px;color:#999999;} +label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:18px;} +input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;} +label{display:block;margin-bottom:5px;color:#333333;} +input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555555;background-color:#ffffff;border:1px solid #cccccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.uneditable-textarea{width:auto;height:auto;} +label input,label textarea,label select{display:block;} +input[type="image"],input[type="checkbox"],input[type="radio"]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer;background-color:transparent;border:0 \9;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +input[type="image"]{border:0;} +input[type="file"]{width:auto;padding:initial;line-height:initial;background-color:#ffffff;background-color:initial;border:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +input[type="button"],input[type="reset"],input[type="submit"]{width:auto;height:auto;} +select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px;} +input[type="file"]{line-height:18px \9;} +select{width:220px;background-color:#ffffff;} +select[multiple],select[size]{height:auto;} +input[type="image"]{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +textarea{height:auto;} +input[type="hidden"]{display:none;} +.radio,.checkbox{min-height:18px;padding-left:18px;} +.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;} +.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;} +.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;} +.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;} +input,textarea{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;} +input:focus,textarea:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);} +input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus,select:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.input-mini{width:60px;} +.input-small{width:90px;} +.input-medium{width:150px;} +.input-large{width:210px;} +.input-xlarge{width:270px;} +.input-xxlarge{width:530px;} +input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;} +input,textarea,.uneditable-input{margin-left:0;} +input.span12,textarea.span12,.uneditable-input.span12{width:930px;} +input.span11,textarea.span11,.uneditable-input.span11{width:850px;} +input.span10,textarea.span10,.uneditable-input.span10{width:770px;} +input.span9,textarea.span9,.uneditable-input.span9{width:690px;} +input.span8,textarea.span8,.uneditable-input.span8{width:610px;} +input.span7,textarea.span7,.uneditable-input.span7{width:530px;} +input.span6,textarea.span6,.uneditable-input.span6{width:450px;} +input.span5,textarea.span5,.uneditable-input.span5{width:370px;} +input.span4,textarea.span4,.uneditable-input.span4{width:290px;} +input.span3,textarea.span3,.uneditable-input.span3{width:210px;} +input.span2,textarea.span2,.uneditable-input.span2{width:130px;} +input.span1,textarea.span1,.uneditable-input.span1{width:50px;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eeeeee;border-color:#ddd;} +input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;} +.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;} +.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853;} +.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e;} +.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;} +.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;} +.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48;} +.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392;} +.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;} +.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;} +.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847;} +.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b;} +.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;} +input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;} +input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} +.form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#f5f5f5;border-top:1px solid #ddd;*zoom:1;} +.form-actions:before,.form-actions:after{display:table;content:"";} +.form-actions:after{clear:both;} +.uneditable-input{overflow:hidden;white-space:nowrap;cursor:not-allowed;background-color:#ffffff;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);} +:-moz-placeholder{color:#999999;} +::-webkit-input-placeholder{color:#999999;} +.help-block,.help-inline{color:#555555;} +.help-block{display:block;margin-bottom:9px;} +.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1;} +.input-prepend,.input-append{margin-bottom:5px;} +.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:middle;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{z-index:2;} +.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc;} +.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;height:18px;min-width:16px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #ffffff;vertical-align:middle;background-color:#eeeeee;border:1px solid #ccc;} +.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.input-prepend .active,.input-append .active{background-color:#a9dba9;border-color:#46a546;} +.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;} +.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append .uneditable-input{border-right-color:#ccc;border-left-color:#eee;} +.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;} +.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;*zoom:1;} +.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;} +.form-search label,.form-inline label{display:inline-block;} +.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;} +.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;} +.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;} +.control-group{margin-bottom:9px;} +legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate;} +.form-horizontal .control-group{margin-bottom:18px;*zoom:1;} +.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";} +.form-horizontal .control-group:after{clear:both;} +.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right;} +.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:160px;*margin-left:0;} +.form-horizontal .controls:first-child{*padding-left:160px;} +.form-horizontal .help-block{margin-top:9px;margin-bottom:0;} +.form-horizontal .form-actions{padding-left:160px;} +table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;} +.table{width:100%;margin-bottom:18px;} +.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;} +.table th{font-weight:bold;} +.table thead th{vertical-align:bottom;} +.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;} +.table tbody+tbody{border-top:2px solid #dddddd;} +.table-condensed th,.table-condensed td{padding:4px 5px;} +.table-bordered{border:1px solid #dddddd;border-collapse:separate;*border-collapse:collapsed;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;} +.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;} +.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px;} +.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px;} +.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;} +.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;} +.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;} +.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;} +table .span1{float:none;width:44px;margin-left:0;} +table .span2{float:none;width:124px;margin-left:0;} +table .span3{float:none;width:204px;margin-left:0;} +table .span4{float:none;width:284px;margin-left:0;} +table .span5{float:none;width:364px;margin-left:0;} +table .span6{float:none;width:444px;margin-left:0;} +table .span7{float:none;width:524px;margin-left:0;} +table .span8{float:none;width:604px;margin-left:0;} +table .span9{float:none;width:684px;margin-left:0;} +table .span10{float:none;width:764px;margin-left:0;} +table .span11{float:none;width:844px;margin-left:0;} +table .span12{float:none;width:924px;margin-left:0;} +table .span13{float:none;width:1004px;margin-left:0;} +table .span14{float:none;width:1084px;margin-left:0;} +table .span15{float:none;width:1164px;margin-left:0;} +table .span16{float:none;width:1244px;margin-left:0;} +table .span17{float:none;width:1324px;margin-left:0;} +table .span18{float:none;width:1404px;margin-left:0;} +table .span19{float:none;width:1484px;margin-left:0;} +table .span20{float:none;width:1564px;margin-left:0;} +table .span21{float:none;width:1644px;margin-left:0;} +table .span22{float:none;width:1724px;margin-left:0;} +table .span23{float:none;width:1804px;margin-left:0;} +table .span24{float:none;width:1884px;margin-left:0;} +[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;} +[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;} +.icon-white{background-image:url("../img/glyphicons-halflings-white.png");} +.icon-glass{background-position:0 0;} +.icon-music{background-position:-24px 0;} +.icon-search{background-position:-48px 0;} +.icon-envelope{background-position:-72px 0;} +.icon-heart{background-position:-96px 0;} +.icon-star{background-position:-120px 0;} +.icon-star-empty{background-position:-144px 0;} +.icon-user{background-position:-168px 0;} +.icon-film{background-position:-192px 0;} +.icon-th-large{background-position:-216px 0;} +.icon-th{background-position:-240px 0;} +.icon-th-list{background-position:-264px 0;} +.icon-ok{background-position:-288px 0;} +.icon-remove{background-position:-312px 0;} +.icon-zoom-in{background-position:-336px 0;} +.icon-zoom-out{background-position:-360px 0;} +.icon-off{background-position:-384px 0;} +.icon-signal{background-position:-408px 0;} +.icon-cog{background-position:-432px 0;} +.icon-trash{background-position:-456px 0;} +.icon-home{background-position:0 -24px;} +.icon-file{background-position:-24px -24px;} +.icon-time{background-position:-48px -24px;} +.icon-road{background-position:-72px -24px;} +.icon-download-alt{background-position:-96px -24px;} +.icon-download{background-position:-120px -24px;} +.icon-upload{background-position:-144px -24px;} +.icon-inbox{background-position:-168px -24px;} +.icon-play-circle{background-position:-192px -24px;} +.icon-repeat{background-position:-216px -24px;} +.icon-refresh{background-position:-240px -24px;} +.icon-list-alt{background-position:-264px -24px;} +.icon-lock{background-position:-287px -24px;} +.icon-flag{background-position:-312px -24px;} +.icon-headphones{background-position:-336px -24px;} +.icon-volume-off{background-position:-360px -24px;} +.icon-volume-down{background-position:-384px -24px;} +.icon-volume-up{background-position:-408px -24px;} +.icon-qrcode{background-position:-432px -24px;} +.icon-barcode{background-position:-456px -24px;} +.icon-tag{background-position:0 -48px;} +.icon-tags{background-position:-25px -48px;} +.icon-book{background-position:-48px -48px;} +.icon-bookmark{background-position:-72px -48px;} +.icon-print{background-position:-96px -48px;} +.icon-camera{background-position:-120px -48px;} +.icon-font{background-position:-144px -48px;} +.icon-bold{background-position:-167px -48px;} +.icon-italic{background-position:-192px -48px;} +.icon-text-height{background-position:-216px -48px;} +.icon-text-width{background-position:-240px -48px;} +.icon-align-left{background-position:-264px -48px;} +.icon-align-center{background-position:-288px -48px;} +.icon-align-right{background-position:-312px -48px;} +.icon-align-justify{background-position:-336px -48px;} +.icon-list{background-position:-360px -48px;} +.icon-indent-left{background-position:-384px -48px;} +.icon-indent-right{background-position:-408px -48px;} +.icon-facetime-video{background-position:-432px -48px;} +.icon-picture{background-position:-456px -48px;} +.icon-pencil{background-position:0 -72px;} +.icon-map-marker{background-position:-24px -72px;} +.icon-adjust{background-position:-48px -72px;} +.icon-tint{background-position:-72px -72px;} +.icon-edit{background-position:-96px -72px;} +.icon-share{background-position:-120px -72px;} +.icon-check{background-position:-144px -72px;} +.icon-move{background-position:-168px -72px;} +.icon-step-backward{background-position:-192px -72px;} +.icon-fast-backward{background-position:-216px -72px;} +.icon-backward{background-position:-240px -72px;} +.icon-play{background-position:-264px -72px;} +.icon-pause{background-position:-288px -72px;} +.icon-stop{background-position:-312px -72px;} +.icon-forward{background-position:-336px -72px;} +.icon-fast-forward{background-position:-360px -72px;} +.icon-step-forward{background-position:-384px -72px;} +.icon-eject{background-position:-408px -72px;} +.icon-chevron-left{background-position:-432px -72px;} +.icon-chevron-right{background-position:-456px -72px;} +.icon-plus-sign{background-position:0 -96px;} +.icon-minus-sign{background-position:-24px -96px;} +.icon-remove-sign{background-position:-48px -96px;} +.icon-ok-sign{background-position:-72px -96px;} +.icon-question-sign{background-position:-96px -96px;} +.icon-info-sign{background-position:-120px -96px;} +.icon-screenshot{background-position:-144px -96px;} +.icon-remove-circle{background-position:-168px -96px;} +.icon-ok-circle{background-position:-192px -96px;} +.icon-ban-circle{background-position:-216px -96px;} +.icon-arrow-left{background-position:-240px -96px;} +.icon-arrow-right{background-position:-264px -96px;} +.icon-arrow-up{background-position:-289px -96px;} +.icon-arrow-down{background-position:-312px -96px;} +.icon-share-alt{background-position:-336px -96px;} +.icon-resize-full{background-position:-360px -96px;} +.icon-resize-small{background-position:-384px -96px;} +.icon-plus{background-position:-408px -96px;} +.icon-minus{background-position:-433px -96px;} +.icon-asterisk{background-position:-456px -96px;} +.icon-exclamation-sign{background-position:0 -120px;} +.icon-gift{background-position:-24px -120px;} +.icon-leaf{background-position:-48px -120px;} +.icon-fire{background-position:-72px -120px;} +.icon-eye-open{background-position:-96px -120px;} +.icon-eye-close{background-position:-120px -120px;} +.icon-warning-sign{background-position:-144px -120px;} +.icon-plane{background-position:-168px -120px;} +.icon-calendar{background-position:-192px -120px;} +.icon-random{background-position:-216px -120px;} +.icon-comment{background-position:-240px -120px;} +.icon-magnet{background-position:-264px -120px;} +.icon-chevron-up{background-position:-288px -120px;} +.icon-chevron-down{background-position:-313px -119px;} +.icon-retweet{background-position:-336px -120px;} +.icon-shopping-cart{background-position:-360px -120px;} +.icon-folder-close{background-position:-384px -120px;} +.icon-folder-open{background-position:-408px -120px;} +.icon-resize-vertical{background-position:-432px -119px;} +.icon-resize-horizontal{background-position:-456px -118px;} +.icon-hdd{background-position:0 -144px;} +.icon-bullhorn{background-position:-24px -144px;} +.icon-bell{background-position:-48px -144px;} +.icon-certificate{background-position:-72px -144px;} +.icon-thumbs-up{background-position:-96px -144px;} +.icon-thumbs-down{background-position:-120px -144px;} +.icon-hand-right{background-position:-144px -144px;} +.icon-hand-left{background-position:-168px -144px;} +.icon-hand-up{background-position:-192px -144px;} +.icon-hand-down{background-position:-216px -144px;} +.icon-circle-arrow-right{background-position:-240px -144px;} +.icon-circle-arrow-left{background-position:-264px -144px;} +.icon-circle-arrow-up{background-position:-288px -144px;} +.icon-circle-arrow-down{background-position:-312px -144px;} +.icon-globe{background-position:-336px -144px;} +.icon-wrench{background-position:-360px -144px;} +.icon-tasks{background-position:-384px -144px;} +.icon-filter{background-position:-408px -144px;} +.icon-briefcase{background-position:-432px -144px;} +.icon-fullscreen{background-position:-456px -144px;} +.dropup,.dropdown{position:relative;} +.dropdown-toggle{*margin-bottom:-3px;} +.dropdown-toggle:active,.open .dropdown-toggle{outline:0;} +.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";opacity:0.3;filter:alpha(opacity=30);} +.dropdown .caret{margin-top:8px;margin-left:2px;} +.dropdown:hover .caret,.open .caret{opacity:1;filter:alpha(opacity=100);} +.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:4px 0;margin:1px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;} +.dropdown-menu.pull-right{right:0;left:auto;} +.dropdown-menu .divider{*width:100%;height:1px;margin:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#333333;white-space:nowrap;} +.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;background-color:#0088cc;} +.open{*z-index:1000;} +.open .dropdown-menu{display:block;} +.pull-right .dropdown-menu{right:0;left:auto;} +.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"\2191";} +.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;} +.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);} +.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} +.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;} +.fade.in{opacity:1;} +.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;} +.collapse.in{height:auto;} +.close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);} +.close:hover{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);} +button.close{padding:0;cursor:pointer;background-color:transparent;border:0;-webkit-appearance:none;} +.btn{display:inline-block;*display:inline;padding:4px 10px 4px;margin-bottom:0;*margin-left:.3em;font-size:13px;line-height:18px;*line-height:20px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-ms-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(top, #ffffff, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-repeat:repeat-x;border:1px solid #cccccc;*border:0;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);} +.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9;} +.btn:active,.btn.active{background-color:#cccccc \9;} +.btn:first-child{*margin-left:0;} +.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-ms-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;} +.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);} +.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.btn-large [class^="icon-"]{margin-top:1px;} +.btn-small{padding:5px 9px;font-size:11px;line-height:16px;} +.btn-small [class^="icon-"]{margin-top:-1px;} +.btn-mini{padding:2px 6px;font-size:11px;line-height:14px;} +.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);} +.btn{border-color:#ccc;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn-primary{background-color:#0074cc;*background-color:#0055cc;background-image:-ms-linear-gradient(top, #0088cc, #0055cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0055cc));background-image:-webkit-linear-gradient(top, #0088cc, #0055cc);background-image:-o-linear-gradient(top, #0088cc, #0055cc);background-image:-moz-linear-gradient(top, #0088cc, #0055cc);background-image:linear-gradient(top, #0088cc, #0055cc);background-repeat:repeat-x;border-color:#0055cc #0055cc #003580;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#0088cc', endColorstr='#0055cc', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} +.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#0055cc;*background-color:#004ab3;} +.btn-primary:active,.btn-primary.active{background-color:#004099 \9;} +.btn-warning{background-color:#faa732;*background-color:#f89406;background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} +.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;*background-color:#df8505;} +.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;} +.btn-danger{background-color:#da4f49;*background-color:#bd362f;background-image:-ms-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(top, #ee5f5b, #bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} +.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a;} +.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;} +.btn-success{background-color:#5bb75b;*background-color:#51a351;background-image:-ms-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(top, #62c462, #51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} +.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249;} +.btn-success:active,.btn-success.active{background-color:#408140 \9;} +.btn-info{background-color:#49afcd;*background-color:#2f96b4;background-image:-ms-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(top, #5bc0de, #2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} +.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0;} +.btn-info:active,.btn-info.active{background-color:#24748c \9;} +.btn-inverse{background-color:#414141;*background-color:#222222;background-image:-ms-linear-gradient(top, #555555, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#222222));background-image:-webkit-linear-gradient(top, #555555, #222222);background-image:-o-linear-gradient(top, #555555, #222222);background-image:-moz-linear-gradient(top, #555555, #222222);background-image:linear-gradient(top, #555555, #222222);background-repeat:repeat-x;border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#555555', endColorstr='#222222', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);} +.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222222;*background-color:#151515;} +.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;} +button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;} +button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;} +button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;} +button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;} +button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;} +.btn-group{position:relative;*margin-left:.3em;*zoom:1;} +.btn-group:before,.btn-group:after{display:table;content:"";} +.btn-group:after{clear:both;} +.btn-group:first-child{*margin-left:0;} +.btn-group+.btn-group{margin-left:5px;} +.btn-toolbar{margin-top:9px;margin-bottom:9px;} +.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;} +.btn-group>.btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px;} +.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px;} +.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px;} +.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px;} +.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;} +.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;} +.btn-group>.dropdown-toggle{*padding-top:4px;padding-right:8px;*padding-bottom:4px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);} +.btn-group>.btn-mini.dropdown-toggle{padding-right:5px;padding-left:5px;} +.btn-group>.btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px;} +.btn-group>.btn-large.dropdown-toggle{padding-right:12px;padding-left:12px;} +.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);} +.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;} +.btn-group.open .btn-primary.dropdown-toggle{background-color:#0055cc;} +.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406;} +.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;} +.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;} +.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;} +.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222222;} +.btn .caret{margin-top:7px;margin-left:0;} +.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100);} +.btn-mini .caret{margin-top:5px;} +.btn-small .caret{margin-top:6px;} +.btn-large .caret{margin-top:6px;border-top-width:5px;border-right-width:5px;border-left-width:5px;} +.dropup .btn-large .caret{border-top:0;border-bottom:5px solid #000000;} +.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);} +.alert{padding:8px 35px 8px 14px;margin-bottom:18px;color:#c09853;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.alert-heading{color:inherit;} +.alert .close{position:relative;top:-2px;right:-21px;line-height:18px;} +.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6;} +.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7;} +.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1;} +.alert-block{padding-top:14px;padding-bottom:14px;} +.alert-block>p,.alert-block>ul{margin-bottom:0;} +.alert-block p+p{margin-top:5px;} +.nav{margin-bottom:18px;margin-left:0;list-style:none;} +.nav>li>a{display:block;} +.nav>li>a:hover{text-decoration:none;background-color:#eeeeee;} +.nav>.pull-right{float:right;} +.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;} +.nav li+.nav-header{margin-top:9px;} +.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0;} +.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.nav-list>li>a{padding:3px 15px;} +.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;} +.nav-list [class^="icon-"]{margin-right:2px;} +.nav-list .divider{*width:100%;height:1px;margin:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.nav-tabs,.nav-pills{*zoom:1;} +.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";} +.nav-tabs:after,.nav-pills:after{clear:both;} +.nav-tabs>li,.nav-pills>li{float:left;} +.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;} +.nav-tabs{border-bottom:1px solid #ddd;} +.nav-tabs>li{margin-bottom:-1px;} +.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:18px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} +.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;} +.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;cursor:default;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;} +.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#0088cc;} +.nav-stacked>li{float:none;} +.nav-stacked>li>a{margin-right:0;} +.nav-tabs.nav-stacked{border-bottom:0;} +.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} +.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} +.nav-tabs.nav-stacked>li>a:hover{z-index:2;border-color:#ddd;} +.nav-pills.nav-stacked>li>a{margin-bottom:3px;} +.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;} +.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;} +.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{margin-top:6px;border-top-color:#0088cc;border-bottom-color:#0088cc;} +.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580;} +.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;border-bottom-color:#333333;} +.nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;} +.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;} +.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);} +.tabs-stacked .open>a:hover{border-color:#999999;} +.tabbable{*zoom:1;} +.tabbable:before,.tabbable:after{display:table;content:"";} +.tabbable:after{clear:both;} +.tab-content{overflow:auto;} +.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;} +.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.tabs-below>.nav-tabs{border-top:1px solid #ddd;} +.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;} +.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} +.tabs-below>.nav-tabs>li>a:hover{border-top-color:#ddd;border-bottom-color:transparent;} +.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd;} +.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;} +.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;} +.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;} +.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} +.tabs-left>.nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;} +.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;} +.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;} +.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.tabs-right>.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;} +.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;} +.navbar{*position:relative;*z-index:2;margin-bottom:18px;overflow:visible;} +.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);} +.navbar .container{width:auto;} +.nav-collapse.collapse{height:auto;} +.navbar{color:#999999;} +.navbar .brand:hover{text-decoration:none;} +.navbar .brand{display:block;float:left;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#999999;} +.navbar .navbar-text{margin-bottom:0;line-height:40px;} +.navbar .navbar-link{color:#999999;} +.navbar .navbar-link:hover{color:#ffffff;} +.navbar .btn,.navbar .btn-group{margin-top:5px;} +.navbar .btn-group .btn{margin:0;} +.navbar-form{margin-bottom:0;*zoom:1;} +.navbar-form:before,.navbar-form:after{display:table;content:"";} +.navbar-form:after{clear:both;} +.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;} +.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0;} +.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;} +.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;} +.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;} +.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;} +.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;background-color:#626262;border:1px solid #151515;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0 rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0 rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0 rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;} +.navbar-search .search-query:-moz-placeholder{color:#cccccc;} +.navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;} +.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);} +.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;} +.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.navbar-fixed-top{top:0;} +.navbar-fixed-bottom{bottom:0;} +.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;} +.navbar .nav.pull-right{float:right;} +.navbar .nav>li{display:block;float:left;} +.navbar .nav>li>a{float:none;padding:9px 10px 11px;line-height:19px;color:#999999;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.navbar .btn{display:inline-block;padding:4px 10px 4px;margin:5px 5px 6px;line-height:18px;} +.navbar .btn-group{padding:5px 5px 6px;margin:0;} +.navbar .nav>li>a:hover{color:#ffffff;text-decoration:none;background-color:transparent;} +.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#ffffff;text-decoration:none;background-color:#222222;} +.navbar .divider-vertical{width:1px;height:40px;margin:0 9px;overflow:hidden;background-color:#222222;border-right:1px solid #333333;} +.navbar .nav.pull-right{margin-right:0;margin-left:10px;} +.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;background-color:#2c2c2c;*background-color:#222222;background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-image:-moz-linear-gradient(top, #333333, #222222);background-repeat:repeat-x;border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);} +.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{background-color:#222222;*background-color:#151515;} +.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#080808 \9;} +.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);} +.btn-navbar .icon-bar+.icon-bar{margin-top:3px;} +.navbar .dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0, 0, 0, 0.2);content:'';} +.navbar .dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #ffffff;border-left:6px solid transparent;content:'';} +.navbar-fixed-bottom .dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0, 0, 0, 0.2);} +.navbar-fixed-bottom .dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #ffffff;border-bottom:0;} +.navbar .nav li.dropdown .dropdown-toggle .caret,.navbar .nav li.dropdown.open .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar .nav li.dropdown.active .caret{opacity:1;filter:alpha(opacity=100);} +.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:transparent;} +.navbar .nav li.dropdown.active>.dropdown-toggle:hover{color:#ffffff;} +.navbar .pull-right .dropdown-menu,.navbar .dropdown-menu.pull-right{right:0;left:auto;} +.navbar .pull-right .dropdown-menu:before,.navbar .dropdown-menu.pull-right:before{right:12px;left:auto;} +.navbar .pull-right .dropdown-menu:after,.navbar .dropdown-menu.pull-right:after{right:13px;left:auto;} +.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);background-repeat:repeat-x;border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;} +.breadcrumb li{display:inline-block;*display:inline;text-shadow:0 1px 0 #ffffff;*zoom:1;} +.breadcrumb .divider{padding:0 5px;color:#999999;} +.breadcrumb .active a{color:#333333;} +.pagination{height:36px;margin:18px 0;} +.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} +.pagination li{display:inline;} +.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0;} +.pagination a:hover,.pagination .active a{background-color:#f5f5f5;} +.pagination .active a{color:#999999;cursor:default;} +.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999999;cursor:default;background-color:transparent;} +.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.pagination-centered{text-align:center;} +.pagination-right{text-align:right;} +.pager{margin-bottom:18px;margin-left:0;text-align:center;list-style:none;*zoom:1;} +.pager:before,.pager:after{display:table;content:"";} +.pager:after{clear:both;} +.pager li{display:inline;} +.pager a{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.pager a:hover{text-decoration:none;background-color:#f5f5f5;} +.pager .next a{float:right;} +.pager .previous a{float:left;} +.pager .disabled a,.pager .disabled a:hover{color:#999999;cursor:default;background-color:#fff;} +.modal-open .dropdown-menu{z-index:2050;} +.modal-open .dropdown.open{*z-index:2050;} +.modal-open .popover{z-index:2060;} +.modal-open .tooltip{z-index:2070;} +.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;} +.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);} +.modal{position:fixed;top:50%;left:50%;z-index:1050;width:560px;margin:-250px 0 0 -280px;overflow:auto;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;} +.modal.fade{top:-25%;-webkit-transition:opacity 0.3s linear,top 0.3s ease-out;-moz-transition:opacity 0.3s linear,top 0.3s ease-out;-ms-transition:opacity 0.3s linear,top 0.3s ease-out;-o-transition:opacity 0.3s linear,top 0.3s ease-out;transition:opacity 0.3s linear,top 0.3s ease-out;} +.modal.fade.in{top:50%;} +.modal-header{padding:9px 15px;border-bottom:1px solid #eee;} +.modal-header .close{margin-top:2px;} +.modal-body{max-height:400px;padding:15px;overflow-y:auto;} +.modal-form{margin-bottom:0;} +.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;} +.modal-footer:before,.modal-footer:after{display:table;content:"";} +.modal-footer:after{clear:both;} +.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px;} +.modal-footer .btn-group .btn+.btn{margin-left:-1px;} +.tooltip{position:absolute;z-index:1020;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible;} +.tooltip.in{opacity:0.8;filter:alpha(opacity=80);} +.tooltip.top{margin-top:-2px;} +.tooltip.right{margin-left:2px;} +.tooltip.bottom{margin-top:2px;} +.tooltip.left{margin-left:-2px;} +.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000000;border-right:5px solid transparent;border-left:5px solid transparent;} +.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000000;border-left:5px solid transparent;} +.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000000;border-bottom:5px solid transparent;} +.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.tooltip-arrow{position:absolute;width:0;height:0;} +.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;} +.popover.top{margin-top:-5px;} +.popover.right{margin-left:5px;} +.popover.bottom{margin-top:5px;} +.popover.left{margin-left:-5px;} +.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000000;border-right:5px solid transparent;border-left:5px solid transparent;} +.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000000;border-bottom:5px solid transparent;} +.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000000;border-left:5px solid transparent;} +.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.popover .arrow{position:absolute;width:0;height:0;} +.popover-inner{width:280px;padding:3px;overflow:hidden;background:#000000;background:rgba(0, 0, 0, 0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);} +.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;} +.popover-content{padding:14px;background-color:#ffffff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;} +.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;} +.thumbnails{margin-left:-20px;list-style:none;*zoom:1;} +.thumbnails:before,.thumbnails:after{display:table;content:"";} +.thumbnails:after{clear:both;} +.row-fluid .thumbnails{margin-left:0;} +.thumbnails>li{margin-bottom:18px;} +.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);} +a.thumbnail:hover{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} +.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto;} +.thumbnail .caption{padding:9px;} +.label,.badge{font-size:10.998px;font-weight:bold;line-height:14px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);white-space:nowrap;vertical-align:baseline;background-color:#999999;} +.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;} +a.label:hover,a.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;} +.label-important,.badge-important{background-color:#b94a48;} +.label-important[href],.badge-important[href]{background-color:#953b39;} +.label-warning,.badge-warning{background-color:#f89406;} +.label-warning[href],.badge-warning[href]{background-color:#c67605;} +.label-success,.badge-success{background-color:#468847;} +.label-success[href],.badge-success[href]{background-color:#356635;} +.label-info,.badge-info{background-color:#3a87ad;} +.label-info[href],.badge-info[href]{background-color:#2d6987;} +.label-inverse,.badge-inverse{background-color:#333333;} +.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;} +@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{height:18px;margin-bottom:18px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-ms-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(top, #f5f5f5, #f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);} +.progress .bar{width:0;height:18px;font-size:12px;color:#ffffff;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(top, #149bdf, #0480be);background-image:-ms-linear-gradient(top, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-ms-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;} +.progress-striped .bar{background-color:#149bdf;background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;} +.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;} +.progress-danger .bar{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);} +.progress-danger.progress-striped .bar{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-success .bar{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);} +.progress-success.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-info .bar{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);} +.progress-info.progress-striped .bar{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-warning .bar{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);} +.progress-warning.progress-striped .bar{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.accordion{margin-bottom:18px;} +.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.accordion-heading{border-bottom:0;} +.accordion-heading .accordion-toggle{display:block;padding:8px 15px;} +.accordion-toggle{cursor:pointer;} +.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;} +.carousel{position:relative;margin-bottom:18px;line-height:1;} +.carousel-inner{position:relative;width:100%;overflow:hidden;} +.carousel .item{position:relative;display:none;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-ms-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;} +.carousel .item>img{display:block;line-height:1;} +.carousel .active,.carousel .next,.carousel .prev{display:block;} +.carousel .active{left:0;} +.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;} +.carousel .next{left:100%;} +.carousel .prev{left:-100%;} +.carousel .next.left,.carousel .prev.right{left:0;} +.carousel .active.left{left:-100%;} +.carousel .active.right{left:100%;} +.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);} +.carousel-control.right{right:15px;left:auto;} +.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);} +.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:10px 15px 5px;background:#333333;background:rgba(0, 0, 0, 0.75);} +.carousel-caption h4,.carousel-caption p{color:#ffffff;} +.hero-unit{padding:60px;margin-bottom:30px;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit;} +.hero-unit p{font-size:18px;font-weight:200;line-height:27px;color:inherit;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.invisible{visibility:hidden;} diff --git a/src/fileupload/static/fileupload/css/jquery.fileupload-ui.css b/src/fileupload/static/fileupload/css/jquery.fileupload-ui.css new file mode 100644 index 00000000..e36a93df --- /dev/null +++ b/src/fileupload/static/fileupload/css/jquery.fileupload-ui.css @@ -0,0 +1,84 @@ +@charset 'UTF-8'; +/* + * jQuery File Upload UI Plugin CSS 6.3 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +.fileinput-button { + position: relative; + overflow: hidden; + float: left; + margin-right: 4px; +} +.fileinput-button input { + position: absolute; + top: 0; + right: 0; + margin: 0; + border: solid transparent; + border-width: 0 0 100px 200px; + opacity: 0; + filter: alpha(opacity=0); + -moz-transform: translate(-300px, 0) scale(4); + direction: ltr; + cursor: pointer; +} +.fileupload-buttonbar .btn, +.fileupload-buttonbar .toggle { + margin-bottom: 5px; +} +.files .progress { + width: 200px; +} +.progress-animated .bar { + background: url(../img/progressbar.gif) !important; + filter: none; +} +.fileupload-loading { + position: absolute; + left: 50%; + width: 128px; + height: 128px; + background: url(../img/loading.gif) center no-repeat; + display: none; +} +.fileupload-processing .fileupload-loading { + display: block; +} + +/* Fix for IE 6: */ +*html .fileinput-button { + line-height: 22px; + margin: 1px -3px 0 0; +} + +/* Fix for IE 7: */ +*+html .fileinput-button { + margin: 1px 0 0 0; +} + +@media (max-width: 480px) { + .files .btn span { + display: none; + } + .files .preview * { + width: 40px; + } + .files .name * { + width: 80px; + display: inline-block; + word-wrap: break-word; + } + .files .progress { + width: 20px; + } + .files .delete { + width: 60px; + } +} diff --git a/src/fileupload/static/fileupload/css/style.css b/src/fileupload/static/fileupload/css/style.css new file mode 100644 index 00000000..e45d81dc --- /dev/null +++ b/src/fileupload/static/fileupload/css/style.css @@ -0,0 +1,10 @@ +.preview img { + max-height: 50px; +} + +.delete button[data-type=""] { + display: none; +} +.delete button[data-type=""] + input { + display: none; +} diff --git a/src/fileupload/static/fileupload/img/glyphicons-halflings-white.png b/src/fileupload/static/fileupload/img/glyphicons-halflings-white.png new file mode 100644 index 00000000..3bf6484a Binary files /dev/null and b/src/fileupload/static/fileupload/img/glyphicons-halflings-white.png differ diff --git a/src/fileupload/static/fileupload/img/glyphicons-halflings.png b/src/fileupload/static/fileupload/img/glyphicons-halflings.png new file mode 100644 index 00000000..79bc568c Binary files /dev/null and b/src/fileupload/static/fileupload/img/glyphicons-halflings.png differ diff --git a/src/fileupload/static/fileupload/img/loading.gif b/src/fileupload/static/fileupload/img/loading.gif new file mode 100644 index 00000000..90f28cbd Binary files /dev/null and b/src/fileupload/static/fileupload/img/loading.gif differ diff --git a/src/fileupload/static/fileupload/img/progressbar.gif b/src/fileupload/static/fileupload/img/progressbar.gif new file mode 100644 index 00000000..fbcce6bc Binary files /dev/null and b/src/fileupload/static/fileupload/img/progressbar.gif differ diff --git a/src/fileupload/static/fileupload/js/bootstrap-image-gallery.min.js b/src/fileupload/static/fileupload/js/bootstrap-image-gallery.min.js new file mode 100644 index 00000000..a749f55c --- /dev/null +++ b/src/fileupload/static/fileupload/js/bootstrap-image-gallery.min.js @@ -0,0 +1 @@ +(function(a){"use strict",typeof define=="function"&&define.amd?define(["jquery","load-image","bootstrap"],a):a(window.jQuery,window.loadImage)})(function(a,b){"use strict",a.extend(a.fn.modal.defaults,{delegate:document,selector:null,filter:"*",index:0,href:null,preloadRange:2,offsetWidth:100,offsetHeight:200,canvas:!1,slideshow:0,imageClickDivision:.5});var c=a.fn.modal.Constructor.prototype.show,d=a.fn.modal.Constructor.prototype.hide;a.extend(a.fn.modal.Constructor.prototype,{initLinks:function(){var b=this,c=this.options,d=c.selector||"a[data-target="+c.target+"]";this.$links=a(c.delegate).find(d).filter(c.filter).each(function(a){b.getUrl(this)===c.href&&(c.index=a)}),this.$links[c.index]||(c.index=0)},getUrl:function(b){return b.href||a(b).data("href")},startSlideShow:function(){var a=this;this.options.slideshow&&(this._slideShow=window.setTimeout(function(){a.next()},this.options.slideshow))},stopSlideShow:function(){window.clearTimeout(this._slideShow)},toggleSlideShow:function(){var a=this.$element.find(".modal-slideshow");this.options.slideshow?(this.options.slideshow=0,this.stopSlideShow()):(this.options.slideshow=a.data("slideshow")||5e3,this.startSlideShow()),a.find("i").toggleClass("icon-play icon-pause")},preloadImages:function(){var b=this.options,c=b.index+b.preloadRange+1,d,e;for(e=b.index-b.preloadRange;e").prop("src",this.getUrl(d))},loadImage:function(){var a=this,c=this.$element,d=this.options.index,e=this.getUrl(this.$links[d]),f;this.abortLoad(),this.stopSlideShow(),c.trigger("beforeLoad"),this._loadingTimeout=window.setTimeout(function(){c.addClass("modal-loading")},100),f=c.find(".modal-image").children().removeClass("in"),window.setTimeout(function(){f.remove()},3e3),c.find(".modal-title").text(this.$links[d].title),c.find(".modal-download").prop("href",e),this._loadingImage=b(e,function(b){a.img=b,window.clearTimeout(a._loadingTimeout),c.removeClass("modal-loading"),c.trigger("load"),a.showImage(b),a.startSlideShow()},this._loadImageOptions),this.preloadImages()},showImage:function(b){var c=this.$element,d=a.support.transition&&c.hasClass("fade"),e=d?c.animate:c.css,f=c.find(".modal-image"),g,h;f.css({width:b.width,height:b.height}),c.find(".modal-title").css({width:Math.max(b.width,380)}),a(window).width()>480&&(d&&(g=c.clone().hide().appendTo(document.body)),e.call(c.stop(),{"margin-top":-((g||c).outerHeight()/2),"margin-left":-((g||c).outerWidth()/2)}),g&&g.remove()),f.append(b),h=b.offsetWidth,c.trigger("display"),d?c.is(":visible")?a(b).on(a.support.transition.end,function(d){d.target===b&&(a(b).off(a.support.transition.end),c.trigger("displayed"))}).addClass("in"):(a(b).addClass("in"),c.one("shown",function(){c.trigger("displayed")})):(a(b).addClass("in"),c.trigger("displayed"))},abortLoad:function(){this._loadingImage&&(this._loadingImage.onload=this._loadingImage.onerror=null),window.clearTimeout(this._loadingTimeout)},prev:function(){var a=this.options;a.index-=1,a.index<0&&(a.index=this.$links.length-1),this.loadImage()},next:function(){var a=this.options;a.index+=1,a.index>this.$links.length-1&&(a.index=0),this.loadImage()},keyHandler:function(a){switch(a.which){case 37:case 38:a.preventDefault(),this.prev();break;case 39:case 40:a.preventDefault(),this.next()}},wheelHandler:function(a){a.preventDefault(),a=a.originalEvent,this._wheelCounter=this._wheelCounter||0,this._wheelCounter+=a.wheelDelta||a.detail||0;if(a.wheelDelta&&this._wheelCounter>=120||!a.wheelDelta&&this._wheelCounter<0)this.prev(),this._wheelCounter=0;else if(a.wheelDelta&&this._wheelCounter<=-120||!a.wheelDelta&&this._wheelCounter>0)this.next(),this._wheelCounter=0},initGalleryEvents:function(){var b=this,c=this.$element;c.find(".modal-image").on("click.modal-gallery",function(c){var d=a(this);b.$links.length===1?b.hide():(c.pageX-d.offset().left)/d.width()480&&b.css({"margin-top":-(b.outerHeight()/2),"margin-left":-(b.outerWidth()/2)}),this.initGalleryEvents(),this.initLinks(),this.$links.length&&(b.find(".modal-slideshow, .modal-prev, .modal-next").toggle(this.$links.length!==1),b.toggleClass("modal-single",this.$links.length===1),this.loadImage())}c.apply(this,arguments)},hide:function(){this.isShown&&this.$element.hasClass("modal-gallery")&&(this.options.delegate=document,this.options.href=null,this.destroyGalleryEvents()),d.apply(this,arguments)}}),a(function(){a(document.body).on("click.modal-gallery.data-api",'[data-toggle="modal-gallery"]',function(b){var c=a(this),d=c.data(),e=a(d.target),f=e.data("modal"),g;f||(d=a.extend(e.data(),d)),d.selector||(d.selector="a[rel=gallery]"),g=a(b.target).closest(d.selector),g.length&&e.length&&(b.preventDefault(),d.href=g.prop("href")||g.data("href"),d.delegate=g[0]!==this?this:document,f&&a.extend(f.options,d),e.modal(d))})})}); diff --git a/src/fileupload/static/fileupload/js/bootstrap.min.js b/src/fileupload/static/fileupload/js/bootstrap.min.js new file mode 100644 index 00000000..fcfb38bc --- /dev/null +++ b/src/fileupload/static/fileupload/js/bootstrap.min.js @@ -0,0 +1,6 @@ +/** +* Bootstrap.js by @fat & @mdo +* Copyright 2012 Twitter, Inc. +* http://www.apache.org/licenses/LICENSE-2.0.txt +*/ +!function(a){a(function(){"use strict",a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.parent('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=c,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(b){return b||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},to:function(b){var c=this.$element.find(".active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(a){return a||(this.paused=!0),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this,j=a.Event("slide");this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();if(e.hasClass("active"))return;if(a.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(j);if(j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})}else{this.$element.trigger(j);if(j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=a.extend({},a.fn.carousel.defaults,typeof c=="object"&&c);e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):typeof c=="string"||(c=f.slide)?e[c]():f.interval&&e.cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b,c,d,e;if(this.transitioning)return;b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find("> .accordion-group > .in");if(d&&d.length){e=d.data("collapse");if(e&&e.transitioning)return;d.collapse("hide"),e||d.data("collapse",null)}this.$element[b](0),this.transition("addClass",a.Event("show"),"shown"),this.$element[b](this.$element[0][c])},hide:function(){var b;if(this.transitioning)return;b=this.dimension(),this.reset(this.$element[b]()),this.transition("removeClass",a.Event("hide"),"hidden"),this.$element[b](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c=="show"&&e.reset(),e.transitioning=0,e.$element.trigger(d)};this.$element.trigger(c);if(c.isDefaultPrevented())return;this.transitioning=1,this.$element[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();a(e).collapse(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}"use strict";var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e,f,g;if(c.is(".disabled, :disabled"))return;return f=c.attr("data-target"),f||(f=c.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,"")),e=a(f),e.length||(e=c.parent()),g=e.hasClass("open"),d(),g||e.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown",".dropdown form",function(a){a.stopPropagation()}).on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('').css({ + position: 'absolute', + height: h, + left: x, + top: y, + width: w + }).appendTo($box[0].offsetParent || $box.parent()).show(); + + + if ($origin.is('.motyw')) { + $('.akap-edit-button').remove(); + withThemes(function(canonThemes){ + $('textarea', $overlay).autocomplete(canonThemes, { + autoFill: true, + multiple: true, + selectFirst: true, + highlight: false + }); + }) + } + + if ($origin.is('.motyw')){ + $('.delete-button', $overlay).click(function(){ + if (window.confirm("Czy jesteś pewien, że chcesz usunąć ten motyw ?")) { + $('[theme-class=' + $origin.attr('theme-class') + ']').remove(); + $overlay.remove(); + $(document).unbind('click.blur-overlay'); + return false; + }; + }); + } + else if($box.is('*[x-annotation-box]')) { + $('.delete-button', $overlay).click(function(){ + if (window.confirm("Czy jesteś pewien, że chcesz usunąć ten przypis?")) { + $origin.remove(); + $overlay.remove(); + $(document).unbind('click.blur-overlay'); + return false; + }; + }); + } + else { + $('.delete-button', $overlay).html("Anuluj"); + $('.delete-button', $overlay).click(function(){ + if (window.confirm("Czy jesteś pewien, że chcesz anulować zmiany?")) { + $overlay.remove(); + $(document).unbind('click.blur-overlay'); + return false; + }; + }); + } + + + var serializer = new XMLSerializer(); + + html2text({ + element: $box[0], + stripOuter: true, + success: function(text){ + $('textarea', $overlay).val($.trim(text)); + + setTimeout(function(){ + $('textarea', $overlay).elastic().focus(); + }, 50); + + function save(argument){ + var nodeName = $box.attr('x-node') || 'pe'; + var insertedText = $('textarea', $overlay).val(); + + if ($origin.is('.motyw')) { + insertedText = insertedText.replace(/,\s*$/, ''); + } + + xml2html({ + xml: '<' + nodeName + '>' + insertedText + '', + success: function(element){ + if (nodeName == 'out-of-flow-text') { + $(element).children().insertAfter($origin); + $origin.remove() + } + else { + $origin.html($(element).html()); + } + $overlay.remove(); + }, + error: function(text){ + alert('Błąd! ' + text); + } + }) + + var msg = $("

    Pamiętaj, żeby zapisać swoje zmiany.

    "); + $("#base").prepend(msg); + $('#base .saveNotify').fadeOut(3000, function(){ + $(this).remove(); + }); + } + + $('.akap-edit-button', $overlay).click(function(){ + var textAreaOpened = $('textarea', $overlay)[0]; + var startTag = ""; + var endTag = ""; + var buttonName = this.innerHTML; + + if(buttonName == "słowo obce") { + startTag = ""; + endTag = ""; + } else if (buttonName == "wyróżnienie") { + startTag = ""; + endTag = ""; + } else if (buttonName == "tytuł dzieła") { + startTag = ""; + endTag = ""; + } else if(buttonName == "znak spec."){ + addSymbol(); + return false; + } + + var myField = textAreaOpened; + + //IE support + if (document.selection) { + textAreaOpened.focus(); + sel = document.selection.createRange(); + sel.text = startTag + sel.text + endTag; + } + //MOZILLA/NETSCAPE support + else if (textAreaOpened.selectionStart || textAreaOpened.selectionStart == '0') { + var startPos = textAreaOpened.selectionStart; + var endPos = textAreaOpened.selectionEnd; + textAreaOpened.value = textAreaOpened.value.substring(0, startPos) + + startTag + textAreaOpened.value.substring(startPos, endPos) + endTag + textAreaOpened.value.substring(endPos, textAreaOpened.value.length); + } + }); + + $('.accept-button', $overlay).click(function(){ + save(); + $(document).unbind('click.blur-overlay'); + }); + + $(document).bind('click.blur-overlay', function(event){ + if ($(event.target).closest('.html-editarea, #specialCharsContainer').length > 0) { + return; + } + save(); + $(document).unbind('click.blur-overlay'); + }); + + }, + error: function(text){ + alert('Błąd! ' + text); + } + }); + } + + function VisualPerspective(options){ + + var old_callback = options.callback; + + options.callback = function(){ + var element = $("#html-view"); + var button = $(''); + + if (!CurrentDocument.readonly) { + $('#html-view').bind('mousemove', function(event){ + var editable = $(event.target).closest('*[x-editable]'); + $('.active', element).not(editable).removeClass('active').children('.edit-button').remove(); + + if (!editable.hasClass('active')) { + editable.addClass('active').append(button); + } + if (editable.is('.annotation-inline-box')) { + $('*[x-annotation-box]', editable).css({ + position: 'absolute', + left: event.clientX - editable.offset().left + 5, + top: event.clientY - editable.offset().top + 5 + }).show(); + } + else { + $('*[x-annotation-box]').hide(); + } + }); + + $('#insert-annotation-button').click(function(){ + addAnnotation(); + return false; + }); + + $('#insert-theme-button').click(function(){ + addTheme(); + return false; + }); + + $('.edit-button').live('click', function(event){ + event.preventDefault(); + openForEdit($(this).parent()); + }); + + } + + $('.motyw').live('click', function(){ + selectTheme($(this).attr('theme-class')); + }); + + old_callback.call(this); + }; + + $.wiki.Perspective.call(this, options); + }; + + VisualPerspective.prototype = new $.wiki.Perspective(); + + VisualPerspective.prototype.freezeState = function(){ + + }; + + VisualPerspective.prototype.onEnter = function(success, failure){ + $.wiki.Perspective.prototype.onEnter.call(this); + + $.blockUI({ + message: 'Uaktualnianie widoku...' + }); + + function _finalize(callback){ + $.unblockUI(); + if (callback) + callback(); + } + + xml2html({ + xml: this.doc.text, + success: function(element){ + var htmlView = $('#html-view'); + htmlView.html(element); + htmlView.find('*[x-node]').dblclick(function(e) { + if($(e.target).is('textarea')) + return; + var selection = window.getSelection(); + selection.collapseToStart(); + selection.modify('extend', 'forward', 'word'); + e.stopPropagation(); + }); + _finalize(success); + }, + error: function(text, source){ + err = '

    Wystąpił błąd:

    '+text+'

    '; + if (source) + err += '
    '+source.replace(/&/g, '&').replace(/'
    +                $('#html-view').html(err);
    +                _finalize(failure);
    +            }
    +        });
    +    };
    +
    +    VisualPerspective.prototype.onExit = function(success, failure){
    +        var self = this;
    +
    +        $.blockUI({
    +            message: 'Zapisywanie widoku...'
    +        });
    +
    +        function _finalize(callback){
    +            $.unblockUI();
    +            if (callback)
    +                callback();
    +        }
    +
    +        if ($('#html-view .error').length > 0)
    +            return _finalize(failure);
    +
    +        html2text({
    +            element: $('#html-view').get(0),
    +            stripOuter: true,
    +            success: function(text){
    +                self.doc.setText(text);
    +                _finalize(success);
    +            },
    +            error: function(text){
    +                $('#source-editor').html('

    Wystąpił błąd:

    ' + text + '
    '); + _finalize(failure); + } + }); + }; + + $.wiki.VisualPerspective = VisualPerspective; + +})(jQuery); diff --git a/src/redakcja/static/js/wiki/view_gallery.js b/src/redakcja/static/js/wiki/view_gallery.js new file mode 100644 index 00000000..65a716af --- /dev/null +++ b/src/redakcja/static/js/wiki/view_gallery.js @@ -0,0 +1,259 @@ +(function($){ + + function normalizeNumber(pageNumber, pageCount){ + // Page number should be >= 1, <= pageCount; 0 if pageCount = 0 + var pageNumber = parseInt(pageNumber, 10); + + if (!pageCount) + return 0; + + if (!pageNumber || + isNaN(pageNumber) || + pageNumber == Infinity || + pageNumber == -Infinity || + pageNumber < 1) + return 1; + + if (pageNumber > pageCount) + return pageCount; + + return pageNumber; + } + + function bounds(galleryWidth, galleryHeight, imageWidth, imageHeight){ + return { + maxX: 0, + maxY: 0, + minX: galleryWidth - imageWidth, + minY: galleryHeight - imageHeight + } + }; + + function normalizePosition(x, y, galleryWidth, galleryHeight, imageWidth, imageHeight){ + var b = bounds(galleryWidth, galleryHeight, imageWidth, imageHeight); + return { + x: Math.min(b.maxX, Math.max(b.minX, x)), + y: Math.min(b.maxY, Math.max(b.minY, y)) + } + }; + + function fixImageSize(){ + + } + + /* + * Perspective + */ + function ScanGalleryPerspective(options){ + var old_callback = options.callback || function() { }; + + this.noupdate_hash_onenter = true; + this.vsplitbar = 'GALERIA'; + + options.callback = function(){ + var self = this; + + this.dimensions = {}; + this.zoomFactor = 1; + if (this.config().page == undefined) + this.config().page = CurrentDocument.galleryStart; + this.$element = $("#side-gallery"); + this.$numberInput = $('.page-number', this.$element); + + // ... + var origin = {}; + var imageOrigin = {}; + + this.$image = $('.gallery-image img', this.$element).attr('unselectable', 'on'); + + // button handlers + this.$numberInput.change(function(event){ + event.preventDefault(); + self.setPage($(this).val()); + }); + + $('.start-page', this.$element).click(function(){ + self.setPage(CurrentDocument.galleryStart); + }); + + $('.previous-page', this.$element).click(function(){ + self.setPage(parseInt(self.$numberInput.val(),10) - 1); + }); + + $('.next-page', this.$element).click(function(){ + self.setPage(parseInt(self.$numberInput.val(),10) + 1); + }); + + $('.zoom-in', this.$element).click(function(){ + self.alterZoom(0.2); + }); + + $('.zoom-out', this.$element).click(function(){ + self.alterZoom((-0.2)); + }); + + $(window).resize(function(){ + self.dimensions.galleryWidth = self.$image.parent().width(); + self.dimensions.galleryHeight = self.$image.parent().height(); + }); + + this.$image.load(function(){ + console.log("Image loaded.") + self._resizeImage(); + }).bind('mousedown', function() { + self.imageMoveStart.apply(self, arguments); + }); + + + + old_callback.call(this); + }; + + $.wiki.Perspective.call(this, options); + }; + + ScanGalleryPerspective.prototype = new $.wiki.Perspective(); + + ScanGalleryPerspective.prototype._resizeImage = function(){ + var $img = this.$image; + + $img.css({ + width: '', + height: '' + }); + + this.dimensions = { + width: $img.width() * this.zoomFactor, + height: $img.height() * this.zoomFactor, + originWidth: $img.width(), + originHeight: $img.height(), + galleryWidth: $img.parent().width(), + galleryHeight: $img.parent().height() + }; + + if (!(this.dimensions.width && this.dimensions.height)) { + setTimeout(function(){ + $img.load(); + }, 100); + } + + var position = normalizePosition($img.position().left, $img.position().top, this.dimensions.galleryWidth, this.dimensions.galleryHeight, this.dimensions.width, this.dimensions.height); + + $img.css({ + left: position.x, + top: position.y, + width: $img.width() * this.zoomFactor, + height: $img.height() * this.zoomFactor + }); + }; + + ScanGalleryPerspective.prototype.setPage = function(newPage){ + newPage = normalizeNumber(newPage, this.doc.galleryImages.length); + this.$numberInput.val(newPage); + this.config().page = newPage; + $('.gallery-image img', this.$element).attr('src', this.doc.galleryImages[newPage - 1]); + }; + + ScanGalleryPerspective.prototype.alterZoom = function(delta){ + var zoomFactor = this.zoomFactor + delta; + if (zoomFactor < 0.2) + zoomFactor = 0.2; + if (zoomFactor > 2) + zoomFactor = 2; + this.setZoom(zoomFactor); + }; + + ScanGalleryPerspective.prototype.setZoom = function(factor){ + this.zoomFactor = factor; + + this.dimensions.width = this.dimensions.originWidth * this.zoomFactor; + this.dimensions.height = this.dimensions.originHeight * this.zoomFactor; + + // var position = normalizePosition(this.$image.position().left, this.$image.position().top, this.dimensions.galleryWidth, this.dimensions.galleryHeight, this.dimensions.width, this.dimensions.height); + + this._resizeImage(); + }; + + /* + * Movement + */ + ScanGalleryPerspective.prototype.imageMoved = function(event){ + event.preventDefault(); + + // origin is where the drag started + // imageOrigin is where the drag started on the image + + var newX = event.clientX - this.origin.x + this.imageOrigin.left; + var newY = event.clientY - this.origin.y + this.imageOrigin.top; + + var position = normalizePosition(newX, newY, this.dimensions.galleryWidth, this.dimensions.galleryHeight, this.dimensions.width, this.dimensions.height); + + this.$image.css({ + left: position.x, + top: position.y, + }); + + return false; + }; + + ScanGalleryPerspective.prototype.imageMoveStart = function(event){ + event.preventDefault(); + + var self = this; + + this.origin = { + x: event.clientX, + y: event.clientY + }; + + this.imageOrigin = self.$image.position(); + $(document).bind('mousemove.gallery', function(){ + self.imageMoved.apply(self, arguments); + }).bind('mouseup.gallery', function() { + self.imageMoveStop.apply(self, arguments); + }); + + return false; + }; + + ScanGalleryPerspective.prototype.imageMoveStop = function(event){ + $(document).unbind('mousemove.gallery').unbind('mouseup.gallery'); + }; + + /* + * Loading gallery + */ + ScanGalleryPerspective.prototype.onEnter = function(success, failure){ + var self = this; + + $.wiki.Perspective.prototype.onEnter.call(this); + + $('.vsplitbar').not('.active').trigger('click'); + $(".vsplitbar-title").html("↓ GALERIA ↓"); + + this.doc.refreshGallery({ + success: function(doc, data){ + self.$image.show(); + console.log("gconfig:", self.config().page ); + self.setPage( self.config().page ); + $('#imagesCount').html("/" + doc.galleryImages.length); + + $('.error_message', self.$element).hide(); + if(success) success(); + }, + failure: function(doc, message){ + self.$image.hide(); + $('.error_message', self.$element).show().html(message); + if(failure) failure(); + } + }); + + }; + + ScanGalleryPerspective.prototype.onExit = function(success, failure) { + + }; + + $.wiki.ScanGalleryPerspective = ScanGalleryPerspective; + +})(jQuery); diff --git a/src/redakcja/static/js/wiki/view_history.js b/src/redakcja/static/js/wiki/view_history.js new file mode 100644 index 00000000..85adca0a --- /dev/null +++ b/src/redakcja/static/js/wiki/view_history.js @@ -0,0 +1,204 @@ +(function($){ + + function HistoryPerspective(options) { + var old_callback = options.callback || function() {}; + + 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() { + self.makeDiff(); + }); + + $('#pubmark-changeset-button').click(function() { + self.showPubmarkForm(); + }); + + $('#doc-revert-button').click(function() { + self.revertDialog(); + }); + + $('#open-preview-button').click(function(event) { + var selected = $('#changes-list .entry.selected'); + + if (selected.length != 1) { + window.alert("Wybierz dokładnie *jedną* wersję."); + return; + } + + var version = parseInt($("*[data-stub-value='version']", selected[0]).text()); + window.open($(this).attr('data-basehref') + "?revision=" + version); + + event.preventDefault(); + }); + + $('#changes-list .entry').live('click', function(){ + var $this = $(this); + + var selected_count = $("#changes-list .entry.selected").length; + + if ($this.hasClass('selected')) { + $this.removeClass('selected'); + selected_count -= 1; + } + else { + if (selected_count < 2) { + $this.addClass('selected'); + selected_count += 1; + }; + }; + + $('#history-view-editor .toolbar button').attr('disabled', 'disabled'). + filter('*[data-enabled-when~=' + selected_count + '], *[data-enabled-when~=*]'). + attr('disabled', null); + }); + + $('#changes-list span.tag').live('click', function(event){ + return false; + }); + + old_callback.call(this); + } + + $.wiki.Perspective.call(this, options); + }; + + HistoryPerspective.prototype = new $.wiki.Perspective(); + + HistoryPerspective.prototype.freezeState = function(){ + // must + }; + + HistoryPerspective.prototype.onEnter = function(success, failure){ + $.wiki.Perspective.prototype.onEnter.call(this); + + $.blockUI({ + message: 'Odświeżanie historii...' + }); + + function _finalize(s){ + $.unblockUI(); + + if (s) { + if (success) + success(); + } + else { + if (failure) + failure(); + } + } + + function _failure(doc, message){ + $('#history-view .message-box').html('Nie udało się odświeżyć historii:' + message).show(); + _finalize(false); + }; + + function _success(doc, data){ + $('#history-view .message-box').hide(); + var changes_list = $('#changes-list'); + var $stub = $('#history-view .row-stub'); + changes_list.html(''); + + var tags = $('select#id_addtag-tag option'); + + $.each(data, function(){ + $.wiki.renderStub({ + container: changes_list, + stub: $stub, + data: this, + filters: { +// tag: function(value) { +// return tags.filter("*[value='"+value+"']").text(); +// } +// description: function(value) { +// return value.replace('\n', '); +// } + } + }); + }); + + _finalize(true); + }; + + return this.doc.fetchHistory({ + success: _success, + failure: _failure + }); + }; + + 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); + + if (selected.length != 2) { + window.alert("Musisz zaznaczyć dokładnie dwie wersje do porównania."); + return; + } + + $.blockUI({ + message: 'Wczytywanie porównania...' + }); + + var rev_from = $("*[data-stub-value='version']", selected[1]).text(); + var rev_to = $("*[data-stub-value='version']", selected[0]).text(); + + return 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); + $.unblockUI(); + }, + failure: function(doc){ + $.unblockUI(); + } + }); + }; + + HistoryPerspective.prototype.revertDialog = function(){ + var self = this; + 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('#revert_dialog', {revision: version}); + }; + + $.wiki.HistoryPerspective = HistoryPerspective; + +})(jQuery); diff --git a/src/redakcja/static/js/wiki/view_search.js b/src/redakcja/static/js/wiki/view_search.js new file mode 100644 index 00000000..b49671c6 --- /dev/null +++ b/src/redakcja/static/js/wiki/view_search.js @@ -0,0 +1,117 @@ +(function($){ + + /* + * Perspective + */ + function SearchPerspective(options){ + var old_callback = options.callback || function() { }; + + this.noupdate_hash_onenter = true; + this.vsplitbar = 'ZNAJDŹ I ZAMIEŃ'; + + options.callback = function(){ + var self = this; + + this.editor = null; + this.$element = $("#side-search"); + this.$searchInput = $('#search-input', this.$element); + this.$replaceInput = $('#replace-input', this.$element); + this.$searchButton = $('#search-button', this.$element); + this.$replaceButton = $('#replace-button', this.$element); + + this.$replaceButton.attr("disabled","disabled"); + this.options = Array(); + + // handlers + this.$searchInput.change(function(event){ + self.searchCursor = null; + }); + this.$replaceInput.change(function(event){ + self.searchCursor = null; + }); + + $("#side-search input:checkbox").each(function() { + self.options[this.id] = this.checked; + }).change(function(){ + self.options[this.id] = this.checked; + self.searchCursor = null; + }); + + this.$searchButton.click(function(){ + if (!self.search()) + alert('Brak wyników.'); + }); + + this.$replaceButton.click(function(){ + self.replace(); + }); + + old_callback.call(this); + }; + + $.wiki.Perspective.call(this, options); + }; + + SearchPerspective.prototype = new $.wiki.Perspective(); + + SearchPerspective.prototype.search = function(){ + var self = this; + var query = self.$searchInput.val(); + + if (!self.editor) + self.editor = $.wiki.perspectiveForTab('#CodeMirrorPerspective').codemirror + + if (!self.searchCursor) { + self.searchCursor = self.editor.getSearchCursor( + self.$searchInput.val(), + self.options['search-from-cursor'], + !self.options['search-case-sensitive'] + ); + } + if (self.searchCursor.findNext()) { + self.searchCursor.select(); + self.$replaceButton.removeAttr("disabled"); + return true; + } + else { + self.searchCursor = null; + this.$replaceButton.attr("disabled","disabled"); + return false; + } + }; + + SearchPerspective.prototype.replace = function(){ + var self = this; + var query = self.$replaceInput.val(); + + if (!self.searchCursor) { + self.search(); + } + else {} + self.searchCursor.select(); + self.searchCursor.replace(query); + if(self.search() && self.options['replace-all']) { + self.replace(); + } + }; + + SearchPerspective.prototype.onEnter = function(success, failure){ + var self = this; + + $.wiki.Perspective.prototype.onEnter.call(this); + self.$searchCursor = null; + + $('.vsplitbar').not('.active').trigger('click'); + $(".vsplitbar-title").html("↓ ZNAJDŹ I ZAMIEŃ ↓"); + + if ($.wiki.activePerspective() != 'CodeMirrorPerspective') + $.wiki.switchToTab('#CodeMirrorPerspective'); + }; + + SearchPerspective.prototype.onExit = function(success, failure) { + + }; + + $.wiki.SearchPerspective = SearchPerspective; + +})(jQuery); diff --git a/src/redakcja/static/js/wiki/view_summary.js b/src/redakcja/static/js/wiki/view_summary.js new file mode 100644 index 00000000..099a0e81 --- /dev/null +++ b/src/redakcja/static/js/wiki/view_summary.js @@ -0,0 +1,61 @@ +(function($){ + + function SummaryPerspective(options) { + var old_callback = options.callback || function() {}; + + options.callback = function() { + var self = this; + + // first time page is rendered + $('#summary-cover-refresh').click(function() { + self.refreshCover(); + }); + + old_callback.call(this); + } + + $.wiki.Perspective.call(this, options); + }; + + SummaryPerspective.prototype = new $.wiki.Perspective(); + + SummaryPerspective.prototype.refreshCover = function() { + $('#summary-cover-refresh').attr('disabled', 'disabled'); + this.doc.refreshCover({ + success: function(text) { + $('#summary-cover').attr('src', text); + $('#summary-cover-refresh').removeAttr('disabled'); + } + }); + }; + + SummaryPerspective.prototype.showCharCount = function() { + var cc; + try { + cc = this.doc.getLength(); + $('#charcount_untagged').hide(); + } + catch (e) { + $('#charcount_untagged').show(); + cc = this.doc.text.replace(/\s{2,}/g, ' ').length; + } + $('#charcount').html(cc); + $('#charcount_pages').html((Math.round(cc/18)/100).toLocaleString()); + } + + SummaryPerspective.prototype.freezeState = function(){ + // must + }; + + SummaryPerspective.prototype.onEnter = function(success, failure){ + $.wiki.Perspective.prototype.onEnter.call(this); + + this.showCharCount(); + + console.log("Entered summery view"); + }; + + $.wiki.SummaryPerspective = SummaryPerspective; + +})(jQuery); + diff --git a/src/redakcja/static/js/wiki/wikiapi.js b/src/redakcja/static/js/wiki/wikiapi.js new file mode 100644 index 00000000..8df3ef5a --- /dev/null +++ b/src/redakcja/static/js/wiki/wikiapi.js @@ -0,0 +1,434 @@ +(function($) { + $.wikiapi = {}; + var noop = function() { + }; + var noops = { + success: noop, + failure: noop + }; + /* + * Return absolute reverse path of given named view. (at least he have it + * hard-coded in one place) + * + * TODO: think of a way, not to hard-code it here ;) + * + */ + function reverse() { + var vname = arguments[0]; + var base_path = "/editor"; + + if (vname == "ajax_document_text") { + var path = "/text/" + arguments[1] + '/'; + + if (arguments[2] !== undefined) + path += arguments[2] + '/'; + + return base_path + path; + } + + if (vname == "ajax_document_revert") { + return base_path + "/revert/" + arguments[1] + '/'; + } + + + if (vname == "ajax_document_history") { + + return base_path + "/history/" + arguments[1] + '/'; + } + + if (vname == "ajax_document_gallery") { + + return base_path + "/gallery/" + arguments[1] + '/'; + } + + if (vname == "ajax_document_diff") + return base_path + "/diff/" + arguments[1] + '/'; + + if (vname == "ajax_document_rev") + return base_path + "/rev/" + arguments[1] + '/'; + + if (vname == "ajax_document_pubmark") + return base_path + "/pubmark/" + arguments[1] + '/'; + + if (vname == "ajax_cover_preview") + return "/cover/preview/"; + + console.log("Couldn't reverse match:", vname); + return "/404.html"; + }; + + /* + * Document Abstraction + */ + function WikiDocument(element_id) { + var meta = $('#' + element_id); + this.id = meta.attr('data-chunk-id'); + + this.revision = $("*[data-key='revision']", meta).text(); + this.readonly = !!$("*[data-key='readonly']", meta).text(); + + this.galleryLink = $("*[data-key='gallery']", meta).text(); + this.galleryStart = parseInt($("*[data-key='gallery-start']", meta).text()); + + var diff = $("*[data-key='diff']", meta).text(); + if (diff) { + 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; + this._lock = -1; + this._context_lock = -1; + this._lock_count = 0; + }; + + WikiDocument.prototype.triggerDocumentChanged = function() { + $(document).trigger('wlapi_document_changed', this); + }; + /* + * Fetch text of this document. + */ + WikiDocument.prototype.fetch = function(params) { + params = $.extend({}, noops, params); + var self = this; + $.ajax({ + method: "GET", + url: reverse("ajax_document_text", self.id), + data: {"revision": self.revision}, + dataType: 'json', + success: function(data) { + var changed = false; + + if (self.text === null || self.revision !== data.revision) { + self.text = data.text; + self.revision = data.revision; + self.gallery = data.gallery; + changed = true; + self.triggerDocumentChanged(); + }; + + self.has_local_changes = false; + params['success'](self, changed); + }, + error: function() { + params['failure'](self, "Nie udało się wczytać treści dokumentu."); + } + }); + }; + /* + * Fetch history of this document. + * + * from - First revision to fetch (default = 0) upto - Last revision to + * fetch (default = tip) + * + */ + WikiDocument.prototype.fetchHistory = function(params) { + /* this doesn't modify anything, so no locks */ + params = $.extend({}, noops, params); + var self = this; + $.ajax({ + method: "GET", + url: reverse("ajax_document_history", self.id), + dataType: 'json', + data: { + "from": params['from'], + "upto": params['upto'] + }, + success: function(data) { + params['success'](self, data); + }, + error: function() { + params['failure'](self, "Nie udało się wczytać historii dokumentu."); + } + }); + }; + WikiDocument.prototype.fetchDiff = function(params) { + /* this doesn't modify anything, so no locks */ + var self = this; + params = $.extend({ + 'from': self.revision, + 'to': self.revision + }, noops, params); + $.ajax({ + method: "GET", + url: reverse("ajax_document_diff", self.id), + dataType: 'html', + data: { + "from": params['from'], + "to": params['to'] + }, + success: function(data) { + params['success'](self, data); + }, + error: function() { + params['failure'](self, "Nie udało się wczytać porównania wersji."); + } + }); + }; + + WikiDocument.prototype.checkRevision = function(params) { + /* this doesn't modify anything, so no locks */ + var self = this; + $.ajax({ + method: "GET", + url: reverse("ajax_document_rev", self.id), + dataType: 'text', + success: function(data) { + if (data == '') { + if (params.error) + params.error(); + } + else if (data != self.revision) + params.outdated(); + } + }); + }; + + /* + * Fetch gallery + */ + WikiDocument.prototype.refreshGallery = function(params) { + params = $.extend({}, noops, params); + var self = this; + $.ajax({ + method: "GET", + url: reverse("ajax_document_gallery", self.galleryLink), + dataType: 'json', + // data: {}, + success: function(data) { + self.galleryImages = data; + params['success'](self, data); + }, + error: function(xhr) { + switch (xhr.status) { + case 403: + var msg = 'Galerie dostępne tylko dla zalogowanych użytkowników.'; + break; + case 404: + var msg = "Nie znaleziono galerii o nazwie: '" + self.galleryLink + "'."; + default: + var msg = "Nie udało się wczytać galerii o nazwie: '" + self.galleryLink + "'."; + } + self.galleryImages = []; + params['failure'](self, "

    " + msg + "

    "); + } + }); + }; + + /* + * Set document's text + */ + WikiDocument.prototype.setText = function(text) { + return this.setDocumentProperty('text', text); + }; + + /* + * Set document's gallery link + */ + WikiDocument.prototype.setGalleryLink = function(gallery) { + return this.setDocumentProperty('galleryLink', gallery); + }; + + /* + * Set document's property + */ + WikiDocument.prototype.setDocumentProperty = function(property, value) { + if(this[property] !== value) { + this[property] = value; + this.has_local_changes = true; + } + }; + + /* + * Save text back to the server + */ + WikiDocument.prototype.save = function(params) { + params = $.extend({}, noops, params); + var self = this; + + if (!self.has_local_changes) { + console.log("Abort: no changes."); + return params['success'](self, false, "Nie ma zmian do zapisania."); + }; + + // Serialize form to dictionary + var data = {}; + $.each(params['form'].serializeArray(), function() { + data[this.name] = this.value; + }); + + data['textsave-text'] = self.text; + + $.ajax({ + url: reverse("ajax_document_text", self.id), + type: "POST", + dataType: "json", + data: data, + success: function(data) { + var changed = false; + + $('#header').removeClass('saving'); + + if (data.text) { + self.text = data.text; + self.revision = data.revision; + self.gallery = data.gallery; + changed = true; + self.triggerDocumentChanged(); + }; + + params['success'](self, changed, ((changed && "Udało się zapisać :)") || "Twoja wersja i serwera jest identyczna")); + }, + error: function(xhr) { + if ($('#header').hasClass('saving')) { + $('#header').removeClass('saving'); + $.blockUI({ + message: "

    Nie udało się zapisać zmian.

    " + }) + } + else { + try { + params['failure'](self, $.parseJSON(xhr.responseText)); + } + catch (e) { + params['failure'](self, { + "__message": "

    Nie udało się zapisać - błąd serwera.

    " + }); + }; + } + + } + }); + + $('#save-hide').click(function(){ + $('#header').addClass('saving'); + $.unblockUI(); + $.wiki.blocking.unblock(); + }); + }; /* end of save() */ + + WikiDocument.prototype.revertToVersion = function(params) { + var self = this; + params = $.extend({}, noops, params); + + if (params.revision >= this.revision) { + params.failure(self, 'Proszę wybrać rewizję starszą niż aktualna.'); + return; + } + + // Serialize form to dictionary + var data = {}; + $.each(params['form'].serializeArray(), function() { + data[this.name] = this.value; + }); + + $.ajax({ + url: reverse("ajax_document_revert", self.id), + type: "POST", + dataType: "json", + data: data, + success: function(data) { + if (data.text) { + self.text = data.text; + self.revision = data.revision; + self.gallery = data.gallery; + self.triggerDocumentChanged(); + + params.success(self, "Udało się przywrócić wersję :)"); + } + else { + params.failure(self, "Przywracana wersja identyczna z aktualną. Anulowano przywracanie."); + } + }, + error: function(xhr) { + params.failure(self, "Nie udało się przywrócić wersji - błąd serwera."); + } + }); + }; + + 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."] + }); + }; + }; + } + }); + }; + + WikiDocument.prototype.refreshCover = function(params) { + var self = this; + var data = { + xml: self.text // TODO: send just DC + }; + $.ajax({ + url: reverse("ajax_cover_preview"), + type: "POST", + data: data, + success: function(data) { + params.success(data); + }, + error: function(xhr) { + // params.failure("Nie udało się odświeżyć okładki - błąd serwera."); + } + }); + }; + + + WikiDocument.prototype.getLength = function(params) { + var xml = this.text.replace(/\/(\s+)/g, '
    $1'); + var parser = new DOMParser(); + var doc = parser.parseFromString(xml, 'text/xml'); + var error = $('parsererror', doc); + + if (error.length > 0) { + throw "Not an XML document."; + } + $.xmlns["rdf"] = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + $('rdf|RDF, motyw, pa, pe, pr, pt', doc).remove(); + var text = $(doc).text(); + text = $.trim(text.replace(/\s{2,}/g, ' ')); + return text.length; + } + + + $.wikiapi.WikiDocument = WikiDocument; +})(jQuery); diff --git a/src/redakcja/static/js/wiki/xslt.js b/src/redakcja/static/js/wiki/xslt.js new file mode 100644 index 00000000..ab90e0c1 --- /dev/null +++ b/src/redakcja/static/js/wiki/xslt.js @@ -0,0 +1,393 @@ +/* + * + * XSLT STUFF + * + */ +function createXSLT(xsl) { + var p = new XSLTProcessor(); + p.importStylesheet(xsl); + return p; +} + +var xml2htmlStylesheet = null; + +// Wykonuje block z załadowanymi arkuszami stylów +function withStylesheets(code_block, onError) +{ + if (!xml2htmlStylesheet) { + $.blockUI({message: 'Ładowanie arkuszy stylów...'}); + $.ajax({ + url: STATIC_URL + 'xsl/wl2html_client.xsl?20171106', + dataType: 'xml', + timeout: 10000, + success: function(data) { + xml2htmlStylesheet = createXSLT(data); + $.unblockUI(); + code_block(); + + }, + error: onError + }) + } + else { + code_block(); + } +} + + +// Wykonuje block z załadowanymi kanonicznymi motywami +function withThemes(code_block, onError) +{ + if (typeof withThemes.canon == 'undefined') { + $.ajax({ + url: '/editor/themes', + dataType: 'text', + success: function(data) { + withThemes.canon = data.split('\n'); + code_block(withThemes.canon); + }, + error: function() { + withThemes.canon = null; + code_block(withThemes.canon); + } + }) + } + else { + code_block(withThemes.canon); + } +} + + +function xml2html(options) { + withStylesheets(function() { + var xml = options.xml.replace(/\/(\s+)/g, '
    $1'); + xml = xml.replace(/([^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ\s<>«»\\*_!,:;?&%."'=#()\/-]+)/g, '$1'); + var parser = new DOMParser(); + var serializer = new XMLSerializer(); + var doc = parser.parseFromString(xml, 'text/xml'); + var error = $('parsererror', doc); + + if (error.length == 0) { + doc = xml2htmlStylesheet.transformToFragment(doc, document); + console.log(doc.firstChild); + + if(doc.firstChild === null) { + options.error("Błąd w przetwarzaniu XML."); + return; + } + + error = $('parsererror', doc); + } + + if (error.length > 0 && options.error) { + source = $('sourcetext', doc); + source_text = source.text(); + source.text(''); + options.error(error.text(), source_text); + } else { + options.success(doc.childNodes); + + withThemes(function(canonThemes) { + if (canonThemes != null) { + $('.theme-text-list').addClass('canon').each(function(){ + var themes = $(this).html().split(','); + for (i in themes) { + themes[i] = $.trim(themes[i]); + if (canonThemes.indexOf(themes[i]) == -1) + themes[i] = '' + themes[i] + "" + } + $(this).html(themes.join(', ')); + }); + } + }); + } + }, function() { options.error && options.error('Nie udało się załadować XSLT'); }); +} + +/* USEFULL CONSTANTS */ +const ELEMENT_NODE = 1; +const ATTRIBUTE_NODE = 2; +const TEXT_NODE = 3; +const CDATA_SECTION_NODE = 4; +const ENTITY_REFERENCE_NODE = 5; +const ENTITY_NODE = 6; +const PROCESSING_INSTRUCTION_NODE = 7; +const COMMENT_NODE = 8; +const DOCUMENT_NODE = 9; +const DOCUMENT_TYPE_NODE = 10; +const DOCUMENT_FRAGMENT_NODE = 11; +const NOTATION_NODE = 12; +const XATTR_RE = /^x-attr-name-(.*)$/; + +const ELEM_START = 1; +const ELEM_END = 2; +const NS_END = 3; + +const NAMESPACES = { + // namespaces not listed here will be assigned random names + "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf", + "http://purl.org/dc/elements/1.1/": "dc", + "http://www.w3.org/XML/1998/namespace": "xml" +}; + +function HTMLSerializer() { + // empty constructor +} + + + +HTMLSerializer.prototype._prepare = function() { + this.stack = []; + + // XML namespace is implicit + this.nsMap = {"http://www.w3.org/XML/1998/namespace": "xml"}; + + this.result = ""; + this.nsCounter = 1; +} + +HTMLSerializer.prototype._pushElement = function(element) { + this.stack.push({ + "type": ELEM_START, + "node": element + }); +} + +HTMLSerializer.prototype._pushChildren = function(element) { + for(var i = element.childNodes.length-1; i >= 0; i--) + this._pushElement(element.childNodes.item(i)); +} + +HTMLSerializer.prototype._pushTagEnd = function(tagName) { + this.stack.push({ + "type": ELEM_END, + "tagName": tagName + }); +} + +HTMLSerializer.prototype._verseBefore = function(node) { + /* true if previous element is a previous verse of a stanza */ + var parent = node.parentNode; + if (!parent || !parent.hasAttribute('x-node') || parent.getAttribute('x-node') != 'strofa') + return false; + + var prev = node.previousSibling; + + while((prev !== null) && (prev.nodeType != ELEMENT_NODE)) { + prev = prev.previousSibling; + } + + return (prev !== null) && prev.hasAttribute('x-verse'); +} + +HTMLSerializer.prototype._nodeIgnored = function(node) { + return node.getAttribute('x-node') == 'wers'; +} + +HTMLSerializer.prototype._ignoredWithWhitespace = function(node) { + while (node.nodeType == ELEMENT_NODE && this._nodeIgnored(node) && node.childNodes.length > 0) + node = node.childNodes[0]; + if (node.nodeType == TEXT_NODE) + return node.nodeValue.match(/^\s/) + else return false; +} + + +HTMLSerializer.prototype.serialize = function(rootElement, stripOuter) +{ + var self = this; + self._prepare(); + + if(!stripOuter) + self._pushElement(rootElement); + else + self._pushChildren(rootElement); + + var text_buffer = ''; + + while(self.stack.length > 0) { + var token = self.stack.pop(); + + if(token.type === ELEM_END) { + self.result += text_buffer; + text_buffer = ''; + if (token.tagName != '') + self.result += ""; + continue; + }; + + if(token.type === NS_END) { + self._unassignNamespace(token.namespace); + continue; + } + + + switch(token.node.nodeType) { + case ELEMENT_NODE: + if(token.node.hasAttribute('x-pass-thru') + || token.node.hasAttribute('data-pass-thru')) { + self._pushChildren(token.node); + break; + } + + if(!token.node.hasAttribute('x-node')) + break; + + var xnode = token.node.getAttribute('x-node'); + + if(xnode === 'out-of-flow-text') { + self._pushChildren(token.node); + break; + } + + if(token.node.hasAttribute('x-verse') && self._verseBefore(token.node)) { + self.result += '/'; + // add whitespace if there's none + if (!(text_buffer.match(/^\s/) || self._ignoredWithWhitespace(token.node))) + self.result += ' '; + } + + self.result += text_buffer; + text_buffer = ''; + self._serializeElement(token.node); + break; + case TEXT_NODE: + self.result += text_buffer; + text_buffer = token.node.nodeValue.replace(/&/g, '&').replace(/'; + break; + }; + }; + self.result += text_buffer; + + return this.result; +} + +/* + * TODO: this doesn't support prefix redefinitions + */ +HTMLSerializer.prototype._unassignNamespace = function(nsData) { + this.nsMap[nsData.uri] = undefined; +}; + +HTMLSerializer.prototype._assignNamespace = function(uri) { + if(uri === null) { + // default namespace + return ({"prefix": "", "uri": "", "fresh": false}); + } + + if(this.nsMap[uri] === undefined) { + // this prefix hasn't been defined yet in current context + var prefix = NAMESPACES[uri]; + + if (prefix === undefined) { // not predefined + prefix = "ns" + this.nsCounter; + this.nsCounter += 1; + } + + this.nsMap[uri] = prefix; + return ({ + "prefix": prefix, + "uri": uri, + "fresh": true + }); + } + + return ({"prefix": this.nsMap[uri], "uri": uri, "fresh": false}); +}; + +HTMLSerializer.prototype._join = function(prefix, name) { + if(!!prefix) + return prefix + ":" + name; + return name; +}; + +HTMLSerializer.prototype._rjoin = function(prefix, name) { + if(!!name) + return prefix + ":" + name; + return prefix; +}; + +HTMLSerializer.prototype._serializeElement = function(node) { + var self = this; + + if (self._nodeIgnored(node)) { + self._pushTagEnd(''); + self._pushChildren(node); + } + else { + var ns = node.getAttribute('x-ns'); + var nsPrefix = null; + var newNamespaces = []; + + var nsData = self._assignNamespace(node.getAttribute('x-ns')); + + if(nsData.fresh) { + newNamespaces.push(nsData); + self.stack.push({ + "type": NS_END, + "namespace": nsData + }); + } + + var tagName = self._join(nsData.prefix, node.getAttribute('x-node')); + + /* retrieve attributes */ + var attributeIDs = []; + for (var i = 0; i < node.attributes.length; i++) { + var attr = node.attributes.item(i); + + // check if name starts with "x-attr-name" + var m = attr.name.match(XATTR_RE); + if (m !== null) + attributeIDs.push(m[1]); + }; + + /* print out */ + + self.result += '<' + tagName; + + $.each(attributeIDs, function() { + var nsData = self._assignNamespace(node.getAttribute('x-attr-ns-'+this)); + + if(nsData.fresh) { + newNamespaces.push(nsData); + self.stack.push({ + "type": NS_END, + "namespace": nsData + }); + }; + + self.result += ' ' + self._join(nsData.prefix, node.getAttribute('x-attr-name-'+this)); + self.result += '="'+node.getAttribute('x-attr-value-'+this) +'"'; + }); + + /* print new namespace declarations */ + $.each(newNamespaces, function() { + self.result += " " + self._rjoin("xmlns", this.prefix); + self.result += '="' + this.uri + '"'; + }); + + if (node.childNodes.length > 0) { + self.result += ">"; + self._pushTagEnd(tagName); + self._pushChildren(node); + } + else { + self.result += "/>"; + }; + } +}; + +function html2text(params) { + try { + var s = new HTMLSerializer(); + params.success( s.serialize(params.element, params.stripOuter) ); + } catch(e) { + params.error("Nie udało się zserializować tekstu:" + e) + } +} diff --git a/src/redakcja/static/js/wiki_img/base.js b/src/redakcja/static/js/wiki_img/base.js new file mode 100644 index 00000000..ffe5a01d --- /dev/null +++ b/src/redakcja/static/js/wiki_img/base.js @@ -0,0 +1,329 @@ +(function($) +{ + var noop = function() { }; + + $.wiki = { + perspectives: {}, + cls: {}, + state: { + "version": 1, + "perspectives": { + "CodeMirrorPerspective": {} + } + } + }; + + $.wiki.loadConfig = function() { + if(!window.localStorage) + return; + + try { + var value = window.localStorage.getItem(CurrentDocument.id) || "{}"; + var config = JSON.parse(value); + + if (config.version == $.wiki.state.version) { + $.wiki.state.perspectives = $.extend($.wiki.state.perspectives, config.perspectives); + } + } catch(e) { + console.log("Failed to load config, using default."); + } + + console.log("Loaded:", $.wiki.state, $.wiki.state.version); + }; + + $(window).bind('unload', function() { + if(window.localStorage) + window.localStorage.setItem(CurrentDocument.id, JSON.stringify($.wiki.state)); + }) + + + $.wiki.activePerspective = function() { + return this.perspectives[$("#tabs li.active").attr('id')]; + }; + + $.wiki.exitContext = function() { + var ap = this.activePerspective(); + if(ap) ap.onExit(); + return ap; + }; + + $.wiki.enterContext = function(ap) { + if(ap) ap.onEnter(); + }; + + $.wiki.isDirty = function() { + var ap = this.activePerspective(); + return (!!CurrentDocument && CurrentDocument.has_local_changes) || ap.dirty(); + }; + + $.wiki.newTab = function(doc, title, klass) { + var base_id = 'id' + Math.floor(Math.random()* 5000000000); + var id = (''+klass)+'_' + base_id; + var $tab = $('
  • ' + + title + '
  • '); + var $view = $('
    '); + + this.perspectives[id] = new $.wiki[klass]({ + doc: doc, + id: id, + base_id: base_id, + }); + + $('#tabs').append($tab); + $view.hide().appendTo('#editor'); + return { + tab: $tab[0], + view: $view[0], + }; + }; + + $.wiki.initTab = function(options) { + var klass = $(options.tab).attr('data-ui-jsclass'); + + return new $.wiki[klass]({ + doc: options.doc, + id: $(options.tab).attr('id'), + callback: function() { + $.wiki.perspectives[this.perspective_id] = this; + if(options.callback) + options.callback.call(this); + } + }); + }; + + $.wiki.perspectiveForTab = function(tab) { // element or id + return this.perspectives[ $(tab).attr('id')]; + } + + $.wiki.switchToTab = function(tab){ + var self = this; + var $tab = $(tab); + + if($tab.length != 1) + $tab = $(DEFAULT_PERSPECTIVE); + + var $old = $tab.closest('.tabs').find('.active'); + + $old.each(function(){ + $(this).removeClass('active'); + self.perspectives[$(this).attr('id')].onExit(); + $('#' + $(this).attr('data-ui-related')).hide(); + }); + + /* show new */ + $tab.addClass('active'); + $('#' + $tab.attr('data-ui-related')).show(); + + console.log($tab); + console.log($.wiki.perspectives); + + $.wiki.perspectives[$tab.attr('id')].onEnter(); + }; + + /* + * Basic perspective. + */ + $.wiki.Perspective = function(options) { + if(!options) return; + + this.doc = options.doc; + if (options.id) { + this.perspective_id = options.id; + } + else { + this.perspective_id = ''; + } + + if(options.callback) + options.callback.call(this); + }; + + $.wiki.Perspective.prototype.config = function() { + return $.wiki.state.perspectives[this.perspective_id]; + } + + $.wiki.Perspective.prototype.toString = function() { + return this.perspective_id; + }; + + $.wiki.Perspective.prototype.dirty = function() { + return true; + }; + + $.wiki.Perspective.prototype.onEnter = function () { + // called when perspective in initialized + if (!this.noupdate_hash_onenter) { + document.location.hash = '#' + this.perspective_id; + } + }; + + $.wiki.Perspective.prototype.onExit = function () { + // called when user switches to another perspective + if (!this.noupdate_hash_onenter) { + document.location.hash = ''; + } + }; + + $.wiki.Perspective.prototype.destroy = function() { + // pass + }; + + $.wiki.Perspective.prototype.freezeState = function () { + // free UI state (don't store data here) + }; + + $.wiki.Perspective.prototype.unfreezeState = function (frozenState) { + // restore UI state + }; + + /* + * Stub rendering (used in generating history) + */ + $.wiki.renderStub = function(params) + { + params = $.extend({ 'filters': {} }, params); + var $elem = params.stub.clone(); + $elem.removeClass('row-stub'); + params.container.append($elem); + + $('*[data-stub-value]', $elem).each(function() { + var $this = $(this); + var field = $this.attr('data-stub-value'); + + var value = params.data[field]; + + if(params.filters[field]) + value = params.filters[field](value); + + if(value === null || value === undefined) return; + + if(!$this.attr('data-stub-target')) { + $this.text(value); + } + else { + $this.attr($this.attr('data-stub-target'), value); + $this.removeAttr('data-stub-target'); + $this.removeAttr('data-stub-value'); + } + }); + + $elem.show(); + return $elem; + }; + + /* + * Dialogs + */ + function GenericDialog(element) { + if(!element) return; + + var self = this; + + self.$elem = $(element); + + if(!self.$elem.attr('data-ui-initialized')) { + console.log("Initializing dialog", this); + self.initialize(); + self.$elem.attr('data-ui-initialized', true); + } + + self.show(); + }; + + GenericDialog.prototype = { + + /* + * Steps to follow when the dialog in first loaded on page. + */ + initialize: function(){ + var self = this; + + /* bind buttons */ + $('button[data-ui-action]', self.$elem).click(function(event) { + event.preventDefault(); + + var action = $(this).attr('data-ui-action'); + console.log("Button pressed, action: ", action); + + try { + self[action + "Action"].call(self); + } catch(e) { + console.log("Action failed:", e); + // always hide on cancel + if(action == 'cancel') + self.hide(); + } + }); + }, + + /* + * Prepare dialog for user. Clear any unnessary data. + */ + show: function() { + $.blockUI({ + message: this.$elem, + css: { + 'top': '25%', + 'left': '25%', + 'width': '50%' + } + }); + }, + + hide: function(){ + $.unblockUI(); + }, + + cancelAction: function() { + this.hide(); + }, + + doneAction: function() { + this.hide(); + }, + + clearForm: function() { + $("*[data-ui-error-for]", this.$elem).text(''); + }, + + reportErrors: function(errors) { + var global = $("*[data-ui-error-for='__all__']", this.$elem); + var unassigned = []; + + for (var field_name in errors) + { + var span = $("*[data-ui-error-for='"+field_name+"']", this.$elem); + + if(!span.length) { + unassigned.push(field_name); + continue; + } + + span.text(errors[field_name].join(' ')); + } + + if(unassigned.length > 0) + global.text( global.text() + 'W formularzu wystąpiły błędy'); + } + }; + + $.wiki.cls.GenericDialog = GenericDialog; + + $.wiki.showDialog = function(selector, options) { + var elem = $(selector); + + if(elem.length != 1) { + console.log("Failed to show dialog:", selector, elem); + return false; + } + + try { + var klass = elem.attr('data-ui-jsclass'); + return new $.wiki.cls[klass](elem, options); + } catch(e) { + console.log("Failed to show dialog", selector, klass, e); + return false; + } + }; + +})(jQuery); diff --git a/src/redakcja/static/js/wiki_img/loader.js b/src/redakcja/static/js/wiki_img/loader.js new file mode 100644 index 00000000..c3fe03e0 --- /dev/null +++ b/src/redakcja/static/js/wiki_img/loader.js @@ -0,0 +1,137 @@ +if (!window.console) { + window.console = { + log: function(){ + } + } +} + +DEFAULT_PERSPECTIVE = "#MotifsPerspective"; + +$(function() +{ + var tabs = $('ol#tabs li'); + var gallery = null; + CurrentDocument = new $.wikiapi.WikiDocument("document-meta"); + + $.blockUI.defaults.baseZ = 10000; + + function initialize() + { + $(document).keydown(function(event) { + console.log("Received key:", event); + }); + + /* The save button */ + $('#save-button').click(function(event){ + event.preventDefault(); + $.wiki.showDialog('#save_dialog'); + }); + + $('.editor').hide(); + + /* + * TABS + */ + $('.tabs li').live('click', function(event, callback) { + event.preventDefault(); + $.wiki.switchToTab(this); + }); + + $('#tabs li > .tabclose').live('click', function(event, callback) { + var $tab = $(this).parent(); + + if($tab.is('.active')) + $.wiki.switchToTab(DEFAULT_PERSPECTIVE); + + var p = $.wiki.perspectiveForTab($tab); + p.destroy(); + + return false; + }); + + + /*$(window).resize(function(){ + $('iframe').height($(window).height() - $('#tabs').outerHeight() - $('#source-editor .toolbar').outerHeight()); + }); + + $(window).resize();*/ + + /*$('.vsplitbar').toggle( + function() { + $.wiki.state.perspectives.ScanGalleryPerspective.show = true; + $('#sidebar').show(); + $('.vsplitbar').css('right', 480).addClass('active'); + $('#editor .editor').css('right', 510); + $(window).resize(); + $.wiki.perspectiveForTab('#tabs-right .active').onEnter(); + }, + function() { + var active_right = $.wiki.perspectiveForTab('#tabs-right .active'); + $.wiki.state.perspectives.ScanGalleryPerspective.show = false; + $('#sidebar').hide(); + $('.vsplitbar').css('right', 0).removeClass('active'); + $(".vsplitbar-title").html("↑ " + active_right.vsplitbar + " ↑"); + $('#editor .editor').css('right', 30); + $(window).resize(); + active_right.onExit(); + } + );*/ + + window.onbeforeunload = function(e) { + if($.wiki.isDirty()) { + e.returnValue = "Na stronie mogą być nie zapisane zmiany."; + return "Na stronie mogą być nie zapisane zmiany."; + }; + }; + + console.log("Fetching document's text"); + + $(document).bind('wlapi_document_changed', function(event, doc) { + try { + $('#document-revision').text(doc.revision); + } catch(e) { + console.log("Failed handler", e); + } + }); + + CurrentDocument.fetch({ + success: function(){ + console.log("Fetch success"); + $('#loading-overlay').fadeOut(); + var active_tab = document.location.hash || DEFAULT_PERSPECTIVE; + + console.log("Initial tab is:", active_tab) + $.wiki.switchToTab(active_tab); + }, + failure: function() { + $('#loading-overlay').fadeOut(); + alert("FAILURE"); + } + }); + }; /* end of initialize() */ + + + /* Load configuration */ + $.wiki.loadConfig(); + + var initAll = function(a, f) { + if (a.length == 0) return f(); + + $.wiki.initTab({ + tab: a.pop(), + doc: CurrentDocument, + callback: function(){ + initAll(a, f); + } + }); + }; + + + /* + * Initialize all perspectives + */ + initAll( $.makeArray($('.tabs li')), initialize); + console.log(location.hash); +}); + + diff --git a/src/redakcja/static/js/wiki_img/loader_readonly.js b/src/redakcja/static/js/wiki_img/loader_readonly.js new file mode 100755 index 00000000..99e5ad0e --- /dev/null +++ b/src/redakcja/static/js/wiki_img/loader_readonly.js @@ -0,0 +1,93 @@ +if (!window.console) { + window.console = { + log: function(){ + } + } +} + + +DEFAULT_PERSPECTIVE = "#MotifsPerspective"; + + +$(function() +{ + var tabs = $('ol#tabs li'); + var gallery = null; + + CurrentDocument = new $.wikiapi.WikiDocument("document-meta"); + $.blockUI.defaults.baseZ = 10000; + + function initialize() + { + $('.editor').hide(); + + /* + * TABS + */ + $('#tabs li').live('click', function(event, callback) { + event.preventDefault(); + $.wiki.switchToTab(this); + }); + + $('#tabs li > .tabclose').live('click', function(event, callback) { + var $tab = $(this).parent(); + + if($tab.is('.active')) + $.wiki.switchToTab(DEFAULT_PERSPECTIVE); + + var p = $.wiki.perspectiveForTab($tab); + p.destroy(); + return false; + }); +/* + $(window).resize(function(){ + $('iframe').height($(window).height() - $('#tabs').outerHeight() - $('#source-editor .toolbar').outerHeight()); + }); + */ + + $(document).bind('wlapi_document_changed', function(event, doc) { + try { + $('#document-revision').text(doc.revision); + } catch(e) { + console.log("Failed handler", e); + } + }); + + CurrentDocument.fetch({ + success: function(){ + console.log("Fetch success"); + $('#loading-overlay').fadeOut(); + var active_tab = document.location.hash || DEFAULT_PERSPECTIVE; + + console.log("Initial tab is:", active_tab) + $.wiki.switchToTab(active_tab); + }, + failure: function() { + $('#loading-overlay').fadeOut(); + alert("FAILURE"); + } + }); + }; /* end of initialize() */ + + /* Load configuration */ + $.wiki.loadConfig(); + + var initAll = function(a, f) { + if (a.length == 0) return f(); + + $.wiki.initTab({ + tab: a.pop(), + doc: CurrentDocument, + callback: function(){ + initAll(a, f); + } + }); + }; + + + /* + * Initialize all perspectives + */ + initAll( $.makeArray($('ol#tabs li')), initialize); + console.log(location.hash); +}); diff --git a/src/redakcja/static/js/wiki_img/view_editor_motifs.js b/src/redakcja/static/js/wiki_img/view_editor_motifs.js new file mode 100644 index 00000000..ad60c229 --- /dev/null +++ b/src/redakcja/static/js/wiki_img/view_editor_motifs.js @@ -0,0 +1,158 @@ +(function($){ + + function MotifsPerspective(options){ + + var old_callback = options.callback; + + options.callback = function(){ + var self = this; + + self.$tag_name = $('#motifs-editor .tag-name'); + self.$toolbar = $('#motifs-editor .toolbar'); + self.$scrolled = $('#motifs-editor .scrolled'); + withThemes(function(canonThemes){ + self.$tag_name.autocomplete(canonThemes, { + autoFill: true, + multiple: true, + selectFirst: true, + highlight: false + }); + }) + + self.$objects_list = $('#motifs-editor .objects-list'); + + self.x1 = null; + self.x2 = null; + self.y1 = null; + self.y2 = null; + + if (!CurrentDocument.readonly) { + self.ias = $('#motifs-editor img.area-selectable').imgAreaSelect({ handles: true, onSelectEnd: self._fillCoords(self), instance: true }); + $('#motifs-editor .add').click(self._addObject(self)); + + $('.delete', self.$objects_list).live('click', function() { + $(this).prev().trigger('click'); + if (window.confirm("Czy na pewno chcesz usunąć ten motyw?")) { + $(this).prev().remove(); + $(this).remove(); + self._refreshLayout(); + } + self._resetSelection(); + return false; + }); + } + + $('.image-object', self.$objects_list).live('click', function(){ + $('.active', self.$objects_list).removeClass('active'); + $(this).addClass('active'); + var coords = $(this).data('coords'); + if (coords) { + self.ias.setSelection.apply(self.ias, coords); + self.ias.setOptions({ show: true }); + } + else { + self._resetSelection(); + } + }); + + old_callback.call(this); + }; + + $.wiki.Perspective.call(this, options); + }; + + MotifsPerspective.prototype = new $.wiki.Perspective(); + + MotifsPerspective.prototype.freezeState = function(){ + + }; + + MotifsPerspective.prototype._resetSelection = function() { + var self = this; + self.x1 = self.x2 = self.y1 = self.y2 = null; + self.ias.setOptions({ hide: true }); + } + + MotifsPerspective.prototype._refreshLayout = function() { + this.$scrolled.css({top: this.$toolbar.height()}); + }; + + MotifsPerspective.prototype._push = function(name, x1, y1, x2, y2) { + var $e = $('') + $e.text(name); + if (x1 !== null) + $e.data('coords', [x1, y1, x2, y2]); + this.$objects_list.append($e); + this.$objects_list.append('(x) '); + this._refreshLayout(); + } + + + MotifsPerspective.prototype._addObject = function(self) { + return function() { + outputs = []; + chunks = self.$tag_name.val().split(','); + for (i in chunks) { + item = chunks[i].trim(); + if (item == '') + continue; + outputs.push(item.trim()); + } + output = outputs.join(', '); + + self._push(output, self.x1, self.y1, self.x2, self.y2); + self._resetSelection(); + } + } + + MotifsPerspective.prototype._fillCoords = function(self) { + return function(img, selection) { + $('.active', self.$objects_list).removeClass('active'); + if (selection.x1 != selection.x2 && selection.y1 != selection.y2) { + self.x1 = selection.x1; + self.x2 = selection.x2; + self.y1 = selection.y1; + self.y2 = selection.y2; + } + else { + self.x1 = self.x2 = self.y1 = self.y2 = null; + } + } + } + + MotifsPerspective.prototype.onEnter = function(success, failure){ + var self = this; + this.$objects_list.children().remove(); + + $.each(this.doc.getImageItems('theme'), function(i, e) { + self._push.apply(self, e); + }); + + if (this.x1 !== null) + this.ias.setOptions({enable: true, show: true}); + else + this.ias.setOptions({enable: true}); + + $.wiki.Perspective.prototype.onEnter.call(this); + + }; + + MotifsPerspective.prototype.onExit = function(success, failure){ + var self = this; + var motifs = []; + this.$objects_list.children(".image-object").each(function(i, e) { + var args = $(e).data('coords'); + if (!args) + args = [null, null, null, null]; + args.unshift($(e).text()); + motifs.push(args); + }) + self.doc.setImageItems('theme', motifs); + + this.ias.setOptions({disable: true, hide: true}); + + }; + + $.wiki.MotifsPerspective = MotifsPerspective; + +})(jQuery); diff --git a/src/redakcja/static/js/wiki_img/view_editor_objects.js b/src/redakcja/static/js/wiki_img/view_editor_objects.js new file mode 100644 index 00000000..b0cf2a9d --- /dev/null +++ b/src/redakcja/static/js/wiki_img/view_editor_objects.js @@ -0,0 +1,149 @@ +(function($){ + + function ObjectsPerspective(options){ + + var old_callback = options.callback; + + options.callback = function(){ + var self = this; + + self.$tag_name = $('#objects-editor .tag-name'); + self.$toolbar = $('#objects-editor .toolbar'); + self.$scrolled = $('#objects-editor .scrolled'); + self.$objects_list = $('#objects-editor .objects-list'); + + self.x1 = null; + self.x2 = null; + self.y1 = null; + self.y2 = null; + + if (!CurrentDocument.readonly) { + self.ias = $('#objects-editor img.area-selectable').imgAreaSelect({ handles: true, onSelectEnd: self._fillCoords(self), instance: true }); + $('#objects-editor .add').click(self._addObject(self)); + + $('.delete', self.$objects_list).live('click', function() { + $(this).prev().trigger('click'); + if (window.confirm("Czy na pewno chcesz usunąć ten obiekt?")) { + $(this).prev().remove(); + $(this).remove(); + self._refreshLayout(); + } + self._resetSelection(); + return false; + }); + } + + $('.image-object', self.$objects_list).live('click', function(){ + $('.active', self.$objects_list).removeClass('active'); + $(this).addClass('active'); + var coords = $(this).data('coords'); + if (coords) { + self.ias.setSelection.apply(self.ias, coords); + self.ias.setOptions({ show: true }); + } + else { + self._resetSelection(); + } + }); + + old_callback.call(this); + }; + + $.wiki.Perspective.call(this, options); + }; + + ObjectsPerspective.prototype = new $.wiki.Perspective(); + + ObjectsPerspective.prototype.freezeState = function(){ + + }; + + ObjectsPerspective.prototype._resetSelection = function() { + var self = this; + self.x1 = self.x2 = self.y1 = self.y2 = null; + self.ias.setOptions({ hide: true }); + } + + ObjectsPerspective.prototype._refreshLayout = function() { + this.$scrolled.css({top: this.$toolbar.height()}); + }; + + ObjectsPerspective.prototype._push = function(name, x1, y1, x2, y2) { + var $e = $('') + $e.text(name); + if (x1 !== null) + $e.data('coords', [x1, y1, x2, y2]); + this.$objects_list.append($e); + this.$objects_list.append('(x) '); + this._refreshLayout(); + } + + + ObjectsPerspective.prototype._addObject = function(self) { + return function() { + outputs = []; + chunks = self.$tag_name.val().split(','); + for (i in chunks) { + item = chunks[i].trim(); + if (item == '') + continue; + outputs.push(item.trim()); + } + output = outputs.join(', '); + + self._push(output, self.x1, self.y1, self.x2, self.y2); + self._resetSelection(); + } + } + + ObjectsPerspective.prototype._fillCoords = function(self) { + return function(img, selection) { + $('.active', self.$objects_list).removeClass('active'); + if (selection.x1 != selection.x2 && selection.y1 != selection.y2) { + self.x1 = selection.x1; + self.x2 = selection.x2; + self.y1 = selection.y1; + self.y2 = selection.y2; + } + else { + self.x1 = self.x2 = self.y1 = self.y2 = null; + } + } + } + + ObjectsPerspective.prototype.onEnter = function(success, failure){ + var self = this; + this.$objects_list.children().remove(); + + $.each(this.doc.getImageItems('object'), function(i, e) { + self._push.apply(self, e); + }); + + if (this.x1 !== null) + this.ias.setOptions({enable: true, show: true}); + else + this.ias.setOptions({enable: true}); + + $.wiki.Perspective.prototype.onEnter.call(this); + + }; + + ObjectsPerspective.prototype.onExit = function(success, failure){ + var self = this; + var objects = []; + this.$objects_list.children(".image-object").each(function(i, e) { + var args = $(e).data('coords'); + if (!args) + args = [null, null, null, null]; + args.unshift($(e).text()); + objects.push(args); + }) + self.doc.setImageItems('object', objects); + + this.ias.setOptions({disable: true, hide: true}); + + }; + + $.wiki.ObjectsPerspective = ObjectsPerspective; + +})(jQuery); diff --git a/src/redakcja/static/js/wiki_img/wikiapi.js b/src/redakcja/static/js/wiki_img/wikiapi.js new file mode 100644 index 00000000..377e4f94 --- /dev/null +++ b/src/redakcja/static/js/wiki_img/wikiapi.js @@ -0,0 +1,418 @@ +(function($) { + $.wikiapi = {}; + var noop = function() { + }; + var noops = { + success: noop, + failure: noop + }; + /* + * Return absolute reverse path of given named view. (at least he have it + * hard-coded in one place) + * + * TODO: think of a way, not to hard-code it here ;) + * + */ + function reverse() { + var vname = arguments[0]; + var base_path = "/images"; + + if (vname == "ajax_document_text") + return base_path + "/text/" + arguments[1] + "/"; + + + if (vname == "ajax_document_revert") { + return base_path + "/revert/" + arguments[1] + '/'; + } + + if (vname == "ajax_document_history") { + return base_path + "/history/" + arguments[1] + '/'; + } + + if (vname == "ajax_document_diff") + return base_path + "/diff/" + arguments[1] + '/'; + + if (vname == "ajax_document_pubmark") + return base_path + "/pubmark/" + arguments[1] + '/'; + + console.log("Couldn't reverse match:", vname); + return "/404.html"; + }; + + /* + * Document Abstraction + */ + function WikiDocument(element_id) { + var meta = $('#' + element_id); + this.id = meta.attr('data-object-id'); + + this.revision = $("*[data-key='revision']", meta).text(); + this.readonly = !!$("*[data-key='readonly']", meta).text(); + + var diff = $("*[data-key='diff']", meta).text(); + if (diff) { + 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.text = null; + this.has_local_changes = false; + this._lock = -1; + this._context_lock = -1; + this._lock_count = 0; + }; + + WikiDocument.prototype.triggerDocumentChanged = function() { + $(document).trigger('wlapi_document_changed', this); + }; + /* + * Fetch text of this document. + */ + WikiDocument.prototype.fetch = function(params) { + params = $.extend({}, noops, params); + var self = this; + $.ajax({ + method: "GET", + url: reverse("ajax_document_text", self.id), + data: {"revision": self.revision}, + dataType: 'json', + success: function(data) { + var changed = false; + + if (self.text === null || self.revision !== data.revision) { + self.text = data.text; + if (self.text === '') { + self.text = ''; + } + self.revision = data.revision; +// self.commit = data.commit; + changed = true; + self.triggerDocumentChanged(); + }; + + self.has_local_changes = false; + params['success'](self, changed); + }, + error: function() { + params['failure'](self, "Nie udało się wczytać treści dokumentu."); + } + }); + }; + /* + * Fetch history of this document. + * + * from - First revision to fetch (default = 0) upto - Last revision to + * fetch (default = tip) + * + */ + WikiDocument.prototype.fetchHistory = function(params) { + /* this doesn't modify anything, so no locks */ + params = $.extend({}, noops, params); + var self = this; + $.ajax({ + method: "GET", + url: reverse("ajax_document_history", self.id), + dataType: 'json', + data: { + "from": params['from'], + "upto": params['upto'] + }, + success: function(data) { + params['success'](self, data); + }, + error: function() { + params['failure'](self, "Nie udało się wczytać historii dokumentu."); + } + }); + }; + WikiDocument.prototype.fetchDiff = function(params) { + /* this doesn't modify anything, so no locks */ + var self = this; + params = $.extend({ + 'from': self.revision, + 'to': self.revision + }, noops, params); + $.ajax({ + method: "GET", + url: reverse("ajax_document_diff", self.id), + dataType: 'html', + data: { + "from": params['from'], + "to": params['to'] + }, + success: function(data) { + params['success'](self, data); + }, + error: function() { + params['failure'](self, "Nie udało się wczytać porównania wersji."); + } + }); + }; + + /* + * Set document's text + */ + WikiDocument.prototype.setText = function(text) { + this.text = text; + this.has_local_changes = true; + }; + + /* + * Save text back to the server + */ + WikiDocument.prototype.save = function(params) { + params = $.extend({}, noops, params); + var self = this; + + if (!self.has_local_changes) { + console.log("Abort: no changes."); + return params['success'](self, false, "Nie ma zmian do zapisania."); + }; + + // Serialize form to dictionary + var data = {}; + $.each(params['form'].serializeArray(), function() { + data[this.name] = this.value; + }); + + data['textsave-text'] = self.text; + + $.ajax({ + url: reverse("ajax_document_text", self.id), + type: "POST", + dataType: "json", + data: data, + success: function(data) { + var changed = false; + + $('#header').removeClass('saving'); + + if (data.text) { + self.text = data.text; + self.revision = data.revision; +// self.commit = data.commit; + changed = true; + self.triggerDocumentChanged(); + }; + + params['success'](self, changed, ((changed && "Udało się zapisać :)") || "Twoja wersja i serwera jest identyczna")); + }, + error: function(xhr) { + if ($('#header').hasClass('saving')) { + $('#header').removeClass('saving'); + $.blockUI({ + message: "

    Nie udało się zapisać zmian.

    " + }) + } + else { + try { + params['failure'](self, $.parseJSON(xhr.responseText)); + } + catch (e) { + params['failure'](self, { + "__message": "

    Nie udało się zapisać - błąd serwera.

    " + }); + }; + } + + } + }); + + $('#save-hide').click(function(){ + $('#header').addClass('saving'); + $.unblockUI(); + $.wiki.blocking.unblock(); + }); + }; /* end of save() */ + + WikiDocument.prototype.revertToVersion = function(params) { + var self = this; + params = $.extend({}, noops, params); + + if (params.revision >= this.revision) { + params.failure(self, 'Proszę wybrać rewizję starszą niż aktualna.'); + return; + } + + // Serialize form to dictionary + var data = {}; + $.each(params['form'].serializeArray(), function() { + data[this.name] = this.value; + }); + + $.ajax({ + url: reverse("ajax_document_revert", self.id), + type: "POST", + dataType: "json", + data: data, + success: function(data) { + if (data.text) { + self.text = data.text; + self.revision = data.revision; + self.gallery = data.gallery; + self.triggerDocumentChanged(); + + params.success(self, "Udało się przywrócić wersję :)"); + } + else { + params.failure(self, "Przywracana wersja identyczna z aktualną. Anulowano przywracanie."); + } + }, + error: function(xhr) { + params.failure(self, "Nie udało się przywrócić wersji - błąd serwera."); + } + }); + }; + + 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."] + }); + }; + }; + } + }); + }; + + + + WikiDocument.prototype.getImageItems = function(tag) { + var self = this; + + var parser = new DOMParser(); + var doc = parser.parseFromString(self.text, 'text/xml'); + var error = $('parsererror', doc); + + if (error.length != 0) { + return null; + } + + var a = []; + $('sem[type="'+tag+'"]', doc).each(function(i, e) { + var $e = $(e); + var $div = $e.children().first() + var value = $e.attr(tag); + $e.find('div').each(function(i, div) { + var $div = $(div); + switch ($div.attr('type')) { + case 'rect': + a.push([ + value, + $div.attr('x1'), + $div.attr('y1'), + $div.attr('x2'), + $div.attr('y2') + ]); + break; + case 'whole': + a.push([ + value, + null, null, null, null + ]); + break + } + }); + }); + + return a; + } + + WikiDocument.prototype.setImageItems = function(tag, items) { + var self = this; + + var parser = new DOMParser(); + var doc = parser.parseFromString(self.text, 'text/xml'); + var serializer = new XMLSerializer(); + var error = $('parsererror', doc); + + if (error.length != 0) { + return null; + } + + $('sem[type="'+tag+'"]', doc).remove(); + $root = $(doc.firstChild); + $.each(items, function(i, e) { + var $sem = $(doc.createElement("sem")); + $sem.attr('type', tag); + $sem.attr(tag, e[0]); + $div = $(doc.createElement("div")); + if (e[1]) { + $div.attr('type', 'rect'); + $div.attr('x1', e[1]); + $div.attr('y1', e[2]); + $div.attr('x2', e[3]); + $div.attr('y2', e[4]); + } + else { + $div.attr('type', 'whole'); + } + $sem.append($div); + $root.append($sem); + }); + self.setText(serializer.serializeToString(doc)); + } + + + $.wikiapi.WikiDocument = WikiDocument; +})(jQuery); + + + +// Wykonuje block z załadowanymi kanonicznymi motywami +function withThemes(code_block, onError) +{ + if (typeof withThemes.canon == 'undefined') { + $.ajax({ + url: '/editor/themes', + dataType: 'text', + success: function(data) { + withThemes.canon = data.split('\n'); + code_block(withThemes.canon); + }, + error: function() { + withThemes.canon = null; + code_block(withThemes.canon); + } + }) + } + else { + code_block(withThemes.canon); + } +} + diff --git a/src/redakcja/static/xsl/wl2html_client.xsl b/src/redakcja/static/xsl/wl2html_client.xsl new file mode 100644 index 00000000..784acb7e --- /dev/null +++ b/src/redakcja/static/xsl/wl2html_client.xsl @@ -0,0 +1,867 @@ + + + + + + + + + + + + + + + + +
    + + + + +
    +
    + + + + + + + +
    + + + + +
    +
    + + + +
    + + + + +
    +
    + + + +
    + + + + +
    +
    + + + +
    + + + + +
    +
    + + + + + + +

    + + + + + +

    +
    + + + + +

    + + + + + +

    +
    + + + + +

    + + + + + +

    +
    + + + + +

    + + + + + +

    +
    + + + + + +
    + + + + +
    +
    + + + +
    + + + + +
    +
    + + + + +
    + + + + +
    +
    + + + + +
    + + + + +
    +
    + + + + +
    +
    + + + + +
    + + + +
    +
    + + + + + + + + + + +

    + + + + + +

    +
    + + + + + + + + +
    + + + + +
    +
    + + + +

    + + + + + +

    +
    + + + +

    + + + + + +

    +
    + + + + +

    + + + + + +

    +
    + + + + + + + +

    + + + + + +

    +
    + + + + +

    + + + + + +

    +
    + + + + +

    + + + + + +

    +
    + + + + + +

    + + + + + +

    +
    + + + +

    + + + + + +

    +
    + + + + + +

    + + + + + +

    +
    + + + +

    + + + + + +

    +
    + + + + + +
    + + + + +
    +
    + + + +
    + + + + +
    +
    + + + +
    + + + + +
    +
    + + + +
    + + + + +
    +
    + + + +
    + + + + +
    +
    + + + +
    + + + + +
    +
    + + + + + +

    + + + + + +

    +
    + + + +

    + + + + + +

    +
    + + + +

    + + + + + +

    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + +

    + + + +

    +
    + + + + + + +
    +
    + + + + +

    + + + + +

    + +
    + + / + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +

    +
    + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + unknown-tag + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/src/redakcja/templates/404.html b/src/redakcja/templates/404.html new file mode 100644 index 00000000..11edc9d5 --- /dev/null +++ b/src/redakcja/templates/404.html @@ -0,0 +1,29 @@ +{% extends "catalogue/base.html" %} +{% load i18n %} + +{% block titleextra %}{% trans "Page not found" %}{% endblock %} + +{% block content %} + +

    {% trans "Page not found" %}

    + +{% blocktrans with p=request.build_absolute_uri %}The page you're trying +to reach ({{p}}) could not be found. If it's a document, try +looking for it on the document list.{% endblocktrans %} + +

    +{% blocktrans with m="mailto:radoslaw.czajka@nowoczesnapolska.org.pl" %}If you +still can't find what you're looking for, please +contact the administrator.{% endblocktrans %} +

    + +{% url "catalogue_user" as m %} +

    +{% blocktrans %}If you're coming from Redmine, please note that +work is no longer managed there. +Go to the document list +or to your page instead.{% endblocktrans %} +

    + + +{% endblock content %} diff --git a/src/redakcja/templates/500.html b/src/redakcja/templates/500.html new file mode 100644 index 00000000..bd7404f3 --- /dev/null +++ b/src/redakcja/templates/500.html @@ -0,0 +1,13 @@ +{% extends "error_base.html" %} + +{% block "content" %} + +

    Błąd po stronie serwera.

    + +

    Niestety nasz serwer WWW nie był w stanie dostarczyć Ci strony o którą prosisz.

    +

    Serdecznie przepraszamy.

    +

    Administrator został już powiadomiony o błędzie, ale jeśli chciałbyś +przekazać nam więcej informacji na temat błędu, napisz na nasz adres.

    + + +{% endblock %} diff --git a/src/redakcja/templates/503.html b/src/redakcja/templates/503.html new file mode 100644 index 00000000..1c1d39a1 --- /dev/null +++ b/src/redakcja/templates/503.html @@ -0,0 +1,14 @@ +{% extends "error_base.html" %} + +{% block "content" %} + +

    Serwis tymczasowo niedostępny

    + +

    +Platfroma redakcyjna serwisu Wolne Lektury jest +tymczasowo niedostępna z powodu prac administracyjnych. +

    + +

    Prosimy o wyrozumiałość i ponowne odwiedziny.

    + +{% endblock "content" %} diff --git a/src/redakcja/templates/base.html b/src/redakcja/templates/base.html new file mode 100644 index 00000000..fc609954 --- /dev/null +++ b/src/redakcja/templates/base.html @@ -0,0 +1,28 @@ + +{% load i18n %} + + + + + {% block title %}{% block titleextra %}{% endblock titleextra %} :: + {% trans "Platforma Redakcyjna" %}{% endblock title %} + {% block extrahead %} + {% endblock %} + + + +
    +
    + Loading +

    {% trans "Loading" %}

    +
    +
    + +
    + +
    {% block maincontent %} {% endblock %}
    +
    + + {% block extrabody %}{% endblock %} + + diff --git a/src/redakcja/templates/error_base.html b/src/redakcja/templates/error_base.html new file mode 100755 index 00000000..58784dc6 --- /dev/null +++ b/src/redakcja/templates/error_base.html @@ -0,0 +1,66 @@ + + + + + + Platforma Redakcyjna + + + +
    + + + +
    +
    + +
    +{% block "content" %} +{% endblock %} +
    + + + + diff --git a/src/redakcja/templates/pagination/pagination.html b/src/redakcja/templates/pagination/pagination.html new file mode 100755 index 00000000..fe566a86 --- /dev/null +++ b/src/redakcja/templates/pagination/pagination.html @@ -0,0 +1,26 @@ +{% if is_paginated %} +{% load i18n %} + +{% endif %} diff --git a/src/redakcja/templates/registration/head_login.html b/src/redakcja/templates/registration/head_login.html new file mode 100644 index 00000000..2a8fd3bd --- /dev/null +++ b/src/redakcja/templates/registration/head_login.html @@ -0,0 +1,16 @@ +{% load i18n %} + +{% if user.is_authenticated %} +{{ user.username }} | + +{% if user.is_staff %} + {% trans "Admin" %} | +{% endif %} + +{% trans "Log Out" %} +{% else %} +{% url "login" as login_url %} +{% ifnotequal login_url request.path %} + {% trans "Log In" %} +{% endifnotequal %} +{% endif %} diff --git a/src/redakcja/templates/registration/login.html b/src/redakcja/templates/registration/login.html new file mode 100644 index 00000000..adbef3cb --- /dev/null +++ b/src/redakcja/templates/registration/login.html @@ -0,0 +1,17 @@ +{% extends "catalogue/base.html" %} + +{% block titleextra %}Logowanie{% endblock %} +{% block subtitle %} - Logowanie {% endblock subtitle %} + +{% block content %} + +
    +
    +{% csrf_token %} +{{ form.as_p }} +

    + +
    +
    + +{% endblock content %} diff --git a/src/redakcja/urls.py b/src/redakcja/urls.py new file mode 100644 index 00000000..c0629fac --- /dev/null +++ b/src/redakcja/urls.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +from django.conf.urls import include, patterns, url +from django.contrib import admin +from django.conf import settings +from django.conf.urls.static import static +from django.contrib.staticfiles.urls import staticfiles_urlpatterns +from django.views.generic import RedirectView + + +admin.autodiscover() + +urlpatterns = patterns('', + # Auth + url(r'^accounts/login/$', 'django_cas.views.login', name='login'), + url(r'^accounts/logout/$', 'django_cas.views.logout', name='logout'), + url(r'^admin/login/$', 'django_cas.views.login', name='login'), + url(r'^admin/logout/$', 'django_cas.views.logout', name='logout'), + + # Admin panel + url(r'^admin/doc/', include('django.contrib.admindocs.urls')), + (r'^admin/', include(admin.site.urls)), + + (r'^comments/', include('django.contrib.comments.urls')), + + url(r'^$', RedirectView.as_view(url= '/documents/')), + url(r'^documents/', include('catalogue.urls')), + url(r'^apiclient/', include('apiclient.urls')), + url(r'^editor/', include('wiki.urls')), + url(r'^images/', include('wiki_img.urls')), + url(r'^cover/', include('cover.urls')), +) + +if settings.DEBUG: + urlpatterns += staticfiles_urlpatterns() + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/src/redakcja/wsgi.py b/src/redakcja/wsgi.py new file mode 100755 index 00000000..b0097e65 --- /dev/null +++ b/src/redakcja/wsgi.py @@ -0,0 +1,9 @@ +import os + + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "redakcja.settings") + +# This application object is used by the development server +# as well as any WSGI server configured to use this file. +from django.core.wsgi import get_wsgi_application +application = get_wsgi_application() diff --git a/src/toolbar/__init__.py b/src/toolbar/__init__.py new file mode 100644 index 00000000..c53f0e73 --- /dev/null +++ b/src/toolbar/__init__.py @@ -0,0 +1 @@ + # pragma: no cover diff --git a/src/toolbar/admin.py b/src/toolbar/admin.py new file mode 100644 index 00000000..654480ca --- /dev/null +++ b/src/toolbar/admin.py @@ -0,0 +1,31 @@ +from django.contrib import admin +from django.utils.translation import ugettext_lazy as _ +from django import forms +import json + +from toolbar import models + + +class ButtonAdminForm(forms.ModelForm): + class Meta: + model = models.Button + exclude = [] + + def clean_params(self): + value = self.cleaned_data['params'] + try: + return json.dumps(json.loads(value)) + except ValueError, e: + raise forms.ValidationError(e) + + +class ButtonAdmin(admin.ModelAdmin): + form = ButtonAdminForm + list_display = ('slug', 'label', 'tooltip', 'accesskey') + list_display_links = ('slug',) + list_editable = ('label', 'tooltip', 'accesskey') + prepopulated_fields = {'slug': ('label',)} + +admin.site.register(models.Button, ButtonAdmin) +admin.site.register(models.ButtonGroup) +admin.site.register(models.Scriptlet) diff --git a/src/toolbar/fixtures/initial_toolbar.yaml b/src/toolbar/fixtures/initial_toolbar.yaml new file mode 100644 index 00000000..be8c3399 --- /dev/null +++ b/src/toolbar/fixtures/initial_toolbar.yaml @@ -0,0 +1,1067 @@ +- fields: {name: "Nag\u0142\xF3wki", position: 0, slug: naglowki} + model: toolbar.buttongroup + pk: 1 +- fields: {name: Autokorekta, position: 0, slug: autokorekta} + model: toolbar.buttongroup + pk: 2 +- fields: {name: Mastery, position: 0, slug: mastery} + model: toolbar.buttongroup + pk: 11 +- fields: {name: 'Dramat ', position: 0, slug: dramat} + model: toolbar.buttongroup + pk: 12 +- fields: {name: "Elementy pocz\u0105tkowe", position: 0, slug: elementy-poczatkowe} + model: toolbar.buttongroup + pk: 13 +- fields: {name: Akapity, position: 0, slug: akapity} + model: toolbar.buttongroup + pk: 14 +- fields: {name: Style znakowe, position: 0, slug: style-znakowe} + model: toolbar.buttongroup + pk: 15 +- fields: {name: Separatory, position: 0, slug: separatory} + model: toolbar.buttongroup + pk: 16 +- fields: {name: Wersy, position: 0, slug: wersy} + model: toolbar.buttongroup + pk: 17 +- fields: {name: Bloki, position: 0, slug: bloki} + model: toolbar.buttongroup + pk: 21 +- fields: {name: "Pocz\u0105tek dramatu", position: 0, slug: poczatek-dramatu} + model: toolbar.buttongroup + pk: 22 +- fields: {name: Przypisy, position: 0, slug: przypisy} + model: toolbar.buttongroup + pk: 26 +- fields: {name: Autotagowanie, position: 0, slug: autotagowanie} + model: toolbar.buttongroup + pk: 28 +- fields: {name: Uwaga, position: 0, slug: uwaga} + model: toolbar.buttongroup + pk: 29 +- fields: + accesskey: '' + group: [2] + label: ",,\u2026\" na \xAB\u2026\xBB" + link: '' + params: '{"exprs": [[",,", "\u00ab"], ["\"", "\u00bb"]]}' + scriptlet: fulltextregexp + slug: na-francuskie + tooltip: "Zamienia cudzys\u0142owy podw\xF3jne na francuskie" + model: toolbar.button + pk: 2 +- fields: + accesskey: '' + group: [2] + label: ",,\u2026\" na \xBB\u2026\xAB" + link: '' + params: '{"exprs": [[",,", "\u00bb"], ["\"", "\u00ab"]]}' + scriptlet: fulltextregexp + slug: na-niemieckie + tooltip: "Zamienia cudzys\u0142owy podw\xF3jne na niemieckie" + model: toolbar.button + pk: 3 +- fields: + accesskey: '' + group: [2] + label: Podstawowa + link: '' + params: '[["fulltextregexp", {"exprs": [["\ufeff", ""], ["$[\\s]*\\d+[\\s]*^", + ""], ["-\\s*^", ""], ["\\,\\.\\.|\\.\\,\\.|\\.\\.\\,", "..."], ["<(/?)P([aert])", + "<$1p$2"], ["[\u2014\u2013\u2010-]{2,}|[\u2014\u2013\u2010]+", "---"], + ["(\\s)-([^-])", "$1---$2"], ["([^-])-(\\s)", "$1---$2"], ["(\\d)-+(\\d)", + "$1--$2"], ["---(\\S)", "--- $1"], ["(\\S)---", "$1 ---"], ["\\s*-+\\s*", + "--- "]]}], ["lineregexp", {"exprs": [["^\\s+|\\s+$", ""], + ["\\s+", " "], ["(,,)\\s+", "$1"], ["\\s+(\")", "$1"], ["([^\\.])(\\s*)\u2193" + link: '' + params: '[]' + scriptlet: lowercase + slug: tolowercase + tooltip: "Zamie\u0144 wielkie litery na ma\u0142e" + model: toolbar.button + pk: 76 +- fields: + accesskey: '' + group: [2] + label: "zamiana cudzys\u0142ow\xF3w 2" + link: '' + params: '{"exprs": [["\u00bb|\u201e", ",,"], ["\u00ab", "\""], ["([^=])\"([\u0104\u0118\u00d3\u0141\u017b\u0179\u0106\u0143\u0105\u017c\u017a\u015b\u0144\u00f3\u0142\u0107\\w])", + "$1,,$2"], ["^\"([\u0104\u0118\u00d3\u0141\u017b\u0179\u0106\u0143\u0105\u017c\u017a\u015b\u0144\u00f3\u0142\u0107\\w])", + ",,$1"], ["(,,)\\s+|\\s+(\")", "$1"]]}' + scriptlet: lineregexp + slug: cudzyslow-niemiecki + tooltip: "zamienia \" na ,, oraz \xBBa\xAB na ,,a\"" + model: toolbar.button + pk: 77 +- fields: + accesskey: '' + group: [22] + label: 'typ osoby ' + link: '' + params: '{"padding_top": 1, "padding_bottom": 1, "tag": "lista_osoba", "attrs": + {"typ": ""}}' + scriptlet: insert_tag + slug: lista-osob-pole + tooltip: osoby z takim samym opisem + model: toolbar.button + pk: 78 +- fields: + accesskey: '' + group: [22] + label: "didaskalia pocz\u0105tkowe" + link: '' + params: '{"padding_top": 1, "padding_bottom": 3, "tag": "miejsce_czas"}' + scriptlet: insert_tag + slug: didaskalia-poczatkowe + tooltip: "komentarze wprowadzaj\u0105ce przed tekstem dramatu" + model: toolbar.button + pk: 79 +- fields: + accesskey: s + group: [12, 17] + label: strofa + link: '' + params: '{"padding_top": 1, "padding_bottom": 3, "tag": "strofa"}' + scriptlet: insert_stanza + slug: strofa + tooltip: "wstawia strof\u0119" + model: toolbar.button + pk: 81 +- fields: + accesskey: k + group: [12] + label: kwestia + link: '' + params: '{"padding_top": 1, "padding_bottom": 1, "tag": "kwestia"}' + scriptlet: insert_tag + slug: kwestia + tooltip: "wstawia kwesti\u0119" + model: toolbar.button + pk: 82 +- fields: + accesskey: w + group: [12, 17] + label: "wers mocno wci\u0119ty" + link: '' + params: '{"tag": "wers_wciety", "attrs": {"typ": ""}}' + scriptlet: insert_tag + slug: wers-mocno-wciety + tooltip: "argumenty wersu wci\u0119tego: od 2 do 6" + model: toolbar.button + pk: 84 +- fields: + accesskey: '' + group: [12, 17] + label: wers cd. + link: '' + params: '{"tag": "wers_cd"}' + scriptlet: insert_tag + slug: wers-cd + tooltip: "cz\u0119\u015B\u0107 wersu przeniesiona do innego wiersza" + model: toolbar.button + pk: 85 +- fields: + accesskey: '' + group: [12, 17] + label: wers do prawej + link: '' + params: '{"tag": "wers_do_prawej"}' + scriptlet: insert_tag + slug: wers-do-prawej + tooltip: "wers wyr\xf3wnany do prawej" + model: toolbar.button + pk: 109 +- fields: + accesskey: '' + group: [] + label: Wydrukuj + link: print/xml + params: '[]' + scriptlet: insert_tag + slug: print-xml + tooltip: '' + model: toolbar.button + pk: 86 +- fields: + accesskey: '' + group: [] + label: Wydrukuj + link: print/html + params: '[]' + scriptlet: insert_tag + slug: htmleditor-print + tooltip: '' + model: toolbar.button + pk: 87 +- fields: + accesskey: '' + group: [2] + label: "zamiana cudzys\u0142ow\xF3w 1" + link: '' + params: '{"exprs": [["\u00ab|\u201e", ",,"], ["\u00bb", "\""], ["([^=])\"([\u0104\u0118\u00d3\u0141\u017b\u0179\u0106\u0143\u0105\u017c\u017a\u015b\u0144\u00f3\u0142\u0107\\w])", + "$1,,$2"], ["^\"([\u0104\u0118\u00d3\u0141\u017b\u0179\u0106\u0143\u0105\u017c\u017a\u015b\u0144\u00f3\u0142\u0107\\w])", + ",,$1"], ["(,,)\\s+|\\s+(\")", "$1"]]}' + scriptlet: lineregexp + slug: cudzyslow-francuski + tooltip: "zamiana \" na ,, oraz \xABa\xBB na ,,a\"" + model: toolbar.button + pk: 89 +- fields: + accesskey: q + group: [12, 17] + label: "wers wci\u0119ty" + link: '' + params: '{"tag": "wers_wciety", "attrs": {"typ": "1"}}' + scriptlet: insert_tag + slug: wers-wciety + tooltip: "wstawia wers wci\u0119ty" + model: toolbar.button + pk: 91 +- fields: + accesskey: r + group: [15] + label: "tytu\u0142 dzie\u0142a" + link: '' + params: '{"tag": "tytul_dziela"}' + scriptlet: insert_tag + slug: tytul-dziela + tooltip: '' + model: toolbar.button + pk: 92 +- fields: + accesskey: '' + group: [22] + label: "lista os\xF3b" + link: '' + params: '{"padding_top": 1, "padding_bottom": 4, "tag": "lista_osob"}' + scriptlet: insert_tag + slug: lista-osob + tooltip: "lista os\xF3b poprzedzaj\u0105ca tekst dramatu" + model: toolbar.button + pk: 93 +- fields: + accesskey: '' + group: [22] + label: "nag\u0142\xF3wek listy" + link: '' + params: '{"padding_top": 1, "padding_bottom": 2, "tag": "naglowek_listy"}' + scriptlet: insert_tag + slug: naglowek-listy + tooltip: "nag\u0142\xF3wek listy os\xF3b" + model: toolbar.button + pk: 94 +- fields: + accesskey: '' + group: [22] + label: osoba na liscie + link: '' + params: '{"padding_top": 1, "padding_bottom": 1, "tag": "lista_osoba"}' + scriptlet: insert_tag + slug: osoba-na-liscie + tooltip: "nazwa osoby na liscie os\xF3b" + model: toolbar.button + pk: 95 +- fields: + accesskey: '' + group: [] + label: extra + link: '' + params: '{"tag": "extra"}' + scriptlet: insert_tag + slug: extra + tooltip: "uwagi dotycz\u0105ce sk\u0142adu" + model: toolbar.button + pk: 96 +- fields: + accesskey: '' + group: [28] + label: akapity + link: '' + params: '{"tag": "akap"}' + scriptlet: autotag + slug: akapity + tooltip: "autotagowanie akapit\xF3w" + model: toolbar.button + pk: 97 +- fields: + accesskey: '' + group: [28] + label: strofy + link: '' + params: '{"tag": "strofa"}' + scriptlet: autotag + slug: strofy + tooltip: autotagowanie strof + model: toolbar.button + pk: 99 +- fields: + accesskey: '' + group: [28] + label: "wersy wci\u0119te" + link: '' + params: '{"padding": 1, "tag": "wers_wciety", "split": 1}' + scriptlet: autotag + slug: wersy-wciete + tooltip: "autotagowanie wers\xF3w wci\u0119tych" + model: toolbar.button + pk: 100 +- fields: + accesskey: g + group: [12] + label: kwestioakapit + link: '' + params: '[["insert_tag", {"tag": "akap"}], ["insert_tag", {"padding_top": + 1, "padding_bottom": 1, "tag": "kwestia"}]]' + scriptlet: macro + slug: kwestioakapit + tooltip: '' + model: toolbar.button + pk: 101 +- fields: + accesskey: '' + group: [12] + label: kwestiostrofa + link: '' + params: '[["insert_stanza", {"tag": "strofa"}], ["insert_tag", {"padding_top": + 1, "padding_bottom": 1, "tag": "kwestia"}]]' + scriptlet: macro + slug: kwestiostrofa + tooltip: '' + model: toolbar.button + pk: 102 +- fields: + accesskey: '' + group: [28] + label: "nag\u0142. dramatu" + link: '' + params: '{"exprs": [["^AKT(\\s\\w*)$", "AKT$1"], + ["^SCENA(\\s\\w*)$", "SCENA$1"], ["([A-Z\u0104\u0106\u0118\u0141\u0143\u00d3\u015a\u017b\u0179]{2}[A-Z\u0104\u0106\u0118\u0141\u0143\u00d3\u015a\u017b\u0179\\s]+)$", + "$1"]]}' + scriptlet: lineregexp + slug: nagl-dramatu + tooltip: "autotagowanie akt\xF3w, scen, nag\u0142\xF3wk\xF3w os\xF3b" + model: toolbar.button + pk: 103 +- fields: + accesskey: x + group: [13] + label: nota red. + link: '' + params: '{"padding_top": 1, "padding_bottom": 3, "tag": "nota_red"}' + scriptlet: insert_tag + slug: nota-red + tooltip: nota redakcyjna + model: toolbar.button + pk: 104 +- fields: + accesskey: '' + group: [2] + label: slug + link: '' + params: '[]' + scriptlet: slugify + slug: slug + tooltip: slugifikacja + model: toolbar.button + pk: 105 +- fields: + accesskey: '' + group: [11] + label: trim begin + link: '' + params: '{"text": "\n\n"}' + scriptlet: insert_text + slug: trim-begin + tooltip: "Wstawia pocz\u0105tkowy znacznik ci\u0119cia cz\u0119\u015Bci" + model: toolbar.button + pk: 106 +- fields: + accesskey: '' + group: [11] + label: trim end + link: '' + params: '{"text": "\n\n"}' + scriptlet: insert_text + slug: trim-end + tooltip: "Wstawia ko\u0144cowy znacznik ci\u0119cia cz\u0119\u015Bci" + model: toolbar.button + pk: 107 +- fields: + accesskey: '' + group: [11] + label: etap + link: '' + params: '{"tag": "developmentStage"}' + scriptlet: insert_tag + slug: etap + tooltip: "wymaga uwsp\xF3\u0142czesnienia: 0.3" + model: toolbar.button + pk: 108 +- fields: {code: '-'} + model: toolbar.scriptlet + pk: autotag +- fields: {code: "editor.showPopup('generic-info', 'Przetwarzanie zaznaczonego tekstu...',\ + \ '', -1);\n$.log(editor, panel, params);\nvar cm = panel.texteditor;\n\ + var exprs = $.map(params.exprs, function(expr) {\n var opts = \"mg\"\ + ;\n if(expr.length > 2)\n opts = expr[2];\n\n return {rx:\ + \ new RegExp(expr[0], opts), repl: expr[1]};\n});\n\nvar partial = true;\n\ + var text = cm.selection();\n\nif(!text) {\n var cpos = cm.cursorPosition();\n\ + \ cpos.line = cm.lineNumber(cpos.line)\n cm.selectLines(cm.firstLine(),\ + \ 0, cm.lastLine(), 0);\n\n text = cm.selection();\n partial = false;\n\ + }\n\nvar original = text;\n$(exprs).each(function() { \n text = text.replace(this.rx,\ + \ this.repl);\n});\n\nif( original != text) \n{ \n cm.replaceSelection(text);\n\ + \ panel.fireEvent('contentChanged');\n editor.showPopup('generic-yes',\ + \ 'Zmieniono tekst' );\n editor.advancePopupQueue();\n}\nelse {\n \ + \ editor.showPopup('generic-info', 'Brak zmian w tek\u015Bcie.');\n\ + \ editor.advancePopupQueue();\n}\n\nif(!partial) {\n cm.selectLines(\ + \ cm.nthLine(cpos.line), cpos.character );\n}"} + model: toolbar.scriptlet + pk: fulltextregexp +- fields: {code: "var texteditor = panel.texteditor;\r\nvar text = texteditor.selection();\r\ + \n\r\nif(text) {\r\n var verses = text.split('\\n');\r\n var text =\ + \ ''; var buf = ''; var ebuf = '';\r\n var first = true;\r\n\r\n for(var\ + \ i=0; i < verses.length; i++) {\r\n verse = verses[i].replace(/^\\\ + s+/, \"\").replace(/\\s+$/, \"\"); \r\n if(verse) {\r\n text\ + \ += (buf ? buf + '/\\n' : '') + ebuf;\r\n buf = (first ? '\\\ + n' : '') + verses[i];\r\n ebuf = '';\r\n first = false;\r\n\ + \ } else { \r\n ebuf += '\\n' + verses[i];\r\n }\r\n };\r\ + \n text = text + buf + '\\n' + ebuf; \r\n texteditor.replaceSelection(text);\r\ + \n}\r\n\r\nif (!text) {\r\n var pos = texteditor.cursorPosition();\r\ + \n texteditor.selectLines(pos.line, pos.character + 6 + 2);\r\n}\r\n\ + \r\n\r\n\r\n\r\n\r\n\r\n\r\npanel.fireEvent('contentChanged');"} + model: toolbar.scriptlet + pk: insert_stanza +- fields: {code: "var texteditor = panel.texteditor;\nvar text = texteditor.selection();\n\ + var start_tag = '<'+params.tag;\nfor (var attr in params.attrs) {\n \ + \ start_tag += ' '+attr+'=\"' + params.attrs[attr] + '\"';\n};\nstart_tag\ + \ += '>';\nvar end_tag = '';\n\nif(text.length > 0) {\n\ + // tokenize\nvar output = ''\nvar token = ''\nfor(var index=0; index <\ + \ text.length; index++)\n{\n if (text[index].match(/\\s/)) { // whitespace\n\ + \ token += text[index];\n }\n else { // character\n \ + \ output += token;\n if(output == token) output += start_tag;\n\ + \ token = ''\n output += text[index];\n }\n}\n\nif( output[output.length-1]\ + \ == '\\\\' ) {\n output = output.substr(0, output.length-1) + end_tag\ + \ + '\\\\';\n} else {\n output += end_tag;\n}\noutput += token;\n}\n\ + else {\n output = start_tag + end_tag;\n}\n\ntexteditor.replaceSelection(output);\n\ + \nif (text.length == 0) {\n var pos = texteditor.cursorPosition();\n\ + \ texteditor.selectLines(pos.line, pos.character + params.tag.length\ + \ + 2);\n}\n\npanel.fireEvent('contentChanged');"} + model: toolbar.scriptlet + pk: insert_tag +- fields: {code: '-'} + model: toolbar.scriptlet + pk: insert_text +- fields: {code: "editor.showPopup('generic-info', 'Przetwarzanie zaznaczonego tekstu...',\ + \ '', -1);\n\nvar cm = panel.texteditor;\nvar exprs = $.map(params.exprs,\ + \ function(expr) {\n\n var opts = \"g\";\n\n if(expr.length > 2)\n\ + \n opts = expr[2];\n\n return {rx: new RegExp(expr[0], opts),\ + \ repl: expr[1]};\n\n});\n\n\n\nvar partial = true;\n\nvar text = cm.selection();\n\ + \n\n\nif(!text) {\n\n var cpos = cm.cursorPosition();\n\n cpos.line\ + \ = cm.lineNumber(cpos.line)\n\n cm.selectLines(cm.firstLine(), 0,\ + \ cm.lastLine(), 0);\n\n text = cm.selection();\n\n partial = false;\n\ + \n}\n\n\n\nvar changed = 0;\nvar lines = text.split('\\n');\nvar lines\ + \ = $.map(lines, function(line) { \n var old_line = line;\n $(exprs).each(function()\ + \ { \n var expr = this;\n line = line.replace(expr.rx, expr.repl);\n\ + \ });\n\n if(old_line != line) changed += 1;\n return line;\n\ + });\n\nif(changed > 0) \n{\n cm.replaceSelection( lines.join('\\n')\ + \ );\n panel.fireEvent('contentChanged');\n editor.showPopup('generic-yes',\ + \ 'Zmieniono ' + changed + ' linii.', 1500);\n editor.advancePopupQueue();\n\ + }\nelse {\n editor.showPopup('generic-info', 'Brak zmian w tek\u015B\ + cie', 1500);\n editor.advancePopupQueue();\n}\n\nif(!partial)\n \ + \ cm.selectLines( cm.nthLine(cpos.line), cpos.character )"} + model: toolbar.scriptlet + pk: lineregexp +- fields: {code: "var cm = panel.texteditor;\r\nvar text = cm.selection();\r\n\r\ + \nif(!text) return;\r\nvar repl = '';\r\nvar lcase = text.toLowerCase();\r\ + \nvar ucase = text.toUpperCase();\r\n\r\nif(lcase == text) repl = ucase;\ + \ /* was lowercase */\r\nelse if(ucase != text) repl = lcase; /* neither\ + \ lower- or upper-case */\r\nelse { /* upper case -> title-case */\r\n\ + \ var words = $(lcase.split(/\\s/)).map(function() { \r\n if(this.length\ + \ > 0) { return this[0].toUpperCase() + this.slice(1); } else { return\ + \ ''}\r\n }); \r\n repl = words.join(' ');\r\n} \r\n\r\nif(repl !=\ + \ text) {\r\n cm.replaceSelection(repl);\r\n panel.fireEvent('contentChanged');\r\ + \n};"} + model: toolbar.scriptlet + pk: lowercase +- fields: {code: "$(params).each(function() {\n $.log(this[0], this[1]);\n \ + \ editor.callScriptlet(this[0], panel, this[1]);\n\n});"} + model: toolbar.scriptlet + pk: macro +- fields: {code: '-'} + model: toolbar.scriptlet + pk: slugify diff --git a/src/toolbar/locale/pl/LC_MESSAGES/django.mo b/src/toolbar/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 00000000..54dabdf9 Binary files /dev/null and b/src/toolbar/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/toolbar/locale/pl/LC_MESSAGES/django.po b/src/toolbar/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 00000000..8c7cfb16 --- /dev/null +++ b/src/toolbar/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,37 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-08-03 12:14+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: models.py:17 +msgid "button group" +msgstr "grupa przycisków" + +#: models.py:17 +msgid "button groups" +msgstr "grupy przycisków" + +#: models.py:51 +msgid "button" +msgstr "przycisk" + +#: models.py:51 +msgid "buttons" +msgstr "przyciski" + +#: models.py:75 +msgid "javascript" +msgstr "" diff --git a/src/toolbar/management/__init__.py b/src/toolbar/management/__init__.py new file mode 100644 index 00000000..792d6005 --- /dev/null +++ b/src/toolbar/management/__init__.py @@ -0,0 +1 @@ +# diff --git a/src/toolbar/management/commands/__init__.py b/src/toolbar/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/toolbar/management/commands/fixbuttons.py b/src/toolbar/management/commands/fixbuttons.py new file mode 100644 index 00000000..de48ceda --- /dev/null +++ b/src/toolbar/management/commands/fixbuttons.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# + +from django.core.management.base import NoArgsCommand +from toolbar.models import Button, ButtonGroup +import json +import re + + +class Command(NoArgsCommand): + + def handle_noargs(self, **options): + buttons = Button.objects.all() + print "Validating parameters... " + for b in buttons: + params = b.params + try: + v = json.loads(b.params) + except ValueError, e: + print 'Trying to fix button "%s" ...' % b.slug + if params[0] == u'(': + params = params[1:] + if params[-1] == u')': + params = params[:-1] + try: + v = son.loads(re.sub(u'([\\w-]+)\\s*:', u'"\\1": ', params).encode('utf-8')) + except ValueError, e: + print "Unable to fix '%s' " % b.params + print "Try to fix this button manually and rerun the script." + return False + + # resave + b.params = json.dumps(v) + b.save() + + print "Merge duplicate buttons (if any)..." + hash = {} + for b in buttons: + if b.slug not in hash: + hash[b.slug] = b + continue + + # duplicate button + print "Found duplicate of '%s'" % b.slug + a = hash[b.slug] + + remove_duplicate = True + if a.params != b.params: + print "Conflicting params for duplicate of '%s'." % b.slug + print "Groups will be joined, but won't remove duplicates." + remove_duplicate = False + + for g in b.group.all(): + a.group.add(g) + + b.group.clear() + + a.save() + if remove_duplicate: + b.delete() + + print "Searching for empty groups and orphaned buttons:" + for g in ButtonGroup.objects.all(): + if len(g.button_set.all()) == 0: + print "Empty group: '%s'" % g.slug + + for b in Button.objects.all(): + if len(b.group.all()) == 0: + print "orphan: '%s'" % b.slug diff --git a/src/toolbar/migrations/0001_initial.py b/src/toolbar/migrations/0001_initial.py new file mode 100644 index 00000000..5a6f27c6 --- /dev/null +++ b/src/toolbar/migrations/0001_initial.py @@ -0,0 +1,93 @@ +# 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 'ButtonGroup' + db.create_table('toolbar_buttongroup', ( + ('position', self.gf('django.db.models.fields.IntegerField')(default=0)), + ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=32)), + )) + db.send_create_signal('toolbar', ['ButtonGroup']) + + # Adding model 'Button' + db.create_table('toolbar_button', ( + ('key_mod', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, blank=True)), + ('scriptlet', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['toolbar.Scriptlet'], null=True, blank=True)), + ('tooltip', self.gf('django.db.models.fields.CharField')(max_length=120, blank=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('link', self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True)), + ('key', self.gf('django.db.models.fields.CharField')(max_length=1, blank=True)), + ('params', self.gf('django.db.models.fields.TextField')(default='[]')), + ('label', self.gf('django.db.models.fields.CharField')(max_length=32)), + ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=50, db_index=True)), + )) + db.send_create_signal('toolbar', ['Button']) + + # Adding M2M table for field group on 'Button' + db.create_table('toolbar_button_group', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('button', models.ForeignKey(orm['toolbar.button'], null=False)), + ('buttongroup', models.ForeignKey(orm['toolbar.buttongroup'], null=False)) + )) + db.create_unique('toolbar_button_group', ['button_id', 'buttongroup_id']) + + # Adding model 'Scriptlet' + db.create_table('toolbar_scriptlet', ( + ('code', self.gf('django.db.models.fields.TextField')()), + ('name', self.gf('django.db.models.fields.CharField')(max_length=64, primary_key=True)), + )) + db.send_create_signal('toolbar', ['Scriptlet']) + + + def backwards(self, orm): + + # Deleting model 'ButtonGroup' + db.delete_table('toolbar_buttongroup') + + # Deleting model 'Button' + db.delete_table('toolbar_button') + + # Removing M2M table for field group on 'Button' + db.delete_table('toolbar_button_group') + + # Deleting model 'Scriptlet' + db.delete_table('toolbar_scriptlet') + + + models = { + 'toolbar.button': { + 'Meta': {'object_name': 'Button'}, + 'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['toolbar.ButtonGroup']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}), + 'key_mod': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'blank': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'link': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), + 'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + 'scriptlet': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['toolbar.Scriptlet']", 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), + 'tooltip': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}) + }, + 'toolbar.buttongroup': { + 'Meta': {'object_name': 'ButtonGroup'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}) + }, + 'toolbar.scriptlet': { + 'Meta': {'object_name': 'Scriptlet'}, + 'code': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}) + } + } + + complete_apps = ['toolbar'] diff --git a/src/toolbar/migrations/0002_auto__del_field_button_key_mod__chg_field_button_key.py b/src/toolbar/migrations/0002_auto__del_field_button_key_mod__chg_field_button_key.py new file mode 100644 index 00000000..af58c6ec --- /dev/null +++ b/src/toolbar/migrations/0002_auto__del_field_button_key_mod__chg_field_button_key.py @@ -0,0 +1,52 @@ +# 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): + + # Deleting field 'Button.key_mod' + db.delete_column('toolbar_button', 'key_mod') + + # Changing field 'Button.key' + db.alter_column('toolbar_button', 'key', self.gf('django.db.models.fields.CharField')(max_length=1, null=True)) + + def backwards(self, orm): + + # Adding field 'Button.key_mod' + db.add_column('toolbar_button', 'key_mod', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, blank=True), keep_default=False) + + # Changing field 'Button.key' + db.alter_column('toolbar_button', 'key', self.gf('django.db.models.fields.CharField')(max_length=1, blank=True)) + + models = { + 'toolbar.button': { + 'Meta': {'object_name': 'Button'}, + 'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['toolbar.ButtonGroup']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'link': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), + 'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + 'scriptlet': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['toolbar.Scriptlet']", 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), + 'tooltip': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}) + }, + 'toolbar.buttongroup': { + 'Meta': {'object_name': 'ButtonGroup'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}) + }, + 'toolbar.scriptlet': { + 'Meta': {'object_name': 'Scriptlet'}, + 'code': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}) + } + } + + complete_apps = ['toolbar'] diff --git a/src/toolbar/migrations/0003_button_key_rename_to_accesskey.py b/src/toolbar/migrations/0003_button_key_rename_to_accesskey.py new file mode 100644 index 00000000..7a0961d9 --- /dev/null +++ b/src/toolbar/migrations/0003_button_key_rename_to_accesskey.py @@ -0,0 +1,47 @@ +# 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): + + # Deleting field 'Button.key' + db.rename_column('toolbar_button', 'key', 'accesskey') + + + + def backwards(self, orm): + db.rename_column('toolbar_button', 'accesskey', 'key') + + + models = { + 'toolbar.button': { + 'Meta': {'object_name': 'Button'}, + 'accesskey': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True'}), + 'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['toolbar.ButtonGroup']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'link': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), + 'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + 'scriptlet': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['toolbar.Scriptlet']", 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), + 'tooltip': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}) + }, + 'toolbar.buttongroup': { + 'Meta': {'object_name': 'ButtonGroup'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}) + }, + 'toolbar.scriptlet': { + 'Meta': {'object_name': 'Scriptlet'}, + 'code': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}) + } + } + + complete_apps = ['toolbar'] diff --git a/src/toolbar/migrations/0004_auto__chg_field_button_accesskey.py b/src/toolbar/migrations/0004_auto__chg_field_button_accesskey.py new file mode 100644 index 00000000..5f28bf55 --- /dev/null +++ b/src/toolbar/migrations/0004_auto__chg_field_button_accesskey.py @@ -0,0 +1,48 @@ +# 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): + + # Changing field 'Button.accesskey' + db.alter_column('toolbar_button', 'accesskey', self.gf('django.db.models.fields.CharField')(max_length=1)) + + + def backwards(self, orm): + + # Changing field 'Button.accesskey' + db.alter_column('toolbar_button', 'accesskey', self.gf('django.db.models.fields.CharField')(max_length=1, null=True)) + + + models = { + 'toolbar.button': { + 'Meta': {'ordering': "('slug',)", 'object_name': 'Button'}, + 'accesskey': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['toolbar.ButtonGroup']", 'symmetrical': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'link': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), + 'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + 'scriptlet': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['toolbar.Scriptlet']", 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), + 'tooltip': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}) + }, + 'toolbar.buttongroup': { + 'Meta': {'ordering': "('position', 'name')", 'object_name': 'ButtonGroup'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}) + }, + 'toolbar.scriptlet': { + 'Meta': {'object_name': 'Scriptlet'}, + 'code': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}) + } + } + + complete_apps = ['toolbar'] diff --git a/src/toolbar/migrations/0005_initial_data.py b/src/toolbar/migrations/0005_initial_data.py new file mode 100644 index 00000000..b31f3809 --- /dev/null +++ b/src/toolbar/migrations/0005_initial_data.py @@ -0,0 +1,46 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + from django.core.management import call_command + call_command("loaddata", "initial_toolbar.yaml") + + + def backwards(self, orm): + "Write your backwards methods here." + pass + + + models = { + 'toolbar.button': { + 'Meta': {'ordering': "('slug',)", 'object_name': 'Button'}, + 'accesskey': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['toolbar.ButtonGroup']", 'symmetrical': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'link': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), + 'params': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + 'scriptlet': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['toolbar.Scriptlet']", 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), + 'tooltip': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}) + }, + 'toolbar.buttongroup': { + 'Meta': {'ordering': "('position', 'name')", 'object_name': 'ButtonGroup'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}) + }, + 'toolbar.scriptlet': { + 'Meta': {'object_name': 'Scriptlet'}, + 'code': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}) + } + } + + complete_apps = ['toolbar'] diff --git a/src/toolbar/migrations/__init__.py b/src/toolbar/migrations/__init__.py new file mode 100644 index 00000000..9012566c --- /dev/null +++ b/src/toolbar/migrations/__init__.py @@ -0,0 +1 @@ +# pragma: no cover diff --git a/src/toolbar/models.py b/src/toolbar/models.py new file mode 100644 index 00000000..a23e3463 --- /dev/null +++ b/src/toolbar/models.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class ButtonGroup(models.Model): + name = models.CharField(max_length=32) + slug = models.SlugField() + position = models.IntegerField(default=0) + + class Meta: + ordering = ('position', 'name',) + verbose_name, verbose_name_plural = _('button group'), _('button groups') + + def __unicode__(self): + return self.name + + def to_dict(self, with_buttons=False): + d = {'name': self.name, 'position': self.position} + + if with_buttons: + d['buttons'] = [b.to_dict() for b in self.button_set.all()] + + return d + + +class Button(models.Model): + label = models.CharField(max_length=32) + slug = models.SlugField(unique=True) # unused + + # behaviour + params = models.TextField(default='[]') # TODO: should be a JSON field + scriptlet = models.ForeignKey('Scriptlet', null=True, blank=True) + link = models.CharField(max_length=256, blank=True, default='') + + # ui related stuff + accesskey = models.CharField(blank=True, max_length=1) + + tooltip = models.CharField(blank=True, max_length=120) + + # Why the button is restricted to have the same position in each group ? + # position = models.IntegerField(default=0) + group = models.ManyToManyField(ButtonGroup) + + class Meta: + ordering = ('slug',) + verbose_name, verbose_name_plural = _('button'), _('buttons') + + @property + def full_tooltip(self): + return u"%s %s" % (self.tooltip, "[Alt+%s]" % self.accesskey if self.accesskey else "") + + def to_dict(self): + return { + 'label': self.label, + 'tooltip': self.tooltip, + 'accesskey': self.accesskey, + 'params': self.params, + 'scriptlet_id': self.scriptlet_id, + } + + def __unicode__(self): + return self.label + + +class Scriptlet(models.Model): + name = models.CharField(max_length=64, primary_key=True) + code = models.TextField() + + def __unicode__(self): + return _(u'javascript') + u':' + self.name diff --git a/src/toolbar/templates/toolbar/button.html b/src/toolbar/templates/toolbar/button.html new file mode 100644 index 00000000..cdffcaf1 --- /dev/null +++ b/src/toolbar/templates/toolbar/button.html @@ -0,0 +1,13 @@ +{% if button.link %} + +{% endif %} + +{% if button.link %} + +{% endif %} \ No newline at end of file diff --git a/src/toolbar/templates/toolbar/toolbar.html b/src/toolbar/templates/toolbar/toolbar.html new file mode 100644 index 00000000..b9624d69 --- /dev/null +++ b/src/toolbar/templates/toolbar/toolbar.html @@ -0,0 +1,21 @@ +{% load toolbar_tags %} +
    + + + +
    + {% for group in toolbar_groups %} +
      + {# buttons for this group #} + {% for button in group.button_set.all %} +
    • {% toolbar_button button %}
    • + {% endfor %} +
    + {% endfor %} +
    + +
    diff --git a/src/toolbar/templatetags/__init__.py b/src/toolbar/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/toolbar/templatetags/toolbar_tags.py b/src/toolbar/templatetags/toolbar_tags.py new file mode 100644 index 00000000..0766677e --- /dev/null +++ b/src/toolbar/templatetags/toolbar_tags.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django import template +from toolbar import models + +register = template.Library() + + +@register.inclusion_tag('toolbar/toolbar.html') +def toolbar(): + return {'toolbar_groups': models.ButtonGroup.objects.all().select_related()} + + +@register.inclusion_tag('toolbar/button.html') +def toolbar_button(b): + return {'button': b} diff --git a/src/wiki/__init__.py b/src/wiki/__init__.py new file mode 100644 index 00000000..c53f0e73 --- /dev/null +++ b/src/wiki/__init__.py @@ -0,0 +1 @@ + # pragma: no cover diff --git a/src/wiki/admin.py b/src/wiki/admin.py new file mode 100644 index 00000000..ae309a9d --- /dev/null +++ b/src/wiki/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from wiki import models + + +class ThemeAdmin(admin.ModelAdmin): + search_fields = ['name'] + +admin.site.register(models.Theme, ThemeAdmin) diff --git a/src/wiki/fixtures/initial_themes.yaml b/src/wiki/fixtures/initial_themes.yaml new file mode 100644 index 00000000..fc0b773a --- /dev/null +++ b/src/wiki/fixtures/initial_themes.yaml @@ -0,0 +1,1246 @@ +- fields: {name: Alkohol} + model: wiki.theme + pk: 1 +- fields: {name: Ambicja} + model: wiki.theme + pk: 2 +- fields: {name: "Anio\u0142"} + model: wiki.theme + pk: 3 +- fields: {name: Antysemityzm} + model: wiki.theme + pk: 4 +- fields: {name: Arkadia} + model: wiki.theme + pk: 5 +- fields: {name: Artysta} + model: wiki.theme + pk: 6 +- fields: {name: "Bezdomno\u015B\u0107"} + model: wiki.theme + pk: 7 +- fields: {name: "Bezpiecze\u0144stwo"} + model: wiki.theme + pk: 8 +- fields: {name: Bieda} + model: wiki.theme + pk: 9 +- fields: {name: Bijatyka} + model: wiki.theme + pk: 10 +- fields: {name: Bogactwo} + model: wiki.theme + pk: 14 +- fields: {name: Brat} + model: wiki.theme + pk: 16 +- fields: {name: Bunt} + model: wiki.theme + pk: 17 +- fields: {name: Buntownik} + model: wiki.theme + pk: 18 +- fields: {name: Burza} + model: wiki.theme + pk: 19 +- fields: {name: "B\xF3g"} + model: wiki.theme + pk: 15 +- fields: {name: "B\u0142azen"} + model: wiki.theme + pk: 11 +- fields: {name: "B\u0142oto"} + model: wiki.theme + pk: 13 +- fields: {name: "B\u0142\u0105dzenie"} + model: wiki.theme + pk: 12 +- fields: {name: Car} + model: wiki.theme + pk: 20 +- fields: {name: Carpe diem} + model: wiki.theme + pk: 21 +- fields: {name: "Chciwo\u015B\u0107"} + model: wiki.theme + pk: 25 +- fields: {name: Chleb} + model: wiki.theme + pk: 26 +- fields: {name: Choroba} + model: wiki.theme + pk: 28 +- fields: {name: Chrystus} + model: wiki.theme + pk: 29 +- fields: {name: Chrzest} + model: wiki.theme + pk: 30 +- fields: {name: "Ch\u0142op"} + model: wiki.theme + pk: 27 +- fields: {name: "Cia\u0142o"} + model: wiki.theme + pk: 31 +- fields: {name: "Ciemno\u015B\u0107"} + model: wiki.theme + pk: 22 +- fields: {name: Cierpienie} + model: wiki.theme + pk: 32 +- fields: {name: "Cie\u0144"} + model: wiki.theme + pk: 23 +- fields: {name: Cisza} + model: wiki.theme + pk: 24 +- fields: {name: Cmentarz} + model: wiki.theme + pk: 33 +- fields: {name: Cnota} + model: wiki.theme + pk: 34 +- fields: {name: Cud} + model: wiki.theme + pk: 36 +- fields: {name: Czarownica} + model: wiki.theme + pk: 37 +- fields: {name: Czary} + model: wiki.theme + pk: 38 +- fields: {name: Czas} + model: wiki.theme + pk: 39 +- fields: {name: Czyn} + model: wiki.theme + pk: 40 +- fields: {name: "Czy\u015Bciec"} + model: wiki.theme + pk: 41 +- fields: {name: "C\xF3rka"} + model: wiki.theme + pk: 35 +- fields: {name: Dama} + model: wiki.theme + pk: 42 +- fields: {name: Danse macabre} + model: wiki.theme + pk: 43 +- fields: {name: Deszcz} + model: wiki.theme + pk: 44 +- fields: {name: "Diabe\u0142"} + model: wiki.theme + pk: 45 +- fields: {name: Dobro} + model: wiki.theme + pk: 46 +- fields: {name: Dom} + model: wiki.theme + pk: 47 +- fields: {name: "Doros\u0142o\u015B\u0107"} + model: wiki.theme + pk: 48 +- fields: {name: Drzewo} + model: wiki.theme + pk: 49 +- fields: {name: Duch} + model: wiki.theme + pk: 50 +- fields: {name: Duma} + model: wiki.theme + pk: 52 +- fields: {name: Dusza} + model: wiki.theme + pk: 51 +- fields: {name: Dworek} + model: wiki.theme + pk: 53 +- fields: {name: Dworzanin} + model: wiki.theme + pk: 54 +- fields: {name: "Dw\xF3r"} + model: wiki.theme + pk: 55 +- fields: {name: "Dzieci\u0144stwo"} + model: wiki.theme + pk: 56 +- fields: {name: Dziecko} + model: wiki.theme + pk: 57 +- fields: {name: Dziedzictwo} + model: wiki.theme + pk: 58 +- fields: {name: Dziewictwo} + model: wiki.theme + pk: 59 +- fields: {name: "D\u017Awi\u0119k"} + model: wiki.theme + pk: 60 +- fields: {name: Egzorcyzm} + model: wiki.theme + pk: 61 +- fields: {name: Elita} + model: wiki.theme + pk: 62 +- fields: {name: Emigrant} + model: wiki.theme + pk: 63 +- fields: {name: "Fa\u0142sz"} + model: wiki.theme + pk: 64 +- fields: {name: Filozof} + model: wiki.theme + pk: 65 +- fields: {name: Fircyk} + model: wiki.theme + pk: 66 +- fields: {name: Flirt} + model: wiki.theme + pk: 67 +- fields: {name: Gospodarz} + model: wiki.theme + pk: 71 +- fields: {name: Gospodyni} + model: wiki.theme + pk: 72 +- fields: {name: Gotycyzm} + model: wiki.theme + pk: 74 +- fields: {name: "Go\u015B\u0107"} + model: wiki.theme + pk: 73 +- fields: {name: Gra} + model: wiki.theme + pk: 76 +- fields: {name: Grzech} + model: wiki.theme + pk: 78 +- fields: {name: "Grzeczno\u015B\u0107"} + model: wiki.theme + pk: 79 +- fields: {name: "Gr\xF3b"} + model: wiki.theme + pk: 77 +- fields: {name: Gwiazda} + model: wiki.theme + pk: 80 +- fields: {name: "G\xF3ra"} + model: wiki.theme + pk: 75 +- fields: {name: "G\u0142upiec"} + model: wiki.theme + pk: 68 +- fields: {name: "G\u0142upota"} + model: wiki.theme + pk: 69 +- fields: {name: "G\u0142\xF3d"} + model: wiki.theme + pk: 70 +- fields: {name: Handel} + model: wiki.theme + pk: 81 +- fields: {name: "Ha\u0144ba"} + model: wiki.theme + pk: 82 +- fields: {name: Historia} + model: wiki.theme + pk: 83 +- fields: {name: Honor} + model: wiki.theme + pk: 84 +- fields: {name: Idealista} + model: wiki.theme + pk: 85 +- fields: {name: "Imi\u0119"} + model: wiki.theme + pk: 86 +- fields: {name: Interes} + model: wiki.theme + pk: 87 +- fields: {name: "Jab\u0142ka"} + model: wiki.theme + pk: 88 +- fields: {name: Jedzenie} + model: wiki.theme + pk: 89 +- fields: {name: "Jesie\u0144"} + model: wiki.theme + pk: 90 +- fields: {name: Kaleka} + model: wiki.theme + pk: 91 +- fields: {name: Kara} + model: wiki.theme + pk: 92 +- fields: {name: Karczma} + model: wiki.theme + pk: 93 +- fields: {name: "Kl\u0119ska"} + model: wiki.theme + pk: 94 +- fields: {name: Kobieta} + model: wiki.theme + pk: 97 +- fields: {name: "Kobieta \"upad\u0142a\""} + model: wiki.theme + pk: 99 +- fields: {name: Kobieta demoniczna} + model: wiki.theme + pk: 98 +- fields: {name: Kochanek} + model: wiki.theme + pk: 100 +- fields: {name: Kochanek romantyczny} + model: wiki.theme + pk: 101 +- fields: {name: Kolonializm} + model: wiki.theme + pk: 102 +- fields: {name: Kondycja ludzka} + model: wiki.theme + pk: 103 +- fields: {name: Konflikt} + model: wiki.theme + pk: 104 +- fields: {name: "Konflikt wewn\u0119trzny"} + model: wiki.theme + pk: 105 +- fields: {name: "Koniec \u015Bwiata"} + model: wiki.theme + pk: 106 +- fields: {name: "Korzy\u015B\u0107"} + model: wiki.theme + pk: 108 +- fields: {name: Kot} + model: wiki.theme + pk: 109 +- fields: {name: "Ko\u0144"} + model: wiki.theme + pk: 107 +- fields: {name: "Kradzie\u017C"} + model: wiki.theme + pk: 110 +- fields: {name: Krew} + model: wiki.theme + pk: 111 +- fields: {name: Krzywda} + model: wiki.theme + pk: 113 +- fields: {name: "Kr\xF3l"} + model: wiki.theme + pk: 112 +- fields: {name: "Ksi\u0105dz"} + model: wiki.theme + pk: 114 +- fields: {name: "Ksi\u0105\u017Cka"} + model: wiki.theme + pk: 115 +- fields: {name: "Ksi\u0119\u017Cyc"} + model: wiki.theme + pk: 116 +- fields: {name: Kuchnia} + model: wiki.theme + pk: 117 +- fields: {name: Kuszenie} + model: wiki.theme + pk: 118 +- fields: {name: Kwiaty} + model: wiki.theme + pk: 119 +- fields: {name: "K\u0142amstwo"} + model: wiki.theme + pk: 95 +- fields: {name: "K\u0142\xF3tnia"} + model: wiki.theme + pk: 96 +- fields: {name: Labirynt} + model: wiki.theme + pk: 120 +- fields: {name: Las} + model: wiki.theme + pk: 121 +- fields: {name: Lato} + model: wiki.theme + pk: 122 +- fields: {name: Lekarz} + model: wiki.theme + pk: 123 +- fields: {name: Lenistwo} + model: wiki.theme + pk: 124 +- fields: {name: Liberat} + model: wiki.theme + pk: 126 +- fields: {name: List} + model: wiki.theme + pk: 125 +- fields: {name: Los} + model: wiki.theme + pk: 127 +- fields: {name: Lud} + model: wiki.theme + pk: 128 +- fields: {name: Lustro} + model: wiki.theme + pk: 129 +- fields: {name: Marzenie} + model: wiki.theme + pk: 132 +- fields: {name: Maska} + model: wiki.theme + pk: 133 +- fields: {name: Maszyna} + model: wiki.theme + pk: 134 +- fields: {name: Matka} + model: wiki.theme + pk: 135 +- fields: {name: Matka Boska} + model: wiki.theme + pk: 136 +- fields: {name: "Ma\u0142\u017Ce\u0144stwo"} + model: wiki.theme + pk: 131 +- fields: {name: Melancholia} + model: wiki.theme + pk: 139 +- fields: {name: Miasto} + model: wiki.theme + pk: 142 +- fields: {name: Mieszczanin} + model: wiki.theme + pk: 143 +- fields: {name: Mizoginia} + model: wiki.theme + pk: 152 +- fields: {name: "Mi\u0142osierdzie"} + model: wiki.theme + pk: 144 +- fields: {name: "Mi\u0142o\u015B\u0107"} + model: wiki.theme + pk: 145 +- fields: {name: "Mi\u0142o\u015B\u0107 niespe\u0142niona"} + model: wiki.theme + pk: 146 +- fields: {name: "Mi\u0142o\u015B\u0107 platoniczna"} + model: wiki.theme + pk: 147 +- fields: {name: "Mi\u0142o\u015B\u0107 romantyczna"} + model: wiki.theme + pk: 148 +- fields: {name: "Mi\u0142o\u015B\u0107 silniejsza ni\u017C \u015Bmier\u0107"} + model: wiki.theme + pk: 149 +- fields: {name: "Mi\u0142o\u015B\u0107 spe\u0142niona"} + model: wiki.theme + pk: 150 +- fields: {name: "Mi\u0142o\u015B\u0107 tragiczna"} + model: wiki.theme + pk: 151 +- fields: {name: Moda} + model: wiki.theme + pk: 154 +- fields: {name: Modlitwa} + model: wiki.theme + pk: 155 +- fields: {name: Morderstwo} + model: wiki.theme + pk: 156 +- fields: {name: Morze} + model: wiki.theme + pk: 157 +- fields: {name: Motyl} + model: wiki.theme + pk: 158 +- fields: {name: Mucha} + model: wiki.theme + pk: 159 +- fields: {name: Muzyka} + model: wiki.theme + pk: 160 +- fields: {name: "M\u0105dro\u015B\u0107"} + model: wiki.theme + pk: 137 +- fields: {name: "M\u0105\u017C"} + model: wiki.theme + pk: 138 +- fields: {name: "M\u0119drzec"} + model: wiki.theme + pk: 140 +- fields: {name: "M\u0119\u017Cczyzna"} + model: wiki.theme + pk: 141 +- fields: {name: "M\u0142odo\u015B\u0107"} + model: wiki.theme + pk: 153 +- fields: {name: Narodziny} + model: wiki.theme + pk: 161 +- fields: {name: "Nar\xF3d"} + model: wiki.theme + pk: 162 +- fields: {name: Natura} + model: wiki.theme + pk: 163 +- fields: {name: Nauczyciel} + model: wiki.theme + pk: 164 +- fields: {name: Nauczycielka} + model: wiki.theme + pk: 165 +- fields: {name: Nauka} + model: wiki.theme + pk: 166 +- fields: {name: "Niebezpiecze\u0144stwo"} + model: wiki.theme + pk: 167 +- fields: {name: Niedziela} + model: wiki.theme + pk: 168 +- fields: {name: Niemiec} + model: wiki.theme + pk: 169 +- fields: {name: "Nienawi\u015B\u0107"} + model: wiki.theme + pk: 170 +- fields: {name: Niewola} + model: wiki.theme + pk: 172 +- fields: {name: "Nie\u015Bmiertelno\u015B\u0107"} + model: wiki.theme + pk: 171 +- fields: {name: Noc} + model: wiki.theme + pk: 173 +- fields: {name: Nuda} + model: wiki.theme + pk: 174 +- fields: {name: Obcy} + model: wiki.theme + pk: 175 +- fields: {name: "Obowi\u0105zek"} + model: wiki.theme + pk: 177 +- fields: {name: "Obraz \u015Bwiata"} + model: wiki.theme + pk: 178 +- fields: {name: "Obrz\u0119dy"} + model: wiki.theme + pk: 179 +- fields: {name: Obyczaje} + model: wiki.theme + pk: 180 +- fields: {name: Obywatel} + model: wiki.theme + pk: 181 +- fields: {name: "Ob\u0142ok"} + model: wiki.theme + pk: 176 +- fields: {name: "Odrodzenie przez gr\xF3b"} + model: wiki.theme + pk: 182 +- fields: {name: Odwaga} + model: wiki.theme + pk: 183 +- fields: {name: Ofiara} + model: wiki.theme + pk: 184 +- fields: {name: "Ogie\u0144"} + model: wiki.theme + pk: 185 +- fields: {name: "Ogr\xF3d"} + model: wiki.theme + pk: 186 +- fields: {name: Ojciec} + model: wiki.theme + pk: 187 +- fields: {name: Ojczyzna} + model: wiki.theme + pk: 188 +- fields: {name: Oko} + model: wiki.theme + pk: 189 +- fields: {name: "Okrucie\u0144stwo"} + model: wiki.theme + pk: 191 +- fields: {name: "Okr\u0119t"} + model: wiki.theme + pk: 190 +- fields: {name: Omen} + model: wiki.theme + pk: 192 +- fields: {name: Opieka} + model: wiki.theme + pk: 193 +- fields: {name: Organizm} + model: wiki.theme + pk: 194 +- fields: {name: "Otch\u0142a\u0144"} + model: wiki.theme + pk: 195 +- fields: {name: "O\u015Bwiadczyny"} + model: wiki.theme + pk: 415 +- fields: {name: "Paj\u0105k"} + model: wiki.theme + pk: 196 +- fields: {name: "Pami\u0119\u0107"} + model: wiki.theme + pk: 197 +- fields: {name: Pan} + model: wiki.theme + pk: 198 +- fields: {name: "Panna m\u0142oda"} + model: wiki.theme + pk: 199 +- fields: {name: Patriota} + model: wiki.theme + pk: 201 +- fields: {name: "Pa\u0144stwo"} + model: wiki.theme + pk: 200 +- fields: {name: "Piek\u0142o"} + model: wiki.theme + pk: 202 +- fields: {name: Pielgrzym} + model: wiki.theme + pk: 203 +- fields: {name: "Pieni\u0105dz"} + model: wiki.theme + pk: 204 +- fields: {name: Pies} + model: wiki.theme + pk: 205 +- fields: {name: "Pija\u0144stwo"} + model: wiki.theme + pk: 207 +- fields: {name: Piwnica} + model: wiki.theme + pk: 208 +- fields: {name: "Pi\u0119tno"} + model: wiki.theme + pk: 206 +- fields: {name: Plotka} + model: wiki.theme + pk: 209 +- fields: {name: "Pobo\u017Cno\u015B\u0107"} + model: wiki.theme + pk: 210 +- fields: {name: "Poca\u0142unek"} + model: wiki.theme + pk: 211 +- fields: {name: Pochlebstwo} + model: wiki.theme + pk: 212 +- fields: {name: "Podr\xF3\u017C"} + model: wiki.theme + pk: 216 +- fields: {name: "Podst\u0119p"} + model: wiki.theme + pk: 217 +- fields: {name: Poeta} + model: wiki.theme + pk: 213 +- fields: {name: Poetka} + model: wiki.theme + pk: 214 +- fields: {name: Poezja} + model: wiki.theme + pk: 215 +- fields: {name: Pogrzeb} + model: wiki.theme + pk: 218 +- fields: {name: Pojedynek} + model: wiki.theme + pk: 219 +- fields: {name: Pokora} + model: wiki.theme + pk: 220 +- fields: {name: Pokusa} + model: wiki.theme + pk: 221 +- fields: {name: Polak} + model: wiki.theme + pk: 222 +- fields: {name: Polityka} + model: wiki.theme + pk: 223 +- fields: {name: Polowanie} + model: wiki.theme + pk: 224 +- fields: {name: Polska} + model: wiki.theme + pk: 225 +- fields: {name: Portret} + model: wiki.theme + pk: 226 +- fields: {name: Porwanie} + model: wiki.theme + pk: 227 +- fields: {name: "Potw\xF3r"} + model: wiki.theme + pk: 229 +- fields: {name: Powstanie} + model: wiki.theme + pk: 230 +- fields: {name: Powstaniec} + model: wiki.theme + pk: 231 +- fields: {name: Pozory} + model: wiki.theme + pk: 232 +- fields: {name: "Pozycja spo\u0142eczna"} + model: wiki.theme + pk: 233 +- fields: {name: "Po\u015Bwi\u0119cenie"} + model: wiki.theme + pk: 228 +- fields: {name: "Po\u017Car"} + model: wiki.theme + pk: 234 +- fields: {name: "Po\u017C\u0105danie"} + model: wiki.theme + pk: 235 +- fields: {name: Praca} + model: wiki.theme + pk: 236 +- fields: {name: Praca organiczna} + model: wiki.theme + pk: 238 +- fields: {name: Praca u podstaw} + model: wiki.theme + pk: 237 +- fields: {name: Prawda} + model: wiki.theme + pk: 239 +- fields: {name: Prawnik} + model: wiki.theme + pk: 240 +- fields: {name: Prometeusz} + model: wiki.theme + pk: 241 +- fields: {name: Proroctwo} + model: wiki.theme + pk: 242 +- fields: {name: Prorok} + model: wiki.theme + pk: 243 +- fields: {name: Przebranie} + model: wiki.theme + pk: 245 +- fields: {name: Przeczucie} + model: wiki.theme + pk: 246 +- fields: {name: "Przedmurze chrze\u015Bcija\u0144stwa"} + model: wiki.theme + pk: 247 +- fields: {name: "Przekle\u0144stwo"} + model: wiki.theme + pk: 248 +- fields: {name: Przekupstwo} + model: wiki.theme + pk: 249 +- fields: {name: Przemiana} + model: wiki.theme + pk: 250 +- fields: {name: Przemijanie} + model: wiki.theme + pk: 251 +- fields: {name: Przemoc} + model: wiki.theme + pk: 252 +- fields: {name: "Przestrze\u0144"} + model: wiki.theme + pk: 253 +- fields: {name: "Przyja\u017A\u0144"} + model: wiki.theme + pk: 254 +- fields: {name: "Przyroda nieo\u017Cywiona"} + model: wiki.theme + pk: 255 +- fields: {name: "Przysi\u0119ga"} + model: wiki.theme + pk: 256 +- fields: {name: "Przyw\xF3dca"} + model: wiki.theme + pk: 257 +- fields: {name: "Pr\xF3\u017Cno\u015B\u0107"} + model: wiki.theme + pk: 244 +- fields: {name: Ptak} + model: wiki.theme + pk: 258 +- fields: {name: Pustynia} + model: wiki.theme + pk: 259 +- fields: {name: Pycha} + model: wiki.theme + pk: 260 +- fields: {name: Raj} + model: wiki.theme + pk: 261 +- fields: {name: Realista} + model: wiki.theme + pk: 262 +- fields: {name: Religia} + model: wiki.theme + pk: 263 +- fields: {name: Rewolucja} + model: wiki.theme + pk: 264 +- fields: {name: Robak} + model: wiki.theme + pk: 265 +- fields: {name: Robotnik} + model: wiki.theme + pk: 266 +- fields: {name: Rodzina} + model: wiki.theme + pk: 267 +- fields: {name: Rosja} + model: wiki.theme + pk: 268 +- fields: {name: Rosjanin} + model: wiki.theme + pk: 269 +- fields: {name: Rozczarowanie} + model: wiki.theme + pk: 271 +- fields: {name: Rozpacz} + model: wiki.theme + pk: 272 +- fields: {name: Rozstanie} + model: wiki.theme + pk: 273 +- fields: {name: Rozum} + model: wiki.theme + pk: 274 +- fields: {name: "Ro\u015Bliny"} + model: wiki.theme + pk: 270 +- fields: {name: Ruiny} + model: wiki.theme + pk: 275 +- fields: {name: Rycerz} + model: wiki.theme + pk: 276 +- fields: {name: Rzeka} + model: wiki.theme + pk: 277 +- fields: {name: Salon} + model: wiki.theme + pk: 278 +- fields: {name: "Samob\xF3jstwo"} + model: wiki.theme + pk: 279 +- fields: {name: Samolubstwo} + model: wiki.theme + pk: 280 +- fields: {name: Samotnik} + model: wiki.theme + pk: 281 +- fields: {name: "Samotno\u015B\u0107"} + model: wiki.theme + pk: 282 +- fields: {name: Sarmata} + model: wiki.theme + pk: 283 +- fields: {name: Sen} + model: wiki.theme + pk: 287 +- fields: {name: Serce} + model: wiki.theme + pk: 288 +- fields: {name: Sielanka} + model: wiki.theme + pk: 290 +- fields: {name: Sierota} + model: wiki.theme + pk: 291 +- fields: {name: Siostra} + model: wiki.theme + pk: 293 +- fields: {name: "Si\u0142a"} + model: wiki.theme + pk: 292 +- fields: {name: "Sk\u0105piec"} + model: wiki.theme + pk: 299 +- fields: {name: "Sobowt\xF3r"} + model: wiki.theme + pk: 300 +- fields: {name: "Spowied\u017A"} + model: wiki.theme + pk: 302 +- fields: {name: "Spo\u0142ecznik"} + model: wiki.theme + pk: 301 +- fields: {name: "Sprawiedliwo\u015B\u0107"} + model: wiki.theme + pk: 303 +- fields: {name: "Staro\u015B\u0107"} + model: wiki.theme + pk: 304 +- fields: {name: Strach} + model: wiki.theme + pk: 305 +- fields: {name: "Str\xF3j"} + model: wiki.theme + pk: 306 +- fields: {name: Stworzenie} + model: wiki.theme + pk: 307 +- fields: {name: Sumienie} + model: wiki.theme + pk: 308 +- fields: {name: Swaty} + model: wiki.theme + pk: 309 +- fields: {name: Syberia} + model: wiki.theme + pk: 310 +- fields: {name: Syn} + model: wiki.theme + pk: 311 +- fields: {name: Syn marnotrawny} + model: wiki.theme + pk: 312 +- fields: {name: Syzyf} + model: wiki.theme + pk: 313 +- fields: {name: Szaleniec} + model: wiki.theme + pk: 314 +- fields: {name: "Szale\u0144stwo"} + model: wiki.theme + pk: 315 +- fields: {name: "Szanta\u017C"} + model: wiki.theme + pk: 316 +- fields: {name: Szatan} + model: wiki.theme + pk: 317 +- fields: {name: "Szcz\u0119\u015Bcie"} + model: wiki.theme + pk: 318 +- fields: {name: "Szko\u0142a"} + model: wiki.theme + pk: 319 +- fields: {name: Szlachcic} + model: wiki.theme + pk: 320 +- fields: {name: Szpieg} + model: wiki.theme + pk: 321 +- fields: {name: Sztuka} + model: wiki.theme + pk: 322 +- fields: {name: "S\u0105d"} + model: wiki.theme + pk: 285 +- fields: {name: "S\u0105d Ostateczny"} + model: wiki.theme + pk: 286 +- fields: {name: "S\u0105siad"} + model: wiki.theme + pk: 284 +- fields: {name: "S\u0119dzia"} + model: wiki.theme + pk: 289 +- fields: {name: "S\u0142awa"} + model: wiki.theme + pk: 294 +- fields: {name: "S\u0142owo"} + model: wiki.theme + pk: 296 +- fields: {name: "S\u0142o\u0144ce"} + model: wiki.theme + pk: 295 +- fields: {name: "S\u0142uga"} + model: wiki.theme + pk: 297 +- fields: {name: "S\u0142u\u017Calczo\u015B\u0107"} + model: wiki.theme + pk: 298 +- fields: {name: Tajemnica} + model: wiki.theme + pk: 332 +- fields: {name: Taniec} + model: wiki.theme + pk: 333 +- fields: {name: "Tch\xF3rzostwo"} + model: wiki.theme + pk: 334 +- fields: {name: Teatr} + model: wiki.theme + pk: 335 +- fields: {name: Testament} + model: wiki.theme + pk: 336 +- fields: {name: Theatrum mundi} + model: wiki.theme + pk: 338 +- fields: {name: Trucizna} + model: wiki.theme + pk: 340 +- fields: {name: Trup} + model: wiki.theme + pk: 341 +- fields: {name: "Tw\xF3rczo\u015B\u0107"} + model: wiki.theme + pk: 342 +- fields: {name: "T\u0119sknota"} + model: wiki.theme + pk: 337 +- fields: {name: "T\u0142um"} + model: wiki.theme + pk: 339 +- fields: {name: "Ucze\u0144"} + model: wiki.theme + pk: 343 +- fields: {name: Uczta} + model: wiki.theme + pk: 344 +- fields: {name: Umiarkowanie} + model: wiki.theme + pk: 346 +- fields: {name: Upadek} + model: wiki.theme + pk: 347 +- fields: {name: "Upi\xF3r"} + model: wiki.theme + pk: 348 +- fields: {name: Uroda} + model: wiki.theme + pk: 345 +- fields: {name: "Urz\u0119dnik"} + model: wiki.theme + pk: 349 +- fields: {name: Vanitas} + model: wiki.theme + pk: 350 +- fields: {name: Walka} + model: wiki.theme + pk: 351 +- fields: {name: Walka klas} + model: wiki.theme + pk: 352 +- fields: {name: Wampir} + model: wiki.theme + pk: 353 +- fields: {name: Warszawa} + model: wiki.theme + pk: 354 +- fields: {name: Wdowa} + model: wiki.theme + pk: 356 +- fields: {name: Wdowiec} + model: wiki.theme + pk: 357 +- fields: {name: Wesele} + model: wiki.theme + pk: 358 +- fields: {name: Wiatr} + model: wiki.theme + pk: 359 +- fields: {name: Wiedza} + model: wiki.theme + pk: 363 +- fields: {name: "Wierno\u015B\u0107"} + model: wiki.theme + pk: 360 +- fields: {name: Wierzenia} + model: wiki.theme + pk: 361 +- fields: {name: "Wie\u015B"} + model: wiki.theme + pk: 362 +- fields: {name: "Wie\u017Ca Babel"} + model: wiki.theme + pk: 364 +- fields: {name: Wina} + model: wiki.theme + pk: 367 +- fields: {name: Wino} + model: wiki.theme + pk: 368 +- fields: {name: Wiosna} + model: wiki.theme + pk: 369 +- fields: {name: Wizja} + model: wiki.theme + pk: 370 +- fields: {name: "Wi\u0119zienie"} + model: wiki.theme + pk: 365 +- fields: {name: "Wi\u0119zie\u0144"} + model: wiki.theme + pk: 366 +- fields: {name: Woda} + model: wiki.theme + pk: 373 +- fields: {name: Wojna} + model: wiki.theme + pk: 374 +- fields: {name: "Wojna pokole\u0144"} + model: wiki.theme + pk: 375 +- fields: {name: "Wolno\u015B\u0107"} + model: wiki.theme + pk: 376 +- fields: {name: "Wr\xF3g"} + model: wiki.theme + pk: 377 +- fields: {name: Wspomnienia} + model: wiki.theme + pk: 378 +- fields: {name: "Wsp\xF3\u0142praca"} + model: wiki.theme + pk: 379 +- fields: {name: Wygnanie} + model: wiki.theme + pk: 380 +- fields: {name: Wyrzuty sumienia} + model: wiki.theme + pk: 381 +- fields: {name: Wyspa} + model: wiki.theme + pk: 382 +- fields: {name: Wzrok} + model: wiki.theme + pk: 383 +- fields: {name: "W\u0105\u017C"} + model: wiki.theme + pk: 355 +- fields: {name: "W\u0142adza"} + model: wiki.theme + pk: 371 +- fields: {name: "W\u0142asno\u015B\u0107"} + model: wiki.theme + pk: 372 +- fields: {name: Zabawa} + model: wiki.theme + pk: 384 +- fields: {name: Zabobony} + model: wiki.theme + pk: 385 +- fields: {name: Zamek} + model: wiki.theme + pk: 386 +- fields: {name: "Zar\u0119czyny"} + model: wiki.theme + pk: 387 +- fields: {name: "Zazdro\u015B\u0107"} + model: wiki.theme + pk: 389 +- fields: {name: "Za\u015Bwiaty"} + model: wiki.theme + pk: 388 +- fields: {name: Zbawienie} + model: wiki.theme + pk: 390 +- fields: {name: Zbrodnia} + model: wiki.theme + pk: 391 +- fields: {name: Zbrodniarz} + model: wiki.theme + pk: 392 +- fields: {name: Zdrada} + model: wiki.theme + pk: 393 +- fields: {name: Zdrowie} + model: wiki.theme + pk: 394 +- fields: {name: Zemsta} + model: wiki.theme + pk: 395 +- fields: {name: "Zes\u0142aniec"} + model: wiki.theme + pk: 396 +- fields: {name: Ziarno} + model: wiki.theme + pk: 397 +- fields: {name: Ziemia} + model: wiki.theme + pk: 398 +- fields: {name: Zima} + model: wiki.theme + pk: 399 +- fields: {name: Zmartwychwstanie} + model: wiki.theme + pk: 403 +- fields: {name: "Zwierz\u0119ta"} + model: wiki.theme + pk: 405 +- fields: {name: "Zwyci\u0119stwo"} + model: wiki.theme + pk: 406 +- fields: {name: "Zw\u0105tpienie"} + model: wiki.theme + pk: 404 +- fields: {name: "Z\u0142o"} + model: wiki.theme + pk: 400 +- fields: {name: "Z\u0142odziej"} + model: wiki.theme + pk: 401 +- fields: {name: "Z\u0142oty wiek"} + model: wiki.theme + pk: 402 +- fields: {name: "\u0141zy"} + model: wiki.theme + pk: 130 +- fields: {name: "\u015Alub"} + model: wiki.theme + pk: 323 +- fields: {name: "\u015Amiech"} + model: wiki.theme + pk: 324 +- fields: {name: "\u015Amier\u0107"} + model: wiki.theme + pk: 325 +- fields: {name: "\u015Amier\u0107 bohaterska"} + model: wiki.theme + pk: 326 +- fields: {name: "\u015Apiew"} + model: wiki.theme + pk: 327 +- fields: {name: "\u015Awiat\u0142o"} + model: wiki.theme + pk: 328 +- fields: {name: "\u015Awit"} + model: wiki.theme + pk: 331 +- fields: {name: "\u015Awi\u0119toszek"} + model: wiki.theme + pk: 329 +- fields: {name: "\u015Awi\u0119ty"} + model: wiki.theme + pk: 330 +- fields: {name: "\u017Ba\u0142oba"} + model: wiki.theme + pk: 407 +- fields: {name: "\u017Bebrak"} + model: wiki.theme + pk: 408 +- fields: {name: "\u017Bona"} + model: wiki.theme + pk: 410 +- fields: {name: "\u017Bo\u0142nierz"} + model: wiki.theme + pk: 409 +- fields: {name: "\u017Bycie jako w\u0119dr\xF3wka"} + model: wiki.theme + pk: 411 +- fields: {name: "\u017Bycie snem"} + model: wiki.theme + pk: 412 +- fields: {name: "\u017Byd"} + model: wiki.theme + pk: 413 +- fields: {name: "\u017Bywio\u0142y"} + model: wiki.theme + pk: 414 + diff --git a/src/wiki/forms.py b/src/wiki/forms.py new file mode 100644 index 00000000..3ef3ed14 --- /dev/null +++ b/src/wiki/forms.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from catalogue.models import Chunk + + +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 DocumentTextSaveForm(forms.Form): + """ + Form for saving document's text: + + * parent_revision - revision which the modified text originated from. + * comment - user's verbose comment; will be used in commit. + * stage_completed - mark this change as end of given stage. + + """ + + parent_revision = forms.IntegerField(widget=forms.HiddenInput, required=False) + text = forms.CharField(widget=forms.HiddenInput) + + author_name = forms.CharField( + required=True, + label=_(u"Author"), + help_text=_(u"Your name"), + ) + + author_email = forms.EmailField( + required=True, + label=_(u"Author's email"), + help_text=_(u"Your email address, so we can show a gravatar :)"), + ) + + comment = forms.CharField( + required=True, + widget=forms.Textarea, + label=_(u"Your comments"), + help_text=_(u"Describe changes you made."), + ) + + stage_completed = forms.ModelChoiceField( + queryset=Chunk.tag_model.objects.all(), + required=False, + label=_(u"Completed"), + help_text=_(u"If you completed a life cycle stage, select it."), + ) + + publishable = forms.BooleanField(required=False, initial=False, + label=_('Publishable'), + help_text=_(u"Mark this revision as publishable.") + ) + + def __init__(self, *args, **kwargs): + user = kwargs.pop('user') + r = super(DocumentTextSaveForm, self).__init__(*args, **kwargs) + if user and user.is_authenticated(): + self.fields['author_name'].required = False + self.fields['author_email'].required = False + return r + + +class DocumentTextRevertForm(forms.Form): + """ + Form for reverting document's text: + + * revision - revision to revert to. + * comment - user's verbose comment; will be used in commit. + + """ + + revision = forms.IntegerField(widget=forms.HiddenInput) + + author_name = forms.CharField( + required=False, + label=_(u"Author"), + help_text=_(u"Your name"), + ) + + author_email = forms.EmailField( + required=False, + label=_(u"Author's email"), + help_text=_(u"Your email address, so we can show a gravatar :)"), + ) + + comment = forms.CharField( + required=True, + widget=forms.Textarea, + label=_(u"Your comments"), + help_text=_(u"Describe the reason for reverting."), + ) diff --git a/src/wiki/helpers.py b/src/wiki/helpers.py new file mode 100644 index 00000000..877a9d0e --- /dev/null +++ b/src/wiki/helpers.py @@ -0,0 +1,61 @@ +from datetime import datetime +from functools import wraps + +from django import http +import json +from django.utils.functional import Promise + + +class ExtendedEncoder(json.JSONEncoder): + + def default(self, obj): + if isinstance(obj, Promise): + return unicode(obj) + + if isinstance(obj, datetime): + return datetime.ctime(obj) + " " + (datetime.tzname(obj) or 'GMT') + + return json.JSONEncoder.default(self, obj) + + +# shortcut for JSON reponses +class JSONResponse(http.HttpResponse): + + def __init__(self, data={}, **kwargs): + # get rid of content_type + kwargs.pop('content_type', None) + + data = json.dumps(data, cls=ExtendedEncoder) + super(JSONResponse, self).__init__(data, content_type="application/json", **kwargs) + + +# return errors +class JSONFormInvalid(JSONResponse): + def __init__(self, form): + super(JSONFormInvalid, self).__init__(form.errors, status=400) + + +class JSONServerError(JSONResponse): + def __init__(self, *args, **kwargs): + kwargs['status'] = 500 + super(JSONServerError, self).__init__(*args, **kwargs) + + +def ajax_login_required(view): + @wraps(view) + def authenticated_view(request, *args, **kwargs): + if not request.user.is_authenticated(): + return http.HttpResponse("Login required.", status=401, content_type="text/plain") + return view(request, *args, **kwargs) + return authenticated_view + + +def ajax_require_permission(permission): + def decorator(view): + @wraps(view) + def authorized_view(request, *args, **kwargs): + if not request.user.has_perm(permission): + return http.HttpResponse("Access Forbidden.", status=403, content_type="text/plain") + return view(request, *args, **kwargs) + return authorized_view + return decorator diff --git a/src/wiki/locale/pl/LC_MESSAGES/django.mo b/src/wiki/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 00000000..cc5b3a59 Binary files /dev/null and b/src/wiki/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/wiki/locale/pl/LC_MESSAGES/django.po b/src/wiki/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 00000000..a6797240 --- /dev/null +++ b/src/wiki/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,470 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Platforma Redakcyjna\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-07-10 16:58+0200\n" +"PO-Revision-Date: 2013-07-10 16:58+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: Fundacja Nowoczesna Polska \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.4\n" + +#: forms.py:19 forms.py:63 views.py:279 +msgid "Publishable" +msgstr "Gotowe do publikacji" + +#: forms.py:38 forms.py:89 +msgid "Author" +msgstr "Autor" + +#: forms.py:39 forms.py:90 +msgid "Your name" +msgstr "Imię i nazwisko" + +#: forms.py:44 forms.py:95 +msgid "Author's email" +msgstr "E-mail autora" + +#: forms.py:45 forms.py:96 +msgid "Your email address, so we can show a gravatar :)" +msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)" + +#: forms.py:51 forms.py:102 +msgid "Your comments" +msgstr "Twój komentarz" + +#: forms.py:52 +msgid "Describe changes you made." +msgstr "Opisz swoje zmiany" + +#: forms.py:58 +msgid "Completed" +msgstr "Ukończono" + +#: forms.py:59 +msgid "If you completed a life cycle stage, select it." +msgstr "Jeśli został ukończony etap prac, wskaż go." + +#: forms.py:64 +msgid "Mark this revision as publishable." +msgstr "Oznacz tę wersję jako gotową do publikacji." + +#: forms.py:103 +msgid "Describe the reason for reverting." +msgstr "Opisz powód przywrócenia." + +#: models.py:14 +msgid "name" +msgstr "nazwa" + +#: models.py:18 +msgid "theme" +msgstr "motyw" + +#: models.py:19 +msgid "themes" +msgstr "motywy" + +#: views.py:281 +msgid "Published" +msgstr "Opublikowano" + +#: views.py:302 +msgid "Revision marked" +msgstr "Wersja oznaczona" + +#: views.py:304 +msgid "Nothing changed" +msgstr "Nic nie uległo zmianie" + +#: templates/admin/wiki/theme/change_list.html:22 +msgid "Table for Redmine wiki" +msgstr "Tabela do wiki na Redmine" + +#: templates/wiki/diff_table.html:5 +msgid "Old version" +msgstr "Stara wersja" + +#: templates/wiki/diff_table.html:6 +msgid "New version" +msgstr "Nowa wersja" + +#: templates/wiki/document_details.html:32 +msgid "Click to open/close gallery" +msgstr "Kliknij, aby (ro)zwinąć galerię" + +#: templates/wiki/document_details_base.html:33 +msgid "Help" +msgstr "Pomoc" + +#: templates/wiki/document_details_base.html:35 +msgid "Version" +msgstr "Wersja" + +#: templates/wiki/document_details_base.html:35 +msgid "Unknown" +msgstr "nieznana" + +#: templates/wiki/document_details_base.html:37 +#: templates/wiki/pubmark_dialog.html:16 +msgid "Save" +msgstr "Zapisz" + +#: templates/wiki/document_details_base.html:38 +msgid "Save attempt in progress" +msgstr "Trwa zapisywanie" + +#: templates/wiki/document_details_base.html:39 +msgid "There is a newer version of this document!" +msgstr "Istnieje nowsza wersja tego dokumentu!" + +#: templates/wiki/pubmark_dialog.html:17 templates/wiki/revert_dialog.html:40 +msgid "Cancel" +msgstr "Anuluj" + +#: templates/wiki/revert_dialog.html:39 +msgid "Revert" +msgstr "Przywróć" + +#: templates/wiki/tabs/annotations_view.html:9 +msgid "all" +msgstr "wszystkie" + +#: templates/wiki/tabs/annotations_view_item.html:3 +msgid "Annotations" +msgstr "Przypisy" + +#: templates/wiki/tabs/gallery_view.html:5 +msgid "Go to first image of this part" +msgstr "Przejdź na początek" + +#: templates/wiki/tabs/gallery_view.html:8 +msgid "Previous" +msgstr "Poprzednie" + +#: templates/wiki/tabs/gallery_view.html:13 +msgid "Next" +msgstr "Następne" + +#: templates/wiki/tabs/gallery_view.html:16 +msgid "Zoom in" +msgstr "Powiększ" + +#: templates/wiki/tabs/gallery_view.html:17 +msgid "Zoom out" +msgstr "Zmniejsz" + +#: templates/wiki/tabs/gallery_view_item.html:3 +msgid "Gallery" +msgstr "Galeria" + +#: templates/wiki/tabs/history_view.html:5 +msgid "Compare versions" +msgstr "Porównaj wersje" + +#: templates/wiki/tabs/history_view.html:8 +msgid "Mark for publishing" +msgstr "Oznacz do publikacji" + +#: templates/wiki/tabs/history_view.html:11 +msgid "Revert document" +msgstr "Przywróć wersję" + +#: templates/wiki/tabs/history_view.html:14 +msgid "View version" +msgstr "Zobacz wersję" + +#: templates/wiki/tabs/history_view_item.html:3 +msgid "History" +msgstr "Historia" + +#: templates/wiki/tabs/search_view.html:3 +#: templates/wiki/tabs/search_view.html:5 +msgid "Search" +msgstr "Szukaj" + +#: templates/wiki/tabs/search_view.html:8 +msgid "Replace with" +msgstr "Zamień na" + +#: templates/wiki/tabs/search_view.html:10 +msgid "Replace" +msgstr "Zamień" + +#: templates/wiki/tabs/search_view.html:12 +msgid "Replace all" +msgstr "Zamień wszystko" + +msgid "Options" +msgstr "Opcje" + +#: templates/wiki/tabs/search_view.html:15 +msgid "Case sensitive" +msgstr "Rozróżniaj wielkość liter" + +#: templates/wiki/tabs/search_view.html:17 +msgid "From cursor" +msgstr "Zacznij od kursora" + +#: templates/wiki/tabs/search_view_item.html:3 +msgid "Search and replace" +msgstr "Znajdź i zamień" + +#: templates/wiki/tabs/source_editor_item.html:5 +msgid "Source code" +msgstr "Kod źródłowy" + +#: templates/wiki/tabs/summary_view.html:13 +msgid "Refresh from working copy" +msgstr "Odśwież z edytowanej wersji" + +#: templates/wiki/tabs/summary_view.html:17 +msgid "Title" +msgstr "Tytuł" + +#: templates/wiki/tabs/summary_view.html:21 +msgid "Go to the book's page" +msgstr "Przejdź do strony książki" + +#: templates/wiki/tabs/summary_view.html:24 +msgid "Document ID" +msgstr "ID dokumentu" + +#: templates/wiki/tabs/summary_view.html:28 +msgid "Current version" +msgstr "Aktualna wersja" + +#: templates/wiki/tabs/summary_view.html:31 +msgid "Last edited by" +msgstr "Ostatnio edytowane przez" + +#: templates/wiki/tabs/summary_view.html:35 +msgid "Link to gallery" +msgstr "Link do galerii" + +#: templates/wiki/tabs/summary_view.html:40 +msgid "Characters in document" +msgstr "Znaków w dokumencie" + +#: templates/wiki/tabs/summary_view.html:41 +msgid "pages" +msgstr "stron maszynopisu" + +#: templates/wiki/tabs/summary_view.html:41 +msgid "untagged" +msgstr "nieotagowane" + +#: templates/wiki/tabs/summary_view_item.html:3 +msgid "Summary" +msgstr "Podsumowanie" + +#: templates/wiki/tabs/wysiwyg_editor.html:9 +msgid "Insert theme" +msgstr "Wstaw motyw" + +#: templates/wiki/tabs/wysiwyg_editor.html:12 +msgid "Insert annotation" +msgstr "Wstaw przypis" + +#: templates/wiki/tabs/wysiwyg_editor_item.html:3 +msgid "Visual editor" +msgstr "Edytor wizualny" + +#~ msgid "ZIP file" +#~ msgstr "Plik ZIP" + +#~ msgid "Chunk with this slug already exists" +#~ msgstr "Część z tym slugiem już istnieje" + +#~ msgid "Append to" +#~ msgstr "Dołącz do" + +#~ msgid "title" +#~ msgstr "tytuł" + +#~ msgid "scan gallery name" +#~ msgstr "nazwa galerii skanów" + +#~ msgid "parent" +#~ msgstr "rodzic" + +#~ msgid "parent number" +#~ msgstr "numeracja rodzica" + +#~ msgid "book" +#~ msgstr "książka" + +#~ msgid "books" +#~ msgstr "książki" + +#~ msgid "Slug already used for %s" +#~ msgstr "Slug taki sam jak dla pliku %s" + +#~ msgid "Slug already used in repository." +#~ msgstr "Dokument o tym slugu już istnieje w repozytorium." + +#~ msgid "File should be UTF-8 encoded." +#~ msgstr "Plik powinien mieć kodowanie UTF-8." + +#~ msgid "Tag added" +#~ msgstr "Dodano tag" + +#~ msgid "Append book" +#~ msgstr "Dołącz książkę" + +#~ msgid "edit" +#~ msgstr "edytuj" + +#~ msgid "add basic document structure" +#~ msgstr "dodaj podstawową strukturę dokumentu" + +#~ msgid "change master tag to" +#~ msgstr "zmień tak master na" + +#~ msgid "add begin trimming tag" +#~ msgstr "dodaj początkowy ogranicznik" + +#~ msgid "add end trimming tag" +#~ msgstr "dodaj końcowy ogranicznik" + +#~ msgid "unstructured text" +#~ msgstr "tekst bez struktury" + +#~ msgid "unknown XML" +#~ msgstr "nieznany XML" + +#~ msgid "broken document" +#~ msgstr "uszkodzony dokument" + +#~ msgid "Apply fixes" +#~ msgstr "Wykonaj zmiany" + +#~ msgid "Append to other book" +#~ msgstr "Dołącz do innej książki" + +#~ msgid "Last published" +#~ msgstr "Ostatnio opublikowano" + +#~ msgid "Full XML" +#~ msgstr "Pełny XML" + +#~ msgid "HTML version" +#~ msgstr "Wersja HTML" + +#~ msgid "TXT version" +#~ msgstr "Wersja TXT" + +#~ msgid "EPUB version" +#~ msgstr "Wersja EPUB" + +#~ msgid "PDF version" +#~ msgstr "Wersja PDF" + +#~ msgid "This book cannot be published yet" +#~ msgstr "Ta książka nie może jeszcze zostać opublikowana" + +#~ msgid "Add chunk" +#~ msgstr "Dodaj część" + +#~ msgid "Clear filter" +#~ msgstr "Wyczyść filtr" + +#~ msgid "No books found." +#~ msgstr "Nie znaleziono książek." + +#~ msgid "Your last edited documents" +#~ msgstr "Twoje ostatnie edycje" + +#~ msgid "Bulk documents upload" +#~ msgstr "Hurtowe dodawanie dokumentów" + +#~ msgid "" +#~ "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with " +#~ ".xml will be ignored." +#~ msgstr "" +#~ "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie " +#~ "kończące się na .xml zostaną zignorowane." + +#~ msgid "Upload" +#~ msgstr "Załaduj" + +#~ msgid "" +#~ "There have been some errors. No files have been added to the repository." +#~ msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium." + +#~ msgid "Offending files" +#~ msgstr "Błędne pliki" + +#~ msgid "Correct files" +#~ msgstr "Poprawne pliki" + +#~ msgid "Files have been successfully uploaded to the repository." +#~ msgstr "Pliki zostały dodane do repozytorium." + +#~ msgid "Uploaded files" +#~ msgstr "Dodane pliki" + +#~ msgid "Skipped files" +#~ msgstr "Pominięte pliki" + +#~ msgid "Files skipped due to no .xml extension" +#~ msgstr "Pliki pominięte z powodu braku rozszerzenia .xml." + +#~ msgid "Users" +#~ msgstr "Użytkownicy" + +#~ msgid "Assigned to me" +#~ msgstr "Przypisane do mnie" + +#~ msgid "Unassigned" +#~ msgstr "Nie przypisane" + +#~ msgid "All" +#~ msgstr "Wszystkie" + +#~ msgid "Add" +#~ msgstr "Dodaj" + +#~ msgid "Admin" +#~ msgstr "Administracja" + +#~ msgid "First correction" +#~ msgstr "Autokorekta" + +#~ msgid "Tagging" +#~ msgstr "Tagowanie" + +#~ msgid "Initial Proofreading" +#~ msgstr "Korekta" + +#~ msgid "Annotation Proofreading" +#~ msgstr "Sprawdzenie przypisów źródła" + +#~ msgid "Modernisation" +#~ msgstr "Uwspółcześnienie" + +#~ msgid "Themes" +#~ msgstr "Motywy" + +#~ msgid "Editor's Proofreading" +#~ msgstr "Ostateczna redakcja literacka" + +#~ msgid "Technical Editor's Proofreading" +#~ msgstr "Ostateczna redakcja techniczna" + +#~ msgid "Finished stage: %s" +#~ msgstr "Ukończony etap: %s" + +#~ msgid "Refresh" +#~ msgstr "Odśwież" + +#~ msgid "Insert special character" +#~ msgstr "Wstaw znak specjalny" diff --git a/src/wiki/migrations/0001_initial.py b/src/wiki/migrations/0001_initial.py new file mode 100644 index 00000000..4acf5ba5 --- /dev/null +++ b/src/wiki/migrations/0001_initial.py @@ -0,0 +1,21 @@ +# 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): + pass + + + def backwards(self, orm): + pass + + + models = { + + } + + complete_apps = ['wiki'] diff --git a/src/wiki/migrations/0002_auto__add_theme.py b/src/wiki/migrations/0002_auto__add_theme.py new file mode 100644 index 00000000..6688139d --- /dev/null +++ b/src/wiki/migrations/0002_auto__add_theme.py @@ -0,0 +1,38 @@ +# 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 'Theme' + db.create_table('wiki_theme', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=50)), + )) + db.send_create_signal('wiki', ['Theme']) + + if not db.dry_run: + from django.core.management import call_command + call_command("loaddata", "initial_themes.yaml") + + + + def backwards(self, orm): + + # Deleting model 'Theme' + db.delete_table('wiki_theme') + + + models = { + 'wiki.theme': { + 'Meta': {'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/src/wiki/migrations/__init__.py b/src/wiki/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/wiki/models.py b/src/wiki/models.py new file mode 100644 index 00000000..c539908d --- /dev/null +++ b/src/wiki/models.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +import logging +logger = logging.getLogger("fnp.wiki") + + +class Theme(models.Model): + name = models.CharField(_('name'), max_length=50, unique=True) + + class Meta: + ordering = ('name',) + verbose_name = _('theme') + verbose_name_plural = _('themes') + + def __unicode__(self): + return self.name + + def __repr__(self): + return "Theme(name=%r)" % self.name + diff --git a/src/wiki/nice_diff.py b/src/wiki/nice_diff.py new file mode 100644 index 00000000..b228fad9 --- /dev/null +++ b/src/wiki/nice_diff.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +import difflib +import re +from collections import deque + +from django.template.loader import render_to_string +from django.utils.html import escape as html_escape + +DIFF_RE = re.compile(r"""\x00([+^-])""", re.UNICODE) +NAMES = {'+': 'added', '-': 'removed', '^': 'changed'} + + +def diff_replace(match): + return """""" % NAMES[match.group(1)] + + +def filter_line(line): + return DIFF_RE.sub(diff_replace, html_escape(line)).replace('\x01', '') + + +def format_changeset(a, b, change): + return (a[0], filter_line(a[1]), b[0], filter_line(b[1]), change) + + +def html_diff_table(la, lb, context=None): + all_changes = difflib._mdiff(la, lb) + + if context is None: + changes = (format_changeset(*c) for c in all_changes) + else: + changes = [] + q = deque() + after_change = False + + for changeset in all_changes: + q.append(changeset) + + if changeset[2]: + after_change = True + if not after_change: + changes.append((0, '-----', 0, '-----', False)) + changes.extend(format_changeset(*c) for c in q) + q.clear() + else: + if len(q) == context and after_change: + changes.extend(format_changeset(*c) for c in q) + q.clear() + after_change = False + elif len(q) > context: + q.popleft() + + return render_to_string("wiki/diff_table.html", { + "changes": changes, + }) + + +__all__ = ['html_diff_table'] diff --git a/src/wiki/settings.py b/src/wiki/settings.py new file mode 100644 index 00000000..50f49d8b --- /dev/null +++ b/src/wiki/settings.py @@ -0,0 +1,3 @@ +from django.conf import settings + +GALLERY_URL = settings.MEDIA_URL + 'images/' diff --git a/src/wiki/templates/admin/wiki/theme/change_list.html b/src/wiki/templates/admin/wiki/theme/change_list.html new file mode 100755 index 00000000..3e5d2ea4 --- /dev/null +++ b/src/wiki/templates/admin/wiki/theme/change_list.html @@ -0,0 +1,29 @@ +{% extends "admin/change_list.html" %} +{% load i18n %} + +{% block extrahead %} +{{ block.super }} + +{% endblock %} + + + +{% block pretitle %} + + +↓ {% trans "Table for Redmine wiki" %} ↓ + + +{{ block.super }} +{% endblock %} diff --git a/src/wiki/templates/wiki/diff_table.html b/src/wiki/templates/wiki/diff_table.html new file mode 100644 index 00000000..818c38cc --- /dev/null +++ b/src/wiki/templates/wiki/diff_table.html @@ -0,0 +1,21 @@ +{% load i18n %} + + + + + + + + +{% for an, a, bn, b, has_change in changes %} + + + + + + + + +{% endfor %} + +
    {% trans "Old version" %}{% trans "New version" %}
    {{an}}{{ a|safe }} {{bn}}{{ b|safe }} 
    \ No newline at end of file diff --git a/src/wiki/templates/wiki/document_details.html b/src/wiki/templates/wiki/document_details.html new file mode 100644 index 00000000..2211c0a3 --- /dev/null +++ b/src/wiki/templates/wiki/document_details.html @@ -0,0 +1,50 @@ +{% extends "wiki/document_details_base.html" %} +{% load i18n %} + +{% block extrabody %} +{{ block.super }} + + +{% endblock %} + +{% block tabs-menu %} + {% include "wiki/tabs/summary_view_item.html" %} + {% include "wiki/tabs/wysiwyg_editor_item.html" %} + {% include "wiki/tabs/source_editor_item.html" %} + {% include "wiki/tabs/history_view_item.html" %} +{% endblock %} + +{% block tabs-content %} + {% include "wiki/tabs/summary_view.html" %} + {% include "wiki/tabs/wysiwyg_editor.html" %} + {% include "wiki/tabs/source_editor.html" %} + {% include "wiki/tabs/history_view.html" %} +{% endblock %} + +{% block tabs-right %} + {% include "wiki/tabs/gallery_view_item.html" %} + {% include "wiki/tabs/annotations_view_item.html" %} + {% include "wiki/tabs/search_view_item.html" %} +{% endblock %} + +{% block splitter-extra %} +
    +

    +
    + +
    +{% endblock %} + +{% block dialogs %} + {% include "wiki/save_dialog.html" %} + {% include "wiki/revert_dialog.html" %} + {% include "wiki/tag_dialog.html" %} + {% if can_pubmark %} + {% include "wiki/pubmark_dialog.html" %} + {% endif %} +{% endblock %} diff --git a/src/wiki/templates/wiki/document_details_base.html b/src/wiki/templates/wiki/document_details_base.html new file mode 100644 index 00000000..00c4f6a3 --- /dev/null +++ b/src/wiki/templates/wiki/document_details_base.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} +{% load toolbar_tags i18n %} + +{% block titleextra %}{% if chunk.pretty_title %}{{ chunk.pretty_title }}{% else %}{{ chunk.book.title }}{% endif %}{% endblock %} +{% block extrahead %} +{% load pipeline %} +{% stylesheet 'detail' %} +{% endblock %} + +{% block extrabody %} + +{% javascript 'detail' %} +{% endblock %} + +{% block maincontent %} + + + +
    +
    + {% block tabs-content %} {% endblock %} +
    + {% block splitter-extra %} {% endblock %} +
    + +{% block dialogs %} {% endblock %} + +{% endblock %} diff --git a/src/wiki/templates/wiki/document_details_readonly.html b/src/wiki/templates/wiki/document_details_readonly.html new file mode 100644 index 00000000..71556a19 --- /dev/null +++ b/src/wiki/templates/wiki/document_details_readonly.html @@ -0,0 +1,27 @@ +{% extends "wiki/document_details_base.html" %} +{% load i18n %} + +{% block editor-class %}readonly{% endblock %} + +{% block extrabody %} +{{ block.super }} + + +{% endblock %} + +{% block tabs-menu %} + {% include "wiki/tabs/wysiwyg_editor_item.html" %} + {% include "wiki/tabs/source_editor_item.html" %} +{% endblock %} + +{% block tabs-content %} + {% include "wiki/tabs/wysiwyg_editor.html" %} + {% include "wiki/tabs/source_editor.html" %} +{% endblock %} + +{% block splitter-extra %} +{% endblock %} + +{% block dialogs %} +{% endblock %} \ No newline at end of file diff --git a/src/wiki/templates/wiki/pubmark_dialog.html b/src/wiki/templates/wiki/pubmark_dialog.html new file mode 100755 index 00000000..a70a0c38 --- /dev/null +++ b/src/wiki/templates/wiki/pubmark_dialog.html @@ -0,0 +1,20 @@ +{% load i18n %} +
    +
    + {% csrf_token %} + {% for field in forms.pubmark.visible_fields %} +

    {{ field.label_tag }} {{ field }}

    +

    {{ field.help_text }}

    + {% endfor %} + + {% for f in forms.pubmark.hidden_fields %} + {{ f }} + {% endfor %} +

    + +

    + + +

    +
    +
    diff --git a/src/wiki/templates/wiki/revert_dialog.html b/src/wiki/templates/wiki/revert_dialog.html new file mode 100644 index 00000000..6f1793be --- /dev/null +++ b/src/wiki/templates/wiki/revert_dialog.html @@ -0,0 +1,43 @@ +{% load i18n %} +
    +
    + {% csrf_token %} +

    {{ forms.text_revert.comment.label }}

    +

    + {{ forms.text_revert.comment.help_text}} + +

    + {{forms.text_revert.comment }} + + + + {% if request.user.is_anonymous %} + + + + + + + + + +
    {{ forms.text_revert.author_name.label }}:{{ forms.text_revert.author_name }} + {{ forms.text_revert.author_name.help_text }} +
    {{ forms.text_revert.author_email.label }}:{{ forms.text_revert.author_email }} + {{ forms.text_revert.author_email.help_text }} +
    + {% endif %} + + + {% for f in forms.text_revert.hidden_fields %} + {{ f }} + {% endfor %} + +

    + +

    + + +

    +
    +
    diff --git a/src/wiki/templates/wiki/save_dialog.html b/src/wiki/templates/wiki/save_dialog.html new file mode 100644 index 00000000..b1330455 --- /dev/null +++ b/src/wiki/templates/wiki/save_dialog.html @@ -0,0 +1,59 @@ +{% load i18n %} +
    +
    + {% csrf_token %} +

    {{ forms.text_save.comment.label }}

    +

    + {{ forms.text_save.comment.help_text}} + +

    + {{forms.text_save.comment }} + + + + {% if request.user.is_anonymous %} + + + + + + + + + +
    {{ forms.text_save.author_name.label }}:{{ forms.text_save.author_name }} + {{ forms.text_save.author_name.help_text }} +
    {{ forms.text_save.author_email.label }}:{{ forms.text_save.author_email }} + {{ forms.text_save.author_email.help_text }} +
    + {% else %} +

    + {{ forms.text_save.stage_completed.label }}: + {{ forms.text_save.stage_completed }} + {{ forms.text_save.stage_completed.help_text }} + +

    + {% if can_pubmark %} +

    + {{ forms.text_save.publishable.label_tag }}: + {{ forms.text_save.publishable }} + {{ forms.text_save.publishable.help_text }} + +

    + {% endif %} + + {% endif %} + + + {% for f in forms.text_save.hidden_fields %} + {{ f }} + {% endfor %} + +

    + +

    + + +

    +
    +
    diff --git a/src/wiki/templates/wiki/tabs/annotations_view.html b/src/wiki/templates/wiki/tabs/annotations_view.html new file mode 100644 index 00000000..8e54242c --- /dev/null +++ b/src/wiki/templates/wiki/tabs/annotations_view.html @@ -0,0 +1,20 @@ +{% load i18n %} +
    + +
    + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + Loading +
    +
    diff --git a/src/wiki/templates/wiki/tabs/annotations_view_item.html b/src/wiki/templates/wiki/tabs/annotations_view_item.html new file mode 100644 index 00000000..7f17ce57 --- /dev/null +++ b/src/wiki/templates/wiki/tabs/annotations_view_item.html @@ -0,0 +1,4 @@ +{% load i18n %} +
  • + [1] +
  • diff --git a/src/wiki/templates/wiki/tabs/gallery_view.html b/src/wiki/templates/wiki/tabs/gallery_view.html new file mode 100644 index 00000000..4e57ea51 --- /dev/null +++ b/src/wiki/templates/wiki/tabs/gallery_view.html @@ -0,0 +1,27 @@ +{% load i18n %} + diff --git a/src/wiki/templates/wiki/tabs/gallery_view_item.html b/src/wiki/templates/wiki/tabs/gallery_view_item.html new file mode 100644 index 00000000..0ad3adda --- /dev/null +++ b/src/wiki/templates/wiki/tabs/gallery_view_item.html @@ -0,0 +1,4 @@ +{% load i18n %} +
  • + {% trans +
  • diff --git a/src/wiki/templates/wiki/tabs/history_view.html b/src/wiki/templates/wiki/tabs/history_view.html new file mode 100644 index 00000000..93b0bb64 --- /dev/null +++ b/src/wiki/templates/wiki/tabs/history_view.html @@ -0,0 +1,41 @@ +{% load i18n %} + diff --git a/src/wiki/templates/wiki/tabs/history_view_item.html b/src/wiki/templates/wiki/tabs/history_view_item.html new file mode 100644 index 00000000..e9375cde --- /dev/null +++ b/src/wiki/templates/wiki/tabs/history_view_item.html @@ -0,0 +1,4 @@ +{% load i18n %} +
  • + {% trans "History" %} +
  • diff --git a/src/wiki/templates/wiki/tabs/search_view.html b/src/wiki/templates/wiki/tabs/search_view.html new file mode 100644 index 00000000..2ed15eca --- /dev/null +++ b/src/wiki/templates/wiki/tabs/search_view.html @@ -0,0 +1,21 @@ +{% load i18n %} + \ No newline at end of file diff --git a/src/wiki/templates/wiki/tabs/search_view_item.html b/src/wiki/templates/wiki/tabs/search_view_item.html new file mode 100644 index 00000000..7acfaacb --- /dev/null +++ b/src/wiki/templates/wiki/tabs/search_view_item.html @@ -0,0 +1,4 @@ +{% load i18n %} +
  • + {% trans +
  • diff --git a/src/wiki/templates/wiki/tabs/source_editor.html b/src/wiki/templates/wiki/tabs/source_editor.html new file mode 100644 index 00000000..9b228251 --- /dev/null +++ b/src/wiki/templates/wiki/tabs/source_editor.html @@ -0,0 +1,5 @@ +{% load toolbar_tags i18n %} +
    + {% if not readonly %}{% toolbar %}{% endif %} + +
    diff --git a/src/wiki/templates/wiki/tabs/source_editor_item.html b/src/wiki/templates/wiki/tabs/source_editor_item.html new file mode 100644 index 00000000..22b6d660 --- /dev/null +++ b/src/wiki/templates/wiki/tabs/source_editor_item.html @@ -0,0 +1,6 @@ +{% load i18n %} +
  • + {% trans "Source code" %} +
  • \ No newline at end of file diff --git a/src/wiki/templates/wiki/tabs/summary_view.html b/src/wiki/templates/wiki/tabs/summary_view.html new file mode 100644 index 00000000..49ad5fe7 --- /dev/null +++ b/src/wiki/templates/wiki/tabs/summary_view.html @@ -0,0 +1,45 @@ +{% load i18n %} + diff --git a/src/wiki/templates/wiki/tabs/summary_view_item.html b/src/wiki/templates/wiki/tabs/summary_view_item.html new file mode 100644 index 00000000..ea7ae746 --- /dev/null +++ b/src/wiki/templates/wiki/tabs/summary_view_item.html @@ -0,0 +1,4 @@ +{% load i18n %} +
  • + {% trans "Summary" %} +
  • diff --git a/src/wiki/templates/wiki/tabs/wysiwyg_editor.html b/src/wiki/templates/wiki/tabs/wysiwyg_editor.html new file mode 100644 index 00000000..f54f3fb8 --- /dev/null +++ b/src/wiki/templates/wiki/tabs/wysiwyg_editor.html @@ -0,0 +1,18 @@ +{% load i18n %} + diff --git a/src/wiki/templates/wiki/tabs/wysiwyg_editor_item.html b/src/wiki/templates/wiki/tabs/wysiwyg_editor_item.html new file mode 100644 index 00000000..ec853cd7 --- /dev/null +++ b/src/wiki/templates/wiki/tabs/wysiwyg_editor_item.html @@ -0,0 +1,4 @@ +{% load i18n %} +
  • + {% trans "Visual editor" %} +
  • diff --git a/src/wiki/tests/xslt/auto/auto_test.js b/src/wiki/tests/xslt/auto/auto_test.js new file mode 100644 index 00000000..f9e50ee4 --- /dev/null +++ b/src/wiki/tests/xslt/auto/auto_test.js @@ -0,0 +1,139 @@ +var jsdom = require("jsdom"); +var $ = require('jquery')(new jsdom.JSDOM().window); +var fs = require('fs'); +var exec = require('child_process').exec; +var pd = require('pretty-data').pd; +var ansidiff = require('ansidiff'); + + +eval(fs.readFileSync(__dirname + '/../../../../redakcja/static/js/wiki/xslt.js') + ''); + + +function assertNodesEqual(lhs, rhs, areHTMLNodes) { + + function throwError() { + throw new Error(ansidiff.chars(pd.xml(lhs), pd.xml(rhs))); + } + + var lhsNode = $(lhs)[0], + rhsNode = $(rhs)[0]; + + if(areHTMLNodes) { + var getMagicAttrsInfo = function(node) { + var valuePrefix = 'x-attr-value-'; + var namePrefix = 'x-attr-name-'; + var startsWith = function(str, prefix) { return str.substr(0, prefix.length) === prefix; } + var isNameAttribute = function(attr) { return startsWith(attr.name, namePrefix); } + var isValueAttribute = function(attr) { return startsWith(attr.name, valuePrefix); } + var extractId = function(attr) { return attr.name.split('-').pop(); } + var temp = {}; + var toret = {map: {}, magicAttrsList: []}; + + for(var i = 0; i < node.attributes.length; i++) { + var attr = node.attributes[i]; + if(isNameAttribute(attr) || isValueAttribute(attr)) { + var id = extractId(attr); + temp[id] = typeof temp[id] === 'undefined' ? {} : temp[id]; + if(isNameAttribute(attr)) + temp[id]['name'] = attr.value; + else + temp[id]['value'] = attr.value; + toret.magicAttrsList.push(attr.name); + } + } + + Object.keys(temp).forEach(function(id) { + var pair = temp[id]; + toret.map[pair.name] = typeof toret.map[pair.name] === 'undefined' ? toret.map[pair.name] = [] : toret.map[pair.name]; + toret.map[pair.name].push(pair.value); + }); + + return toret; + } + + var removeAttrs = function(node, attrsNames) { + attrsNames.forEach(function(name) { + node.removeAttribute(name); + }); + } + + var mapsEqual = function(map1, map2) { + if(Object.keys(map1).length != Object.keys(map2).length) + return false; + var arraysEqual = function(a1, a2) {return a1.slice().sort().join('') === a2.slice().sort().join('');} + Object.keys(map1).forEach(function(key) { + if(Object.keys(map2).indexOf(key) === -1 || !arraysEqual(map2[key], map1[key])) + return false; + }); + return true; + } + + var lhsMagicAttrsInfo = getMagicAttrsInfo(lhsNode), + rhsMagicAttrsInfo = getMagicAttrsInfo(rhsNode); + + removeAttrs(lhsNode, lhsMagicAttrsInfo.magicAttrsList); + removeAttrs(rhsNode, rhsMagicAttrsInfo.magicAttrsList); + + if(!mapsEqual(lhsMagicAttrsInfo.map, rhsMagicAttrsInfo.map)) + throwError(); + } + + if(!lhsNode.isEqualNode(rhsNode)) + throwError(); +} + +suite('wiki.tests.xslt.auto', function() { + + var tempFileName = '.temp.xml'; + var xsltStyleSheetPath = __dirname + '/../../../../redakcja/static/xsl/wl2html_client.xsl'; + + fs.readdirSync(__dirname + '/data/').forEach(function(fileName) { + + if(fileName === tempFileName) + return; + + var ext = fileName.split('.').pop(); + if(ext !== 'html' && ext !== 'xml') + return; + + var inputData = fs.readFileSync(__dirname + '/data/' + fileName) + ''; + + if(ext === 'html') { + test('[HTML->XML->HTML] ' + fileName, function(done) { + var result = html2text({ + element: $(inputData)[0], + stripOuter: false, + success: function(generatedXML) { + fs.writeFileSync(tempFileName, generatedXML); + exec(['xsltproc', xsltStyleSheetPath, tempFileName].join(' ') , {}, + function(error, stdout, stderr) { + fs.unlinkSync(tempFileName); + assertNodesEqual(inputData, stdout, true); + done(); + }); + }, + error: function(msg){throw msg;} + }); + }); + } else if(ext === 'xml') { + test('[XML->HTML->XML] ' + fileName, function(done) { + var originalXML = $(inputData); + exec(['xsltproc', xsltStyleSheetPath, __dirname + '/data/' + fileName].join(' ') , {}, + function(error, stdout, stderr) { + var generatedHTML = $(stdout); + var result = html2text({ + element: generatedHTML[0], + stripOuter: false, + success: function(xmltext) { + assertNodesEqual(inputData, xmltext); + done(); + }, + error: function(msg){throw msg;} + }); + }); + }); + } + }); +}); + + diff --git a/src/wiki/tests/xslt/auto/data/akap.html b/src/wiki/tests/xslt/auto/data/akap.html new file mode 100644 index 00000000..9b2a3ed4 --- /dev/null +++ b/src/wiki/tests/xslt/auto/data/akap.html @@ -0,0 +1,3 @@ +

    + Potrzebne materialy k1asa +

    diff --git a/src/wiki/tests/xslt/auto/data/big.xml b/src/wiki/tests/xslt/auto/data/big.xml new file mode 100644 index 00000000..de73d5f0 --- /dev/null +++ b/src/wiki/tests/xslt/auto/data/big.xml @@ -0,0 +1,1049 @@ + + + + + + +Mistrz rzekł i dodał: «Prawdzie nie skłamałem»./ +A na dnie jamy wszyscy potępieńce/ +Stanęli, patrzą na mnie gronem całem,/ +Zapominając z podziwu o męce./ +--- «Jak ujrzysz słońce, powiedz DulcynowiDulcyn --- braciszek zakonny; umknął po kryjomu i wstąpił w rolę zapaleńca religijnego, wmawiając w lud łatwowierny, że on jest rzeczywistym apostołem bożym. W r. 1305 w górach Nowary założył sektę znaną odtąd pod imieniem dulcynów. Dulcyn w krótkim czasie zebrał z tysiąc zwolenników około siebie, lecz ścigany przez wojsko biskupa miasta Vercelli schwytany został w górach Nowary razem ze swoją żoną i z nią [w:] 1307 r. żywcem spalony na rynku tegoż miasta./ +Jeśli tu prędko zstąpić nieochoczy,/ +Niech skupi żywność, śniegiem się otoczy;/ +Bo jak ci mówię, bez śniegu i głodu,/ +Nowarczyk w górach niełatwo go złowi»./ +DuchTak z podniesioną stopą do pochodu,/ +Duch Mahometa mówił, potem nogę/ +Na dłuż prostując poszedł w swoją drogę./ +Duch drugi z krtanią przebitą, kaleki,/ +Z nosem rozciętym pod same powieki,/ +I z jednym uchem od lewego oka,/ +Stanął, z nim całe wstrzymało się grono:/ +Duch patrząc na mnie z twarzą zadziwioną,/ +Otworzył gębę jak jamę czerwoną,/ +Którą broczyła świeża krwi posoka,/ +I rzekł: «Schodzący tu gościu bez winy,/ +Jeśli nie ludzi wielkie podobieństwo,/ +Widziałem ciebie pomiędzy Latyny./ +Przypomnij sobie Piotra z MedicynyPrzypomnij sobie Piotra z Medicyny --- Medicyna wziął imię od miejsca swego urodzenia w ziemi bolońskiej; warchoł i intrygant, kłócił +lud ze szlachtą, waśnił władców Rawenny i Rimini.!/ +Idąc z Wercelli smugiem do MarkaboIdąc z Wercelli smugiem do Markabo --- Kraj lombardzki od Wercelli w Piemoncie aż do ujścia rzeki Po, nad którą stał niegdyś zamek Makrabo./ +Kara, ŚmierćOstrzeż ode mnie dwóch najlepszych z Fano,/ +Andziolello i Gwido ich mianoAndziolello i Gwido (...) zdradzi najniegodniej --- Malatestino, książę w Rimini, który był na jedno oko ciemnym, zaprosił dwóch najznakomitszych obywateli miasta Fano, Gwidona del Cassero i Angiolela de Carignano do miasteczka Katoliki na ucztę, gdzie mieli się wspólnie naradzać nad pewnymi sprawami. Wysłał po nich łodzie, lecz majtkowie z jego rozkazu w porcie Katoliki obydwu wrzucili do morza. Dante w tej pieśni prorokuje, co się już stało.,/ +Że niespodziane czeka ich męczeństwo./ +Gdy zmysł proroczy widzi tu niesłabo,/ +Wrzucą ich w morze w porcie Katoliki;/ +Bo od Majorki do Cypru zatoki/ +Neptun nie widział nigdy takiej zbrodni,/ +Gdzie wciąż koczują greckie rozbójniki./ +Zdrajca rządzący, chociaż jednooki,/ +Krajem, którego duch, co za mną kroczy,/ +Nie chciałby nigdy widzieć w żywe oczy,/ +Sprosi ich w gości, zdradzi najniegodniej,/ +Czym ich uwolni od ślubów ofiary/ +Przeciw wiejącym wiatrom od FokaryFokara --- góra blisko Katoliki skąd często wiejące wiatry i burze mitrężą żeglugę. Ponieważ zginęli, nie potrzebują już obawiać się tych wichrów.»./ +--- «Gdy chcesz,» odrzekłem, «abym tam wysoko/ +Mówił o tobie w twoim ziemskim raju,/ +Kto ten nieszczęsny, wskaż mi go na oko,/ +Któremu było tak gorzko w tym kraju?»/ +Do towarzysza duch wyciągnął rękę,/ +Na oścież jemu otwierając szczękę,/ +Krzyczał: «Patrz teraz, to on, lecz nie mówi:/ +On myśl wątpiącą uśpił Cezarowi,/ +Twierdząc, że zawsze niebezpiecznie czekać,/ +Zamiar dojrzały do czynu odwlekać»./ +Jakże przeraził mnie wrażeniem dzikiem,/ +Ze swoim w krtani uciętym językiem,/ +Ten KurionKurio --- wygnany z senatu rzymskiego jako stronnik i przyjaciel Cezara, z nim się połączył i pierwszy go zachęcił do przejścia Rubikonu, co było powodem wojen domowych. Ma tu odcięty język, którym dał tak złą radę. niegdyś tak zuchwały w mowie./ +Duch drugi obie miał ucięte ręce,/ +W zmroku wywijał tępymi ramiony./ +Krwią, co z nich ciekła, cały oczerniony,/ +I krzyknął, krzyk ten aż mi szumiał w głowie:/ +«Przypomnij MoskęPrzypomnij Moskę --- Buondelmonte przyrzekł ożenić się z panną z domu Amidei, gdy tymczasem nagle zmienił swój zamysł i ożenił się z krewną Donatich. Ten postępek rozjątrzył gniew wielu domów spokrewnionych z rodem Amidei. Uberti i Lamberti głośno wołali: ,,Zemścić się, ukarać Buondelmonta!". Starsi radzili roztropność i umiarkowanie: lecz młody Moska wrzący gniewem, radził zabić Buondelmonta, którego sam na koniec przebił puginałem. Ta familijna tragedia dała początek stronnictwom i sporom, jakie przez długie czasy wstrząsały Florencją., przebóg, ja to rzekłem:/ +»Koniec koroną powinien być czynów!«/ +Z tych słów urosły kłótnie florentynów»./ +--- «I śmierć twej całej rodziny!» dodałem./ +On wtenczas z bólu łypiąc okiem wściekłem,/ +Odszedł jak gdyby już oszalał w męce./ +Ja wciąż na trzodę piekielną patrzałem;/ +I to widziałem, czego bez dowodu/ +Nie śmiałbym w pieśni opowiadać mojej,/ +Gdyby nie dobry nasz świadek, sumienie,/ +Które pod zbroją nam czystości swojej/ +Serce umacnia, zagłusza zwątpienie./ +DuchWidziałem, wierzcie na słowo poety,/ +Tułów bez głowy, jako inne cienie/ +Szedł równie dobrze sporym krokiem chodu,/ +A w ręku trzymał swą uciętą głowę;/ +Głowa na włosach na obraz latarni/ +Zwieszona, z gestem bolesnym męczarni/ +Patrzając na nas, mówiła: Niestety!/ +Tułów przejrzysty jak szkło kryształowe/ +Sam stał się lampą i razem w tej chwili/ +Dwoje ich w jednym, jeden w dwojgu byli./ +Ile to widmo było rzeczywiste,/ +Pan nasz i mściciel wie o tym zaiste!/ +Tułów podszedłszy pod szyję mostową,/ +Wzniósł w górę rękę z całą swoją głową,/ +Ażeby do nas przybliżyć swe słowo,/ +Które tak brzmiało: «Gościu nieumarły,/ +Patrz, jakie męki na mnie się wywarły,/ +Patrz, czy jest większa od mojej tortura?/ +Jeśli mną zająć chcesz ciekawość czyją,/ +Wiedz, że ja byłem Bertrandem z BornijoWiedz, że ja byłem Bertrandem z Bornijo --- Bertrand z Bornio (Bertram de Born) był obwiniany za to, że między Henrykiem II, angielskim królem, a jego synem jątrzył niezgodę i syna do buntu przeciw własnemu ojcu pobudził. Ponieważ Bertrand przeciw głowie rodziny jej członków buntował, tu za karę nosi własną głowę odciętą od tułowia. Ta głowa służy mu w piekle za latarkę, jak i na ziemi podobną jemu powinna była posługę, ażeby go ochroniła od smutnych następstw jego występku./ +Dałem złe rady młodemu królowi,/ +Jątrzyłem syna przeciwko ojcowi:/ +Sam Architofel nie gorszym się wyda,/ +Co Absalona jątrzył na Dawida./ +Za to żem dzielił, co łączy natura,/ +Chodzę tu, przebóg, jak bezgłowa mara;/ +I słusznie, jaka zbrodnia, taka kara». + + + + +Pieśń XXIX + + + + + + + + +(Krąg VIII. Tłumok X. Fałszerze. Alchemiści.) + + + +Rozmaitymi tłum ludu ranami/ +Tak moje oczy upoił w tej chwili,/ +Że pragnął spocząć i trzeźwić je łzami./ +«Co tak spoglądasz?» przemówił Wirgili,/ +«Czemu tak pilno patrzą twe powieki/ +Na tłum tych cieniów tak smutnie kaleki?/ +Tegoś nie robił przy drugich tłumokach:/ +Gdy chcesz policzyć, duchów tu jest ile,/ +Pomyśl; ta jama wykuta w opokach,/ +Ma w swym obwodzie dwadzieścia dwie mile./ +Księżyc już zaszedł pod nasze podnóżeKsiężyc już zaszedł pod nasze podnóże --- Kiedy poeci rozpoczynali swoją pielgrzymkę po piekle, księżyc był w pełni, a więc słońce, kiedy teraz księżyc jest pod ich stopami, stoi ponad ich głową; przeto w tej chwili na wschodniej półsferze jest samo południe.,/ +Odtąd dość krótki (szanuj lot chwil czynnych,)/ +Czas dozwolony do pielgrzymki twojej;/ +Jeszcze masz widzieć wiele rzeczy innych,/ +O których może myśl twa ani roi»./ +Odpowiedziałem: «Gdybyś spojrzeć raczył/ +Na powód, co tu wzrok mój tak zahaczył,/ +Dłużej mi patrzeć pozwoliłbyś może?»/ +Wódz szedł, ja za nim z wolna sunąc nogą,/ +Dodałem jeszcze, wciąż mówić ochoczy:/ +«Tam, skąd utkwione odjąłem me oczy,/ +W jamie, co w skał tych zapadła krawędzie,/ +Myślę, swej winy płacze duch mój krewny,/ +Która tam jego kosztuje tak drogo!»/ +Mistrz odpowiedział: «Hamuj pociąg rzewny,/ +Dłużej tym duchem nie rozrzewniaj myśli,/ +Marz o czym innym, gdzie on jest, niech będzie./ +Widziałem jego w chwili, gdyśmy przyszli/ +Na łuk mostowy, on cię zauważał,/ +Wskazywał, żywo palcem ci pograżał;/ +Słyszałem, jak nań jeden duch przeklęty:/ +Geri del Bello, po imieniu krzyknąłGeri del Bello --- po matce krewny Dantego; zabił go Sachetti; śmierci jego trzydzieści lat po popełnionym morderstwie pomści jego synowiec.,/ +Lecz tyś był hrabią z Hetfortu zajętyHrabia z Hetfortu --- jest to Bertrand z Bornio, o którym wzmianka była w pieśni poprzedniej.,/ +W to miejsce wtedy spojrzałeś, gdy zniknął»./ +A ja: «Śmierć jego, której wstyd podziela/ +Krew nasza, od nas wygląda mściciela;/ +Dlatego, myślę, z uczuciem pogardy,/ +Nie mówiąc do mnie, odszedł duch ten hardy,/ +Co go w mej duszy jeszcze droższym czyni»./ +Tak mówiąc szliśmy do pierwszej przystani,/ +Skąd mógłbym widzieć dno innej otchłani,/ +Gdyby być mogło świetlejświetlej --- jaśniej. w tej jaskini./ +Gdym już od mostu stał o kilka kroków,/ +Krzywda, KaraGdzie był ostatni klasztor Złych TłumokówGdzie był ostatni klasztor Złych Tłumoków --- Ostatni oddział kręgu ósmego poeta nazywa klasztorem Złych Tłumoków, prawdopodobnie bez głębszej myśli, bo duchy są tam jak wszędzie w piekle zamknięte jak mnichy za furtą klasztorną.,/ +Krzyki boleści, co w mej czułej duszy,/ +Ile ich było, razem się zmieniły/ +W żelazne strzały, tak serce raniły,/ +Tak, że rękoma zatykałem uszy./ +W miesiącu sierpniu społem tłum zebrany/ +Chorych z Maremmy i WaldikijanyWaldikiana --- okolica błotnista i bardzo niezdrowa dla jej mieszkańców; leży między Arezzo a Kortoną. Oprócz Sardynii, gdzie w środku lata powietrze równie jest niezdrowe, poeta wspomina błota, tak zwane Maremmy, rozciągające się wzdłuż brzegów morskich między Pizą a Sieną./ +I co w sardyńskich szpitalach się mieści,/ +Dałby nam chyba obraz tych boleści./ +Z jamy buchały podobne wyziewy,/ +Jakimi dyszedysze --- dziś popr. forma: dyszy. zgangreniałezgangreniały --- toczony gangreną. ciało./ +I zwróciliśmy krok nasz na brzeg lewyI zwróciliśmy krok nasz na brzeg lewy --- Poeci znowu zstępują z łuku mostowego na tamę, ażeby mogli, będąc bliżej otchłani, widzieć lepiej, co się w niej dzieje. Czytelnik może często pyta sam siebie: jak Dante w ciemnej nocy piekielnej mógł coś widzieć? Sam poeta wyraźnie tego nie objaśnia; być może, że ogień, który widzieliśmy w różnych miejscach piekła, a mianowicie ten, co oświeca cnotliwych i uczonych mężów pogańskich, ten, który spada na czyniących gwałt przeciw Bogu, ten, co się pali w grobach odszczepieńców religijnych, na koniec ten, w którym fałszywi doradcy są ukryci, tyle światła po wszystkich kręgach piekielnych rozrzuca, że tworzy się jakiś zmrok pół widny, przez który można coś widzieć. A może ci, których przewodnikiem jest rozum (jak tu Dantego prowadzi Wergiliusz, symbol rozumu), zupełną nocą nie są otoczeni i widzą jak w półzmroku.,/ +Który się długo jedną ciągnie skałą;/ +KaraSpojrzałem w otchłań, gdzie na dnie jej łoża/ +Karze fałszerzy sprawiedliwość boża./ +Nie mógł smutniejszy być widok w Eginie,/ +Gdzie tysiącami chory lud wymierał,/ +A mórmór (daw.) --- zaraza, pomór. zwierzęta i płazy pożerał;/ +Gdzie, jeśli wierzyć na poetów słowo,/ +Z nasienia mrówek wylęgły na nowo/ +Lud ją zaludnił, zwany Mirmidony,/ +Jak widok duchów w tej ciemnej dolinie,/ +Jakby stos trupów bezładnie zrzucony./ +Ten brzuchem cały do ziemi przypada,/ +Ów głowę skłonił na ramię sąsiada,/ +Ci na czworakach pełzają jak gady./ +A szliśmy milcząc jako dwa niemowydwa niemowy --- dziś popr.: dwie niemowy.,/ +Słuchając, patrząc na chore gromady,/ +Niezdolne dźwigać bezwładne tułowytułowy --- dziś popr. forma: tułowia../ +Dwóch wzajem na się opartych siedziałoDwóch wzajem na się opartych siedziało --- Dwaj alchemicy, jako fałszerze złota, siedzą wzajem o siebie oparci, okryci trądem i przez całą wieczność drapią aż do krwi bolące ich strupy.,/ +Strupy im całe cętkowały ciało;/ +Jak zgrzebłem żywo pociąga stajenny/ +Przed przyjściem pana, choć ziewa półsenny,/ +Tak ci po strupach paznokciami wodzą,/ +Przez co bolesne swe świerzby łagodzą,/ +Przeciwko którym już nie było środka;/ +Jak pod kucharskim nożem pstrąg lub płotka/ +Miecemiece --- dziś popr. forma: miota. łuskami, tak strupów kawały/ +Spod ich paznokci jak łuska leciały./ +«Ty, co jak garbarz przez smutną konieczność,»/ +Rzekł wódz mój, «skórę chropawą pryszczami/ +Wyprawiasz, gładząc paznokcia ostrzami,/ +Mów, czy się spotkam tutaj z Latynami?/ +Oby twój paznokćpaznokć --- dziś popr. forma: paznokieć; tu forma skrócona dla zachowania rytmu jedenastozgłoskowca. nie stępiał przez wieczność!»/ +--- «Tutaj w nas obu dwóch Latynów witasz!»/ +Rzekł jeden z płaczem, «ktoś ty, co nas pytasz?»/ +Mój wódz tak mówił: «Jestem duch i oto/ +Schodzę tu razem z żyjącą istotą,/ +By całe piekło poznał chodząc ze mną»./ +Dwa cienie łamiąc podporę wzajemną,/ +Ze drżeniem ku mnie zwrócili sięDwa cienie (...) zwrócili się --- dziś popr. forma czasownika: zwróciły się. oba;/ +Na wieść tę wszyscy zerwali się społem./ +Mistrz rzekł: «Mów teraz, co ci się podoba»./ +Rad z przyzwolenia tak mówić zacząłem:/ +«Niech pamięć o was czas, co wszystko gładzi/ +Wskrzesi od wschodu słońca do zachodu;/ +Czary, Grzech, KaraKto wy jesteście, z jakiego narodu,/ +Że wśród mąk takich wam tu serce radzi/ +Przede mną z chęcią otworzyć się szczerą?»/ +Cień rzekł: «Arezzo jest moja krainaCień rzekł: Arezzo jest moja kraina --- Griftolino, rodem z miasta Arezzo, alchemik, utrzymywał, że umie latać. Gdy Albero z Sieny, naturalny syn tamtejszego biskupa, udał się doń, aby go tej sztuki nauczył, a to się oczywiście nie udało, wymógł na swym ojcu, że kazał alchemika spalić na stosie jako czarownika./ +Kazał mnie spalić z Syjeny Albero,/ +Chociaż tu inna wtrąciła mnie wina./ +Wprawdzie z nim mówiąc napomknąłem żartem,/ +Że ze mnie dzielny powietrzny latawiec;/ +On człowiek małej głowy a ciekawiecciekawiec --- dziś popr.: ciekawski. ,/ +Chciał, abym odkrył sztuki tajemnicę;/ +A że nie byłem Dedalem w praktyce,/ +Spłonąłem za to, żem kumał się z czartem./ +A że był ze mnie alchemik imienny,/ +Minos niemylny w sędziego pojęciu,/ +Wtrącił mnie w Tłumok ostatni z dziesięciu»./ +«Oprócz Francuzów,» mówiłem poecie,/ +«Więcej próżnego narodu na świecie/ +Nie ma zaiste jako lud z Syjeny»./ +Wtenczas to słysząc, drugi trędowaty,/ +Mówił: «Z nich pierwszym wyjątkiem jest StrikkaZ nich pierwszym wyjątkiem jest Strikka --- Tu poeta ironicznie wyszydza wszystkich głośnych marnotrawców i rozgardiaszów sieneńskich.,/ +Który wydawał pieniądz przyzwoicie,/ +Trwonił kapitał, gdy brakło intraty;/ +Drugim Nikolo, co pierwszy użycie/ +Zbytkowne odkrył wonnego goździkaużycie zbytkowne odkrył wonnego goździka --- Dowiadujemy się tu od poety, że Nikolo pierwszy smakosz sieneński, pierwszy korzenne zaprawy do sosów i pieczeni wprowadził w użycie./ +Mógłbym wyłączyć inne pasibratyinne pasibraty --- Za czasów Dantego w Sienie było towarzystwo złożone z dwunastu młodych i bogatych ludzi, którzy sprzedawszy swoje posiadłości ziemskie, złożyli wielki kapitał, ażeby wspólnie go roztrwonić i zarazem żyć wesoło. Kapitał ten, jak mówi Benvenuto d'Imola, składał się z 216 tysięcy florenów, a w przeciągu niespełna roku już go marnotrawcy stracili.,/ +Z tych Asciano winnicę bogatą,/ +Wielki las przejadł i Abbagliato/ +Dowiódł, że jemu w głowie niepstrokato./ +Lecz iżbyś wiedział, kto twoim wykrzykom/ +Taki wtór trzyma przeciw sieneńczykom,/ +Skieruj twe oczy do mojej figury,/ +A w twej pamięci rysy me odżyją./ +Ja za pomocą alchemiji złoto/ +Rad fałszowałem, jam cień Kapokijozłoto rad fałszowałem, jam cień Kapokijo --- Kapokio, sieneńczyk, znany osobiście poecie, miał jakoby razem z Dantem uczyć się fizyki i historii naturalnej.!/ +Możesz przypomnieć, że byłem niecnotą/ +A przy tym małpą wyborną z natury». + + + + +Pieśń XXX + + + +(Ciąg dalszy.) + + + +Morderstwo, GrzechGdy za Semelę gorączką obrazySemele --- córka Kadmusa, która z Jowisza urodziła Bachusa. Junona przez zazdrość prześladowała za to cały ród Kadmusa, a szczególnie jej siostrę Ino, mamkę Bachusa. Atamas, mąż jej, rozwścieklony gniewem pozabijał jej dzieci. Tak była ukarana nienawiść macosza, z jaką Ino dzieci z pierwszej żony Atamasa prześladowała./ +Junona wrzała przeciw krwi tebańskiej,/ +Spędzając na nich gniew po wiele razy,/ +Widząc swą żonę Atamas szatański/ +Wchodzącą w progi z dwojgiem małych dzieci,/ +Krzyknął jak wściekły: «Rozciągajmy sieci,/ +Lwica z lwiątkami w ich przeguby wleci»./ +I jedno dziecko, Learkiem nazwane,/ +Porwał i cisnął o twardą skał ścianę,/ +A matka, myśl tę snadź rozpacz natchnęła,/ +Z drugim ciężarem swoim utonęła./ +Kiedy Fortuna twarz swą odwróciła/ +Od wielkich Trojan, tak że jednej chwili/ +Społem królestwo i króla stracili,/ +Hekuba smutna, niewolnica chora,/ +Płacząc nad ciałem swego PolidoraHekuba smutna (...) nad ciałem swego Polidora --- Hekuba, żona Priama, opłakująca zwłoki syna swojego.,/ +Jak pies szalona w swej boleści wyła,/ +Tak boleść wszystkie jej zmysły zmąciła!/ +Nienawiść, KaraJednak Tebanie i Trojanie wściekli,/ +Choć tyle zwierząt i ludzi wysiekli,/ +Nie pastwili się z okrucieństwem takiem:/ +Dwa cienie nagie biegły jednym szlakiem,/ +Gryząc się w biegu, jak wieprz wszystkim w oczy/ +Rzuca się, gdy z swej zagrody wyskoczy./ +Jeden z nich, biegnąc, wpadł na Kapokiję,/ +W kark jemu pięścią grzmotnął ponad uchem,/ +Zwalił, po ziemi ciągnął go za szyję,/ +Gracując drogę Kapokija brzuchem./ +A Griffolino, z przestrachu wielkiego/ +Drżąc, mówił do mnie: «To duch Jana SkikiTo duch Jana Skiki --- Jan Skika, z familii Kawalkantych, rodem florentyńczyk: miał talent naśladowania głosu i gestów swoich znajomych, którego często na złe używał. Będąc przyjacielem Szymona Donata, któremu umarł jego krewny Buoso (zob. Pieśń XXV, gdzie on między złodziejami się znajduje) bez testamentu, a nie mógł po nim brać spadku z powodu, że Buoso zostawił bliższych krewnych, Szymon Donati, ażeby zostać spadkobiercą, przez kilka dni ukrywał ciało zmarłego Buosa, rozgłosiwszy, że jeszcze złożony chorobą oczekuje bliskiego zgonu, a w miejsce zmarłego położył w łóżko swojego przyjaciela Jana Skikę, który udając głos i postać Buosa, zrobił testament, pisząc w nim spadkobiercą po Buosie Szymona Donata, z którym zawarł uprzednio umowę, że w nagrodę dobrze odegranej roli weźmie klacz wielkiej ceny z jego stada.,/ +Tak wszystkich dręczy ten szaleniec dziki»./ +--- «Jeśli ten drugi duch cię nie uszczyknieuszczyknie --- dziś popr.: uszczknie; tu forma wydłużona dla zachowania rytmu jedenastozgłoskowca./ +Zębem lub szponą,» wołałem do niego,/ +«Powiedz mi wprzódy, kto on jest, nim zniknie»./ +A on: Kłamstwo, Grzech«To Myrry starożytnej duszaMyrra --- córka króla Cypru, winna tej zbrodni, jaką tu opisał poeta, po jej odkryciu przeklęta i wygnana przez ojca, uciekła do Arabii, gdzie długo i póty opłakiwała swój występek, aż od łez i żalu zamieniła się w gumę drzewną od jej imienia nazwaną myrra.,/ +Która do ojca czuła upał żywy,/ +Przeciwko prawu miłości uczciwej;/ +Pragnąc grzech ukryć w kryjomym niewstydzie,/ +Ojca pod cudzą postacią spokuszaspokusza --- dziś popr. forma: kusi; tu forma dokonana, wydłużona dla zachowania rytmu jedenastozgłoskowca.,/ +Podobnie jak duch, który oto idzie!/ +Znęcony zyskiem końskich stad królowy,/ +Wybieg zaiste cudacki i nowy,/ +Zamiast Donata w łóżko się położył,/ +Skłamał testament i znów z grobu ożył»./ +Gdy znikli z oczu mych ci dwaj okrutni,/ +DuchWnet odwróciłem ciekawe spojrzenie,/ +Patrzeć na inne tam leżące cienie./ +Jeden cień byłby podobny do lutniJeden cień byłby podobny do lutni --- Lutnia ma okrągły wystający brzuch, a cienką szyję; w tym więc podobieństwo do chorego na wodną puchlinę., / +Gdyby miał otwór jego brzuch obrzydły,/ +W miejscu, skąd ciało rozdziela się w widły:/ +Puchlina wodna w jego ciele całem/ +Zmieniając kształty przez wilgoci chore,/ +Rozwarte usta zwiesiła do brody,/ +Jako suchotnik, gdy pragnieniem gore./ +«Wy, których wita z podziwem ta jama,»/ +Cień mówił, «patrzcie na mistrza AdamaMistrz Adam --- rodem z Brescii, na żądanie hrabiego z Romeny, chcącego fałszerstwem monety poprawić zły stan swojego skarbu, fałszował złote floreny mieszaniną podlejszych metali, za co był na stosie ogniem spalony. Puchlina wodna oznacza chorobliwy stan finansowy tych krajów, co zniżaniem wewnętrznej wartości monety chcą sobie chwilowo dopomóc. Przypomnienie tego, co mistrz Adam miał niegdyś, a czego teraz brak czuje przez własną winę, piękne jest i prawdziwe! Tasso, który często w cudze odziewał się pióra, miał wyraźnie to miejsce przed oczyma w Jerozolimie Wyzwolonej, Pieśń XIII.!/ +Czegom zapragnął, żyjąc wszystko miałem,/ +Teraz, niestety, pragnę kropli wody./ +Małe strumyki, co żywią nurt Arny,/ +Płynąc z pagórków zielonych Kasenty,/ +Z rzeźwiącą treścią, przejrzyste aż do dna,/ +Ciągną tam oczy tu z wieczności czarnej;/ +Bo ich obrazu marzone ponęty/ +Więcej mnie trawią jak puchlina wodna./ +Tu Sprawiedliwość z swoim sądem w zgodzie,/ +Tym samym miejscem, gdziem grzeszył, mnie bodzie,/ +Kłamstwo, Grzech, KaraBy więcej westchnień wydostać z grzesznika./ +Tam jest Romena, gdziem oprócz psot wiela/ +Fałszował pieniądz z popiersiem Chrzciciela,/ +Za co spalono żywcem fałszownika./ +Lecz gdybym spotkał tu duchy Gwidona/ +I Aleksandra, i obu ich brataGwidon, Aleksander --- hrabiowie z Romeny.,/ +Jeszcze bym tego nie mieniał widoku/ +Na Fontebrandę, co stoi w mym okuFontebrandą --- wspaniała fontanna w Sienie../ +Już jeden pono tu zstąpił ze świata,/ +Jeśli wieść do mnie doszła nieskażona/ +Z ust innych cieniów; ale co mi po tem,/ +Gdy ja tu leżę jak przybity młotem!/ +Gdybym tak lekki był, że zrobić mogę/ +W sto lat krok jeden, już poszedłbym w drogę,/ +Szukając jego w tej wielkiej przepaści./ +Która w obwodzie ma mil jedenaście/ +A wszerz pół mili. Zły duch, co w nich siedzi./ +Skusił mnie radą tych hrabiów Romeny,/ +Abym w mennicy bił takie floreny,/ +W których jest najmniej trzy karaty miedzi»./ +Rzekłem do niego: «Wskaż tych dwóch na imię,/ +Co tam na prawo leżą razem w dymie,/ +Jak zimą dymiąc parą z mokrej ręki?»/ +--- «Tak ich znalazłem,» odpowiedział «wtedy,/ +Gdy mnie w tę otchłań wtrącono na męki,/ +I wątpię, z miejsca czy ruszą się kiedy?/ +To cień świadczącej krzywo PutyfaryTo cień świadczącej krzywo Putyfary --- Żona Putifara i Sinon Grek, oboje fałszywie świadczący; ta Józefa, którego chciała wciągnąć do winy, fałszywie oskarżyła przed mężem; a ten fałszywą wieścią o koniu drewnianym, którego Grecy zostawili pod murami Troi, oszukał Trojan. Za to oboje w zgniłej gorączce tarzają się tu po ziemi.,/ +Drugi Grek Sinon, co oszukał Troję;/ +W zgniłej gorączce tu leżą oboje,/ +Z ciał swych cuchnące wyziewając pary»./ +Cień pierwszy zemstę warzący w milczeniu,/ +Że śmiał bezczelną nazwać po imieniu,/ +Pięścią w brzuch twardy Adama uderzył,/ +Który jak bęben huk daleki szerzył,/ +Mistrz Adam wzajem dłonią niemniej twardą/ +Odbił policzek mówiąc doń ze wzgardą:/ +«Choć mi puchlina poruszyć się broni,/ +O! do wybitej dość ruchu mam w dłoni»./ +Na to cień drugi: «Gdyś na stos wstępował,/ +Pewnoś tak żywo ręką nie szermował,/ +Lecz miałeś równie, może więcej, żywą/ +Rękę, gdy biłeś monetę fałszywą»./ +Opuchły tak rzekł: «Nie kłamie twe słowo,/ +Lecz gdzieś był z swoją prawdomówną mową,/ +Kiedy o prawdę pytano się w Troi?»/ +--- «Jam fałsz powiedział,» Grek odparł na nowo,/ +«A tyś fałszował pieniądz najniegodniej./ +Jam winny jednej, a ty mnóstwa zbrodni»./ +--- «Czy koń drewniany na myśli ci stoi?»/ +Cisnął żart mówca, co miał brzuch wydęty,/ +«Świat za mną woła: bądź, kłamco, przeklęty!»/ +--- «Niech ci nawzajem» tak Grek mówił w gniewie/ +«Język pragnienie spali jak zarzewie,/ +Niech zgniła woda tak twój brzuch wydyma,/ +By jak zagroda stał ci przed oczyma»./ +A mincarzmincarz a. mincerz --- rzemieślnik zajmujący się biciem monet i medali.: «Bluźnisz na twą gębę całą,/ +Bo jeśli pragnę i mam spuchłe ciało,/ +Tyś sam w gorączce i gore ci głowa./ +Ażebyś lizał Narcyza zwierciadłoZwierciadło Narcyza --- woda, w której przeglądał się piękny młodzieniec Narcissus.,/ +Krótka by ciebie skłoniła przemowa»./ +Gdym był zajęty ich kłótnią zajadłą,/ +Mistrz rzekł: «Czas, abyś tę gawiedź porzucił,/ +Niewiele braknie, bym z tobą się skłócił»./ +Na głos ten jam się do mistrza obrócił,/ +Wstyd, Wina, SłowoOgromnym wstydem spłoniony na twarzy;/ +Podobny temu, co nieszczęście marzy,/ +A marząc życzy, by to, co się śniło,/ +Snem się rozwiało, jakby nic nie było./ +Chciałem przemówić, wstyd mi uciął słowa,/ +Choć wola z winy tłumaczyć się radzi,/ +Ze wstydu przed nim stałem jak niemowa./ +Mistrz rzekł: «Wstyd mniejszy większe winy gładzi,/ +Uspokój siebie i nie patrz tak smutnie:/ +Gdy czasem trafisz na podobne kłótnie,/ +Gdzie ludzie w mowie nie dosyć są skromni,/ +Że, wódz twój, jestem przy tobie, przypomnij!/ +Słowo, MądrośćBo chcieć łakomie słuchać lada bredni,/ +Chętka ta zdradza umysł dość powszedni». + + + + +Pieśń XXXI + + +(Do kręgu IX. Studnia. Olbrzymi. Anteus osadza poetów na dnie piekła.) + + + +Język, co wprzódy zranił mnie do tyla,/ +Żem spłonął wstydem, wnet balsam żądany/ +Podał i leczył jak włócznia Achilla,/ +Co i raniła, i leczyła ranywłócznia Achilla, co i raniła, i leczyła rany --- Achilles zranił króla Telefosa, a przyłożenie do rany tejże włóczni, która ją zadała, uzdrowiło rannego../ +DźwiękI milcząc szliśmy od tej smutnej jamy,/ +Brzegiem krążącej wokoło niej tamyBrzegiem krążącej wokoło niej tamy --- Dziesięć oddziałów zwanych Złe Tłumoki z rozmaitymi rodzajami oszustów, jacy w nich się znajdują, zwiedziwszy, teraz idą poeci wokoło kamiennej tamy okrążającej ostatni ten oddział kręgu ósmego; żeby przybliżyć się do dziewiątego kręgu piekła, w którym karani są zdrajcy. Jest to ta studnia, o której poeta napomyka na początku Pieśni XVIII.;/ +Tam nie noc, zmrok był pół nocny, pół dzienny,/ +Widziałem tylko szary grunt kamienny,/ +Lecz echo za mną dźwiękiem rogu grało,/ +Dźwiękiem, co mógłby zagłuszyć trzask grzmotu./ +W stronę, skąd dźwięk ten pobrzmiewał za skałą,/ +Oczy z uwagą obróciłem całą./ +Nie tak straszliwie Roland do odwrotu/ +Dął w róg wojenny, gdy z chrześcijan żalem/ +Karloman przegrał bój pod RonsewalemKarloman przegrał bój pod Ronsewalem --- Karol Wielki (Karloman, Charlemagne, Carolin Magnus) gdy walczył z Maurami z Hiszpanii, zdradzony przez jednego ze swoich wodzów, przegrał bitwę pod Roncewalem. Wtedy słynny rycerz Roland tak potężnie zadął w róg, że dźwięk tego rogu więcej jak w okręgu milowym najwyraźniej słyszano./ +Podniosłem głowę; nad poziom opoki,/ +Zda się z jej gruntu, tłum wieżyc wyrasta,/ +Dlatego rzekłem: «Widzę mur wysoki,/ +Mistrzu, jakiego to widok jest miasta?»/ +A mistrz: «Tyś w błędzie, bo patrzysz z daleka,/ +Gdy przyjdziesz bliżej, uzna twa powieka,/ +Jak oddalenie fałszuje zmysł wzroku,/ +Więc trochę więcej przyspiesz twego kroku»./ +Wziął mnie za rękę i czule ją ściska,/ +I mówi: «Nim ten przedmiot ujrzysz z bliska,/ +PotwórAby mniej dziwnym zdał się, jak widzimy,/ +Wiedz, że nie wieże to są, lecz olbrzymy,/ +Od stóp po biodra sami i bezludni,/ +Wokoło zrębów stoją w wielkiej studni»./ +Jak wzrok wśród mglistej uwięzły ciemnoty/ +Z wolna odkrywa ukryte przedmioty,/ +Gdy mgła spadając ze światłem się zetrze;/ +Tak przerzynając to ciemne powietrze,/ +W miarę jak szedłem pod studni tej progi,/ +Błąd mój się rozwiał, a wzrastał chłód trwogi/ +Potwór, OlbrzymJak wieże zamku MonteregijoneMonteregione --- zamek w okolicach Sieny./ +Tworzą okrągłych jego ścian koronę,/ +Tak nad zrąb studni pod same podpasy/ +Sterczały wkoło potworne Gigasy,/ +A którym zda się jeszcze groził z góry,/ +Piorunem Jowisz, gdy brzmi z ciemnej chmury,/ +Jeden stał twarzą do nas obrócony,/ +Barkami, piersią i dwoma ramiony;/ +Zaiste, jeśli natura przestała/ +Tworzyć olbrzymy, w tym loicznieloicznie --- dziś popr.: logicznie. działa,/ +Bo przez to, gwoli świata pokojowi,/ +Takich siłaczy odjęła Marsowi./ +A gdy bez troski zapładza w swym łonie/ +Jeszcze ogromne wieloryby, słonie,/ +Łatwo w tym dojrzy ludzka przenikliwość/ +Mądrą ostrożność jej i sprawiedliwość./ +Bo gdzie się rozum człowieka zespoli/ +Z siłą potęgi i chęcią złej woli,/ +Tam ludziom opór nigdy się nie uda./ +Czaszka tak wielka była wielkoluda,/ +Jakby świętego Piotra szyszka w Rzymieświętego Piotra szyszka w Rzymie --- Szyszka pozłocona, spiżowa, 2 i 1/2 metrów wysoka, zabytek starożytności, znajdowała się wtedy przed kościołem św. Piotra w Rzymie, dzisiaj umieszczona na ostatnim dziedzińcu Pałacu Watykańskiego../ +I inne kości miał równie olbrzymie:/ +Tak, że począwszy od pasa w pół ciała,/ +Trzech Fryzów rosłych, daremna ich chluba,/ +Ledwo by wzrostem doszli mu do czubaTrzech Fryzów rosłych (...) Ledwo by wzrostem doszli mu do czuba --- Fryzowie, mieszkający w północnych Niemczech, w średnich wiekach uchodzili za wielkoludów.;/ +Bo jego wzrostu potworna wyżyna,/ +Mogła, jak studni kończyły się zręby,/ +Do miejsca, człowiek gdzie płaszcz swój zapina,/ +Mieć miar trzydzieści wielkiej rzymskiej palmypalma rzymska --- miara nieco większa od stopy paryskiej../ +SłowoRapho Lmaj Amec! jakaś mowa brzmiała/ +Dzika, nam obca, z jego dumnej gęby,/ +Dla której słodsze niestosowne psalmy./ +Mój wódz doń mówił: «Nieroztropny duchu!/ +Zadmij w róg, niech się rozlega w twym uchu/ +Łowczy dźwięk rogu; jeśli tobą miota/ +Gniew albo inna namiętność żywota,/ +Ulgi twej męce szukaj w jego brzmieniu:/ +Patrz, on z twej szyi zwisa na rzemieniu,/ +Który ogromną twoją pierś oplata»./ +Potem rzekł do mnie: «Patrz, to duch NemrodaNemrod --- władca asyryjski, sławny łowca, znajomy z ksiąg starego Zakonu, który budowaniem wieży Babel winien był podstępnego buntu przeciwko Bogu.,/ +Głupi, sam siebie oskarża w swej dumie,/ +Że przezeń znikła jedna mowa świata./ +Zostawmy jego, tu słów naszych szkoda,/ +My go nie pojmiem, on nas nie zrozumie»./ +Grzech, KaraI szliśmy kołem, zwracając się w lewo./ +Z dala sterczało jakieś wielkie drzewo,/ +Podchodzim, widzim olbrzyma drugiego,/ +Jeszcze był dzikszy i wzrostu większego./ +Jaki go siłacz łańcuchem skrępował,/ +Nie wiem, lecz obie miał związane ręce,/ +Łańcuch od szyi pięć razy zagięty/ +Ciało mu ściskał żelaznymi pręty./ +«Pyszny, z Jowiszem o władzę szermowałszermować --- walczyć; por. szermierka.,»/ +Tak mój wódz mówił, «a skończył na męce:/ +To EfialtesEfialtes --- ze swoim bratem Otusem, zwaliwszy Pelion i Ossę, górę na górę, wdzierali się po nich do nieba. Młody Apollo przebił go strzałą. Olbrzym ten ręce ma związane, lecz za ich poruszeniem ziemia drży cała.! w walce z olbrzymami/ +Przed nim zadrżeli i bogowie sami,/ +Myśląc, że pierwszy drzwi niebios wyłamie;/ +Teraz bezwładne to straszliwe ramie»./ +A ja do wodza: «Chciałbym na swe oczy/ +Widzieć potworny kształt BryjareuszaBriareusz --- sturamienny olbrzym, syn Urana i Ziemi. Anteusz, syn Ziemi, która, ledwo wyszedł na świat, obdarzyła go nadzwyczajną siłą. Herkules zmuszonym był go zadusić, ponieważ Anteusz, jako wróg nieubłagany Herkulesa, z nim się wciąż mocował. Olbrzym ten nie ma rąk związanych, ponieważ nie brał udziału w wojnie olbrzymów z bogami.»./ +Wódz rzekł: «Tu bliżej ujrzysz Anteusza./ +Nieskrępowany i mówić ochoczy,/ +On nas jak piłkę na dno piekła stoczy./ +Strach, ZłoLecz Bryjareusz dobrze dalej stoi,/ +Jak ten podobnie skuty, prócz że dwoi/ +Przestrach wrażeniem okropności swojej»./ +Nigdy tak wieżą gwałtownie nie chwiała/ +Trzęsieniem ziemi poruszona skała,/ +Jak Efialtes wstrząsł się na te słowa;/ +Wtenczas mnie trwoga przeszyła grobowa,/ +Strach by mnie dobił i padłbym bez duchu,/ +Gdybym nie widział olbrzyma w łańcuchu./ +Anteusz, gdyśmy podeszli doń bliżej,/ +Nad zrębem studni stał pięć łokci wyżej./ +---Zdrada«O ty, co w owej szczęśliwej dolinieO ty, co w owej szczęśliwej dolinie --- Mieszkanie Anteusza naznacza poeta, według Lukana, w okolicy, w której Hannibala Scypion pokonał./ +Przez którą imię Scypijona słynie,/ +Gdzie Hannibala zmusił do odwrotu,/ +Uprowadziłeś do swego namiotu/ +Łup z lwów tysiąca; ty, co gdybyś zbrojnie/ +Wsparł twoich braci w ich olbrzymiej wojnie,/ +Mógłbyś zapewnić tryumf synom ziemi!/ +Jeśli nie gardzisz prośbami naszemi,/ +Znieś nas, gdzie Kocyt chłód zamarzać zmusza,/ +Lecz nie odsyłaj mnie do TyfeuszaTyfeusz --- Inny olbrzym../ +Stąd mój towarzysz, czego chce twa dusza/ +(Tylko nam schyl się i nie marszcz tak czoła),/ +Sławne twe imię zaniesie do świata;/ +On żyw i przed nim jeszcze długie lata,/ +Gdy go przed czasem Łaska nie powoła»./ +Gdy mowa mistrza zamilkła skończona,/ +Olbrzym już wodza wziął w swoje ramiona,/ +Których sam Herkul czuł uścisk straszliwy!/ +Z ramion olbrzyma rzekł do mnie Wirgili:/ +«Chodź, ja do swego przytulę cię łona»./ +Tak mnie i wodza Anteusz tej chwili/ +Jak jeden ciężar na plecy zarzucił./ +Zdrada, KaraPatrząc na wieży bolońskiej kąt krzywyPatrząc na wieży bolońskiej kąt krzywy --- La Carisenda, wieża pochyła w Bolonii, ma sto trzydzieści stóp wysokości,/ +Od strony, kędy ku ziemi się chyli,/ +Widz drży, by czasem wiatr ją nie wywrócił:/ +Tak mi się wydał olbrzym i tak strwożył,/ +Gdy się nachylił i nas obu złożył/ +Na dnie otchłani, co razem pożera/ +Zdrajcy Judasza duch i Lucyfera;/ +Tak pochylony olbrzym w tym momencie/ +Podniósł się prosty jak maszt na okręcie. + + + + + +Pieśń XXXII + + + +(Krąg IX. Wieczne lody. 1. Kaina. Zdrajcy krewnych. 2. Antenora. Zdrajcy kraju rodzinnego.) + + + +PoezjaGdybym miał dzikie, tak chrapliwe rymy,/ +Co by do ciemnej tej studni przystały,/ +Na której stoją wszystkie inne skałyCo by do ciemnej tej studni przystały, na której stoją wszystkie inne skały --- Kamienna tama otaczająca krąg ósmy, dźwiga na sobie ciężar wyższych kręgów piekielnych, a szczególnie te skały, które wszerz przez oddziały rozmaite, czyli jamy kręgu ósmego, przechodząc, podróżującym poetom za łuki mostowe służyły.,/ +Wydałbym pełniej całą treść mej myśli;/ +Lecz gdy tej władzy w sobie nie widzimy,/ +Ważę się podnieść głos nie bez bojaźni./ +Nie jest to zamiar jak gra wyobraźni/ +Wylęgły w głowie, co dno świata kreślico dno świata kreśli --- Według systemu Ptolemeusza niebo ze wszystkimi planetami i gwiazdami krąży wokoło ziemi i dlatego to środkowy punkt ziemi uważa poeta za punkt środkowy całego wszechświata.,/ +Nie czyn języka, co ledwo szczebioce./ +Muzy, przyzywam ja wasze pomoce,/ +Dajcie mi skrzydła Amfijona lotuDajcie mi skrzydła Amfijona lotu --- Amfion zbudował mury tebańskie i siedem bram tegoż miasta. W tej olbrzymiej pracy dopomagały jemu Muzy; w Muzach szukał natchnienia do pieśni, na której dźwięk same kamienie ruszały się z miejsca i wiązały się w mury., / +Aby nie niższy był wiersz od przedmiotu./ +Mieszkańce miejsca, o przeklęte duchy!/ +Które opisać słowami najtrudniej,/ +Czemu was owcze nie kryją kożuchy/ +W tym chłodnym świecie albo kozie puchy?/ +Gdyśmy już byli w zrębach ciemnej studniGdyśmy już byli w zrębach ciemnej studni --- W lochu tej studni zdrajcy rozmaici karani są pogrążeniem ich na wieczność w bryłach piekielnego lodu. Najdalej są oni od Boga, źródła wszelkiego ciepła, światła i życia. Nawet łzy ich marzną, skrucha nie ma tu siły uśmierzającej. Lody powstają z rzeki Kocytu, którą tworzą odpływy gorącego Flegetonu, zlodowaciałe wskutek ruchu skrzydeł Lucyfera. Krąg ten dziewiąty podzielony jest na cztery oddziały: pierwszy, najbliższy nazywa się Kaina, od bratobójcy Kaina tak nazwany, gdzie są karani ci, co zdradzili bliźnich swoich, a szczególnie swoich krewnych. Po nim następuje oddział Antenora, gdzie spotykamy zdrajców ojczyzny, tak nazwany od trojańskiego Antenora, którego obwiniają, że brał udział w kryjomym porwaniu Palladium tegoż miasta. Trzeci oddział nazywa się Ptolomea od Ptolomeusza, króla Egiptu, który zdradził zaufanie wielkiego Pompejusza i prawo gościnności w czasie, kiedy był jego gościem. Oddział zaś czwarty nazywa się Judeki a od Judasza, który zdradził i zaprzedał Chrystusa Pana.,/ +Daleko niżej jak stopy olbrzyma,/ +Zrąb jej wysoki gdym mierzył oczyma,/ +«Ostrożnie stąpaj!» ktoś rzekł tymi słowy,/ +«Abyś omijał stopą nasze głowy»./ +Spojrzałem za się, zdziwion nie po małunie po mału --- niemało (daw. forma; por. po trochu).,/ +Zmarzłe jezioro leżało pode mną,/ +Lśniące jak szyba ogromna kryształu./ +Nigdy tak grubym lodem się nie ścina/ +Niemiecki Dunaj lub północna Dźwina;/ +Gdyby Tabernik albo PietrapianaTabernik (...) Pietrapiana --- Góry, pierwsza w Sklawonii, druga w Toskanii/ +Spadły przypadkiem w tę otchłań podziemną,/ +Szyba tych lodów stałaby jak ściana./ +Jak żaby skrzecząc wystają znad wody/ +W porze, gdy myśli żniwiarka o żniwie,/ +Tak cienie blade, skarżące płaczliwie,/ +W lód pogrążone sterczały nad lody/ +Pół twarzą, gdzie wstyd czerwieni jagody./ +Z dzikim hałasem jak klekot bociani/ +Brzmiał zgrzyt ich zębów po całej otchłani;/ +Skrzepłe ich usta wzrok ponuro toczy,/ +Smutek ich serca zdradzały ich oczy./ +Gdy spojrzę na dół, pod mymi stopami/ +Dwa cienie z sobą zwarły się piersiami,/ +Aż czub ich włosów przytykał do czuba./ +«Kto wy jesteście?» krzyknąłem. Wtem cienie/ +CierpienieUkośne ku mnie zwróciły spojrzenie,/ +Łzy, co w ich oczach przed chwilą pływały,/ +Spadły na rzęsy i chłodem stężały./ +Silniej nie ściska deskę z deską śruba,/ +Jak potępieńcy zwarli czoła swoje;/ +Jak dwa rogami bodące się byki,/ +Tak gniew ich obu był wielki i dziki./ +Cień, co od mrozu stracił uszu dwoje,/ +Schylając głowę rzekł do mnie łaskawie:/ +«Dlaczego na nas patrzysz tak ciekawie?/ +KaraChcesz wiedzieć, ci dwaj jakiego są roduci dwaj jakiego są rodu --- Aleksander i Napoleon degli Alberti po śmierci ojca robiąc dział między sobą, dwaj bracia, tak się skłócili, że szpadami nawzajem się przebili. Miejsce ich rodzinne leżało nad strumieniem Bisenzio, który przepływa dolinę Falterona między Lukką a Florencją.?/ +Gdzie zdrój Bisencjo wypływa, dolina,/ +Własność Albertich, ojczyzną ich była,/ +Taż sama, jedna matka ich rodziła./ +Nie znajdziesz w całym tym kręgu Kaina/ +Godniejszych siedzieć w bryłach tego lodu:/ +Nawet ten, którym wzdryga się natura,/ +Na wylot włócznią przebity ArturaNa wylot włócznią przebity Artura --- Morderek, syn bajecznego Artura, króla Brytanii, według podania czatował w zasadzce na swojego ojca, ażeby go zabić; lecz ten uprzedził zamach zbrodniarza i włócznią przebił swego syna na wylot, tak że przez otwór rany słońce świeciło.,/ +Ani Kancelieri, ani MaskeroniAni Kancelieri, ani Maskeroni --- Fokacia Kancelieri, rodem z Pistoi, odciął rękę swojemu wujowi, a potem go zabił. Maskeroni, florentyńczyk, też był zabójcą swojego krewnego../ +Jeśliś Toskańczyk, musisz znać, kto oni./ +O jeśli głos mój uszu twych nie kazi,/ +Wiedz, że ja jestem Kamerione z PazziKamicione de Pazzi --- zabił Ubertina, swojego krewnego.,/ +Że oczekuję tu jeszcze KarlinaKarlino de Pazzi --- stronnik białych gwelfów; zdradą zdał czarnym gwelfom zamek obronny, leżący nad rzeką Arno za pewną sumę pieniężną.!»/ +Widziałem potem z tysiąc innych twarzy,/ +Na pół skostniałych, zsiniałych od chłodu/ +Myśl jeszcze z dreszczem o tych lodach marzy!/ +Gdy ku środkowi szukamy przechoduprzechód --- przejście.,/ +Gdzie każda ciężkość cięży swoim ciałem,/ +W tym wiecznym zmroku z przerażenia drżałem./ +Nie wiem, przypadkiem czy wyższym zrządzeniem/ +Śród głów sterczących stawiącstawiąc --- dziś popr. forma: stawiając. stopy z trwogą,/ +Jednemu na twarz nastąpiłem nogą./ +«Za co mnie depczesz?» rzekł duch z bólu blady,/ +«Czy mścisz się na mnie Montaperto zdrady?»/ +«Czekaj mnie, mistrzu,» mówiłem z wzruszeniem;/ +«Chcę mą wątpliwość objaśnić z tym cieniem,/ +Potem, jak zechcesz, jam śpieszyć gotowy»./ +Wódz stanął, a jam wyzwał do rozmowy/ +Ducha, co bluźnił, nie szczędząc klątw długich:/ +Kłótnia«Powiedz, kto jesteś, ty, co fukasz drugich?»/ +--- «Ty sam kto jesteś? Za co w Antenorze/ +Idąc, twe stopy drugich twarze depcą?/ +Za co naciskasz krokiem tak bolącym,/ +Za ciężkim, nawet gdybyś był żyjącym?»/ +--- «Ja żyję,» rzekłem, «miło ci być może,/ +Gdy kochasz sławę lub wspomnienie skromne,/ +Wśród wielu imion twe imię przypomnę»./ +On na to: «Odejdź, natrętny pochlebco!/ +Pochlebne słowa, żal twego zachodu,/ +Nic nas nie grzeją na tych falach lodu»./ +Wziąwszy go za kark, rzekłem: «Jak cię zowązową --- dziś popr. forma 3 os. lm: zwą.?/ +Mów, bo wnet łysą zaświecisz mi głową»./ +--- «Rwij włos, nie powiem tobie, jak się zowię,/ +Choćbyś sto razy deptał mi po głowie»./ +Za czub schwyciłem jego w mgnieniu oka/ +I wyrywałem z czaszki włos garściami;/ +On słowa żadnej skargi nie wyrzucił,/ +Tylko wył dziko i oczy wywrócił./ +Drugi cień krzyknął: «Co tobie, mój BokkaCo tobie, mój Bokka --- Bocca degli Abbati; na początku bitwy pod Montaperti nad rzeką Arbią podstępem uciął rękę gwelfowi, Jakubowi Pazzi, który niósł sztandar stronnictwa; gwelfowie przerażeni upadkiem swojego sztandaru, który ich do boju prowadził, poszli w rozsypkę i bitwę przegrali. Dante, chociaż gibelin, potępia ten czyn nieszlachetny.?/ +Ty wyjesz, nie dosyć ci zgrzytać zębami,/ +Jakiż cię dręczy szatan tak zawzięty?»/ +--- «Teraz,» mówiłem, «milcz, zdrajco przeklęty!/ +Abyś twe imię powiedział, nie proszę,/ +Sam je na wieczną twą hańbę ogłoszę»./ +A on: «Zbrodnia, Grzech, KaraBaj sobie, co masz na języku,/ +Lecz nie zapomnij, idąc z Antenory,/ +Tego, co język miał w mowie za skoryskory --- szybki, prędki.;/ +Tu złoto Franków wtrąciło młokosa,/ +Możesz powiedzieć: widziałem BuosaBuoso da Duera --- rodem kremończyk, przekupiony przez generała francuskiego, Gwidona de Montfort, dał mu przejść przez rzekę Oglio./ +Tam, gdzie grzeszników jest w lodach bez liku./ +Gdy, kto jest więcej, spytają i za co?/ +Patrz, oto stoi Bekeria ladacoBekeria --- Bekeria, rodem z Pawii, opat Walombrozy; gdy odkryto spisek, za pośrednictwem którego miał Florencję oddać w ręce gibelinów, głowę mu ścięto./ +Któremu głowę Florencyja ścięła,/ +A trochę dalej: Gianni, Ganello,/ +Otwierający wrogom TribaldelloA trochę dalej: Gianni, Ganello, (...) Tribaldello --- Wszyscy ci trzej zdrajcy swojego kraju./ +Faencę, kiedy snem głuchym usnęła»./ +Gdy z miejsc okropnych szukamy przechodu,/ +Dwóch potępieńców ujrzałem w parowie,/ +Wyższy niższemu głową legł na głowie;/ +A jak łakomie szarpiemy chleb z głodu,/ +Tak on zatopił kły w ciało sąsiada,/ +Tam kędy czaszka do barków przypada./ +Nie z takim gniewem Tydej zemstą ślepyTydej zemstą ślepy --- Tydeusz podczas wojny tebańskiej wyzwał Menalipa na włócznie i obaj ranili siebie śmiertelnie; Tydeusz, według Stacjusza, konając, z wściekłością gryzł czaszkę swojego wroga, który tylko co skonał./ +Menalipowej głowy gryzł czerepy,/ +Jak on swą zdobycz żuje i wysysa./ +«Człowieku,» rzekłem, «co paszczą tygrysa/ +Mścisz się nad wroga nienawistną głową,/ +Powiedz mi, jakie masz zemsty powody?/ +A ja ci moją odpłacę wymową,/ +Kiedyś, pomiędzy ziemskimi narody,/ +Jeśli mnie Pan Bóg żywcem stąd wydźwignie,/ +A język w ustach moich nie zastygnie». + + + + + +Pieśń XXXIII + + + + + +(Krąg IX. 2. Antenora --- ciąg dalszy. 3. Ptolomea. Zdrajcy przyjaciół.) + + + +Duch, KaraOd strawy dzikiej oderwał paszczękę/ +Ów potępieniec i krew z ust ocierał/ +Włosami czaszki, której mózg pożerał./ +I mówi: «Srogą chcesz odnawiać mękę,/ +Serce mi pęka, nim usta otwieram./ +Lecz gdy ze słów mych jak z nasion dojrzeje/ +Hańba dla zdrajcy, którego pożeram,/ +Słuchaj, wypowiem, wypłaczę me dzieje./ +Nie wiem, kto jesteś, przez jaki cud nowy/ +Zaszedłeś do nas, lecz po dźwięku mowy/ +Poznaję w tobie Włocha, florentyna./ +Widzisz przed sobą hrabię UgolinaHrabia Ugolino --- pochodził ze starożytnej familii pizańskiej hrabiów della Gherardesca. Będąc podestą i naczelnikiem siły zbrojnej w Pizie, podniósł tę rzeczpospolitą do szczytu potęgi i sławy; potem gdy przegrał bitwę morską z genueńczykami, dla podtrzymania dawnej potęgi swojego kraju wiązał się traktatami z Florencją, której wskutek wzajemnej umowy kilka mało znaczących zamków ustąpił. Zazdrosny jego władzy, a więcej jeszcze sławy długoletnim rządem Ugolina nabytej, arcybiskup Rugieri posądził go o zdradę stanu; potem wsparty współdziałaniem hrabiów Gwalandi, Lafranki i Sismondi ze zbuntowanym ludem natarł na straż przyboczną Ugolina, uwięził go z dwoma synami i dwoma wnukami, a wszystkich zamknął w wieży na placu zwanym Degli Anziani. Bramę więzienia zamurowawszy, klucze od niej kazał wrzucić w rzekę Arno. Wieża ta od głodowej śmierci Ugolina nosi nazwę Wieży głodu. Było to 1289 r./ +A ten, co teraz jest mej zemsty łupem,/ +Zwał się Rudżieri, był arcybiskupem./ +Jak mnie w zdradzieckie usidlono słowa,/ +Jak nieostrożnie wpadłem w jego ręce,/ +Nie warto mówić, bo rzecz nie jest nowa./ +Lecz o mym zgonie, o mej strasznej męce,/ +Jeśli nikt wieścią uszu twych nie skaził,/ +Słuchaj i osądź, czy on mnie obraził./ +Śmierć, Grzech, Kara, GłódJest w głębi wieży podziemna pieczara,/ +Sławna mym zgonem; dziś może w niej jęczy/ +Na nowo jaka niewinna ofiara./ +Tam okiem witym z żelaznych obręczy,/ +Widziałem mnogich księżyców oblicze,/ +Aż mnie raz we śnie przywidziana mara/ +Zdarła przyszłości chmury tajemnicze./ +Przyśniło mi się, że biskup zawzięty/ +Polował wilka z małymi wilczęty,/ +Na owej górze, co wzniosłymi szranki/ +Z pizańską ziemią i Lukką graniczyNa owej górze, co wzniosłymi szranki z pizańską ziemią i Lukką graniczy --- Góra San Gitiliano na pograniczu Lukki i Pizy. Tam właśnie uwięziono Ugolina. Polityczna działalność Ugolina, w której ocenę tu bliżej wchodzić nie możemy, oczom wielu przedstawiała się jako zdrada kraju. Dlatego umieścił go Dante, który widocznie podzielał takie zdanie, tu, w Antenorze../ +Już chudą psiarnię zemkniono ze smyczy,/ +Hrabia Gwalandi, Sismondi, Lanfranki/ +Szczują na czele, zdobycz będzie łatwa:/ +Już wilk znużone zatrzymuje kroki,/ +Upada wreszcie i ojciec, i dziatwa:/ +I widzę kłami rozprute ich boki./ +Budzę się! Jeszcze noc nie zeszła z nieba,/ +Już moje dziatki, wspólniki niewoli,/ +Szlochają przez sen i wołają: »chleba!«./ +O! Jeśli dotąd serce ci nie boli,/ +Kiedy pomyślisz, co się we mnie działo/ +I co me serce nadal przeczuwało,/ +Jeśli nie płaczesz, któż ci łzy wyciśnie?/ +Budzą się dzieci, wkrótce chwila błyśnie,/ +W której nam zwykle udzielano strawy,/ +Lecz na sen pomnąc truchlałem z obawy./ +Wtem z bram więzienia łoskot mnie doleci,/ +Zamurowano! --- spojrzałem na dzieci,/ +Spojrzałem z niemej wyrazem rozpaczy;/ +A w głębi serca czułem mróz jak w grobie./ +Gwido mój mały wołał: »Co to znaczy,/ +Tak dziko patrzysz? Ojcze mój, co tobie?«/ +Nie mogłem mówić, ni łzy z oczu dostać,/ +Milczałem długo --- aż do nocy końca./ +Nazajutrz do nas zbłądził promyk słońca/ +I w twarzach dzieci ujrzałem mą postać./ +Natenczas z bólu gryzłem obie ręce./ +Synowie myśląc, że mnie głód tak pali,/ +Łamiąc rączęta ze łzami wołali:/ +»Ojcze kochany, ulżyj twojej męce,/ +Zjedz twoje dzieci, tyś nas ubrał w ciało,/ +Tobie nas biednych rozebrać przystało«. ---/ +Musiałem milczeć i ból w sobie morzyć,/ +Wkrótce i mowa w ustach nam zamarła!/ +Jęczeć nie śmiałem, by dzieci nie trwożyć./ +O ziemio, czemuś ty nas nie pożarła!/ +Weszło czwartego dnia światło zabójcze,/ +Anzelmek mały przywlekł się pod nogi/ +I przerażony wolał: »Ojcze drogi!/ +Ach! Czemu ty nas nie ratujesz, ojcze?«/ +Wołał i skonał! --- Jak mnie tu widzicie,/ +Tak ja widziałem wszystkie dzieci moje/ +Jedno po drugim --- wszystkich było troje,/ +Wszystkie u nóg mych zakończyły życie./ +Od zwłok jednego do drugiego biegłem,/ +Ślepy na trupach potknąwszy się ległem./ +Dzień jeszcze siódmy do słońca zachodu,/ +Krzyczałem z żalu, a na koniec --- z głodu,/ +Bo głód był jeszcze sroższy od żałościKrzyczałem z żalu (...) sroższy od żałości --- Cały ten ustęp głodowej śmierci Ugolina i jego dzieci dziwnie jest piękny i dramatyczny. Poeta tu jak w ustępie Franczeski umiał w porę zakląć okropność i na tym właśnie cała tajemnica dramatycznej sztuki zawisła. Wielu krytyków rozbiorowi tego ustępu Ugolina pod względem estetycznym i poetycznym swoje pióra poświęciło.»./ +Skończył i dziko wywróciwszy oczy,/ +Na nowo usta w krwawą czaszkę broczy,/ +I jak pies zębem zgrzytając, rwie kości. / +O Pizo! hańbo pięknej ziemi włoskiej,/ +Kędy si dźwięczyKędy ,,si" dźwięczy --- Wyrażenie powyższe odnosi się do tego, że w języku włoskim si znaczy ,,tak". tak miękkimi głoski;/ +Gdy cię nie karzą leniwe sąsiady,/ +Niechaj się nagle wzruszą z swej posady/ +Sąsiednie wyspy, Kapraja, GorgonaKapraja, Gorgona --- małe wyspy blisko ujścia Arna do morza.,/ +Zahaczą Arnę, gdzie jej ujście kona;/ +Niech tak szeroko roztoczy swe tonie,/ +Aż wszystkich twoich mieszkańców pochłonie./ +Bo jeśli wrogom Ugolino hrabia/ +Zdał twoje zamki i winien tej zdrady,/ +O nowe Teby! Cóż twa złość wyrabia?/ +Jakaż twą zemstę czarna pamięć szpeci,/ +Głodem niewinne morzyć jego dzieci!/ +Duch, CierpienieWidziałem drugich potępieńców z bliskaWidziałem drugich potępieńców z bliska --- Tu poeci wstępują w trzeci oddział tegoż kręgu, zwany Ptolomea, gdzie są karani ci, co zdradzili swoich krewnych i przyjaciół.,/ +Jak lód ich swymi bryłami naciska;/ +Nie stali w lodach, lecz wznak wywróceni/ +Na lodowatej leżeli przestrzeni./ +Tam łza zamarza w chwili, gdy wybłyska,/ +Boleść jak robak po ich wnętrzach toczy,/ +Bo jej nie mogą wypłakać przez oczy./ +Jak hełm z kryształu, skrzepła łez powłoka/ +Kryje pod rzęsą całą wklęsłość oka./ +Choć jak stwardniała z mrozu skóra muła/ +Twarz moja prawie stała się nieczuła,/ +Wiatr jakiś, czułem, obwiał moje ciało./ +«Mistrzu mój,» rzekłem, «mów, co tu powiało?/ +Czy wiatr w tym chłodzie jeszcze nie zastygnął?»/ +--- «Dowiesz się wkrótce, skąd wiatr aż tu śmignął,»/ +Mówił, «przyczynę gdy wzrok twój wyśledzi,/ +Oko wyręczy głos mej odpowiedzi»./ +Cierpienie, DuchZ tych nieszczęśliwych jeden tymi słowy/ +Do nas przemówił z skorupy lodowej:/ +«Dusze uwięzłe w swego grzechu matni,/ +Który was wtrąca aż w ten krąg ostatni,/ +Zerwijcie z twarzy mej twardą zasłonę,/ +Ulżyjcie bólem serce przepełnione,/ +Niechaj wyleję choć jedną łzę ciepłą,/ +Bo już mi oko i serce zakrzepło»./ +A ja: «Chcesz, abym ulżył ci w cierpieniu,/ +Kto jesteś, nazwij siebie po imieniu;/ +Gdy nie usunę twoich łez przeszkodę,/ +Bodajbym w lodach tych na wieki siedział!»/ +«Brat Alberigo jestem» odpowiedziałBrat Alberigo --- rodem z Faency, jeden z towarzystwa braci wesołych (zob. uwagę do pieśni XXIX), zaprosił krewnego swego z synkiem na ucztę i w chwili, kiedy podawano owoce i jagody, kazał ich zamordować. Stąd urosło włoskie przysłowie: ,,On kosztował owoców brata Alberigo".,/ +Zły owoc z mego wyrasta ogrodu/ +I tu za figę mam daktyl w nagrodęI tu za figę mam daktyl w nagrodę --- Ironia. Daktyl jako owoc z cudzej krainy uchodził za mający wartość większą od krajowej figi.»./ +Zdrada, Piekło, Szatan, Śmierć, Upiór--- «Czy już umarłeś?» rzekłem drżąc od chłodu,/ +A on: «Nic nie wiem, odkąd tu drętwieję,/ +Co z moim ciałem na świecie się dzieje./ +Bo Ptolomea ma te przywileje,/ +Że często dusza wpada w nią niebacznie,/ +Nim Parka przędzę rwać dni naszych zacznie./ +A gdybyś chętniej zdjął lód z moich powiek,/ +Wiedz, że gdy zdrady dopuści się człowiekWiedz, że gdy zdrady dopuści się człowiek --- Z dziwną ścisłością logiczną oznaczony tu stosunek między karą a występkiem. Kto zdradza tego, kto nam ufa, tego dusza w tej chwili staje się łupem kar piekielnych, do których jeszcze za życia wyrzuty sumienia, żal bezowocny i pogarda samego siebie wcześnie przygotowują. A ponieważ wynikająca stąd rozpacz zdrajcy często pędzi go na rozdroże jeszcze dzikszych i nikczemniejszych namiętności, przeto się zdaje, że w jego ciele już nie ludzka dusza, ale szatan zajął gospodę.,/ +Ciało dyjabłu odkazuje dusza,/ +Który nim rządzi, włada najzupełniej,/ +Nim się czas kary cielesnej wypełni;/ +Dusza zaś wpada aż w tę chłodną studnię./ +Może wam jeszcze jawi się w swym ciele/ +Cień, co tu ze mną w tych lodach się rusza:/ +Patrz, Branka d'Oria! Już lat przeszło wieleBranka d'Oria --- zabił w czasie obiadu teścia swego, Michała Zankę, którego wyżej spotkaliśmy w smolnym jeziorze. Morderca ten rodem był genueńczyk.,/ +Odkąd tu siedzi, wśród brył tego lodu;/ +Musiałeś kiedyś znać jego za młodu»./ +«Kłamiesz!» doń rzekłem «lub świadczysz obłudnie,/ +Bo Branka d'Oria nie umarł, on żyje/ +Jeszcze na ziemi, dobrze je i pije»./ +On odpowiedział: «W jamie Złych Tłumoków,/ +Tam gdzie widziałeś war smolnych potoków,/ +Mógł jeszcze nie być cień Sanchy Michała/ +W chwili, gdy Branka d'Oria szatanowi/ +Ustąpił swego pomieszkanie ciała/ +Jako wiernemu zdrady spólnikowi./ +Teraz gdym całą ufność w tobie złożył,/ +Otwórz me oczy!» Jam ich nie otworzył,/ +Bo względem jego nieszczerość tą razą/ +Nie była żadną szczerości obrazą./ +Wrogi cnót wszystkich, o genueńczyki!/ +Wstyd wam, pomiędzy takimi grzeszniki/ +Jednego ziomka waszego spotkałem,/ +Który, co czyny jego świadczyć muszą,/ +Na dno Kocytu pogrążył się duszą,/ +Gdy jeszcze życie kłamie swoim ciałem. + + + +Pieśń XXXIV + + + + +(Krąg IX. 4. Judekka. Najwięksi zdrajcy: Judasz, Brutus, Kasjusz, Szatan. Powrót do światła.) + + + + + +«Vexilla regis prodeunt inferniVexilla regis prodeunt inferni --- co znaczy: Oto zbliżają się sztandary piekielnego króla. Słowa te (początek hymnu śpiewanego w Wielki Piątek) w tekście oryginału są po łacinie./ +Wprost ku nam! Jeśli widzisz za pomroką»/ +Mistrz mówił, «patrzaj, wytężaj twe oko»./ +Kiedy noc naszą półsferę zaczerni/ +Lub ciemny tuman przedmioty powleka,/ +Myślim, że widzim młyn wietrzny z daleka;/ +Tak, zdało mi się, oko me postrzegło/ +Stojącą jakąś budowę odległą./ +Piekło, Szatan, ZimaWtenczas od wiatru szukając ochrony,/ +Stanąłem za mym wodzem, bo zasłony/ +Nie było innej; tam wśród wiecznej zimy,/ +Już byłem w miejscu, gdzie cienie widziałemJuż byłem w miejscu, gdzie cienie widziałem --- Poeci wchodzą już w ostatni oddział kręgu ostatniego, nazwany od Judasza: Judykka, gdzie karani są ci, co dopuścili się zdrady przeciw swoim dobroczyńcom. W środku tego oddziału i całego wszechświata, spotykamy czterech głównych zdrajców tego rodzaju: Disa czyli Lucyfera, naczelnika zbuntowanych aniołów, Judasza Iskariotę, Kasjusza i Brutusa.,/ +A co z przestrachem wpisuję w te rymy/ +Oblane lodem jak ździebło kryształem./ +Ten w lodach leży na wznak rozciągnięty./ +Ci prosto stoją, drudzy wspak na głowie,/ +Ten łukiem twarz swą nagina do pięty./ +Gdyśmy do tyla zaszli w to pustkowie,/ +Że już mojemu mistrzowi się zdało,/ +Pokazać szpetne, niegdyś piękne ciało,/ +Zwrócił się do mnie i mówił z powagą/ +«Oto Lucyfer, oto krąg przeklęty!/ +Teraz się cały uzbrajaj odwagą»./ +StrachJakiem ja wtedy osłabnął i skolałskolał --- skołowaciał; był niby zamieniony w kołek.,/ +Mój czytelniku, zamilczeć bym wolał,/ +Pod piórem moje zastygłoby słowo./ +Jeśli najmniejszy masz kwiat wyobraźni,/ +Wyobraź sobie, jak dręczy i drażni/ +Stan, w jakim całą duszą się zawarłem,/ +Zda się, pół żyłem, na poły umarłem./ +SzatanKról piekielnego państwa jakby kawał/ +Głazu nad lody pół piersią wystawał;/ +Jak wzrost mój dosyć ogromny, nie kłamię,/ +Tak wielkie było jego jedno ramię./ +Zważ, jaka całość mogła być niemała,/ +Zastosowana do tej części ciała./ +Jeśli tak piękny był, jak teraz szpetny,/ +Kiedy od Stwórcy odwrócił wzrok świetny,/ +Grzew, wszelki zakał musi iść od niego./ +Dziw! Głowa jego kształtu potwornego,/ +Na trzech obliczach razem osadzona:głowa jego kształtu potwornego, na trzech obliczach razem osadzona --- Dis albo Lucyfer, pierwszy rodzic grzechu. Widzimy go tutaj jako ukaranego i jako narzędzie kary. Ma on trzy głowy, które podług wykładu komentatorów Dantego symbolem są trzech ówcześnie znajomych części świata, a razem oznaczają powszechność grzechu i panowanie Lucyfera na ziemi. Barwa czerwona ma oznaczać Europę (której mieszkańcy mają cerę rumianą), żółta Azję, czarna Afrykę. Inni rozumieją przez potrójną barwę jego twarzy gniew, łakomstwo i lenistwo; wierzch zaś, czyli czub głowy, ma oznaczać pychę, przez którą szczególnie Lucyfer panowanie swoje rozszerzył i ugruntował./ +Pierwsza twarz była jako żar czerwona,/ +Dwie zaś policzkiem do pierwszej przypadły;/ +Obie na środku dwóch ramion usiadły,/ +Schodząc się z sobą aż pod wierzchem głowy;/ +Oblicze prawe biało-żółtej barwy,/ +Jaką mieszkaniec dziwi nadnilowy./ +Pod każdą twarzą tej potwornej larwy,/ +Jak z okrętowych żagli płachta jaka,/ +WiatrW miarę wielkości tak dziwnego ptaka,/ +Sterczą dwa skrzydła, lecz bez piór, bez pierza,/ +Całe skórzane jak u nietoperza./ +I nieustannym swych skrzydeł trzepotem/ +Wiał na trzy strony trzy wiatry z łoskotem,/ +Od których marzły kocytowe lody./ +Sześcioro oczu miał, z tych każde oko/ +Nie łzami, krwawą płakało posoką,/ +Która spływała jak łza na trzy brody./ +I trzech grzeszników przeżuwał jak zwierze,/ +Każdego żuła osobna paszczęka,/ +Jako cierlica drze lniane paździerze./ +Lecz ząb łagodniej kąsał porównany/ +Z szponami, jakie zadawały rany,/ +Zdało się, skóra aż do kości pęka./ +«Duch, co największe bodaj cierpi męki./ +Którego wewnątrz czarnej paszczy głowa,/ +A sam na zewnątrz jej nogami miota»/ +Mistrz mówił, «oto Judasz Iskariota!/ +Dwaj, co głowami zwisają z paszczęki,/ +Pierwszy to Brutus! choć ból rzeczywisty/ +Szarpie go, jednak milczy jak niemowa;/ +Drugi, patrz dobrze, to Kasjusz barczystyPierwszy to Brutus (...) Drugi (...) to Kasjusz barczysty --- Brutus i Kasjusz, zdrajcy i zabójcy Cezara. Miejsce, jakie im poeta przed innymi zdrajcami tu oznacza, objaśnia własnym przekonaniem: że cesarstwo rzymskie ugruntowane było z bezpośredniej woli bożej, ażeby świeckie wszechwładztwo i poszanowanie dla niego zaszczepić na ziemi. To przekonanie poeta wyraził nie tylko w osobnym traktacie swoim o monarchii, ale i uczynił je jedną z myśli przewodnich swej Boskiej komedii../ +Potwór, DiabełNoc już powraca, teraz czas iść dalej,/ +Bośmy już w piekle wszystko oglądali»./ +Jak chciał, jam jemu na szyi zawisnął./ +W chwili, gdy potwór swe skrzydła roztacza,/ +Szybki jak piorun, co już spadł, nim błysnął./ +Mistrz się uczepił do boków kudłacza,/ +Z kudłów na kudły śliznął się pięściami,/ +Między ich runem spadał a lodami./ +Gdyśmy już doszli do miejsca, o cudo!/ +Tam, gdzie pod biodra rozszerza się udo,/ +Mój wódz, jak gdyby wpadł na fortel nowy,/ +Gdzie były nogi, przewrócił wierzch głowyMistrz się uczepił do boków kudłacza (...) przewrócił wierzch głowy --- Sięgając wyobraźnią naszą aż do środka ziemi, znajdujemy stosowne, że Wergiliusz ślizgając się po kudłach Lucyfera, w tym punkcie obrócił tam nogi, gdzie był wierzch głowy, chociaż bez przerwy w tymże samym kierunku się wspinał. Równie znajdujemy stosowne, że środek Lucyfera jest zarazem środkowym punktem ziemi i że poeci wchodząc po nim, gdy doszli przeciwległego punktu, widzieli stopy Lucyfera wywrócone do góry. Moralne znaczenie tego plastycznego obrazu wygląda dość przeźroczysto: człowiek, który swój błąd lub grzech poznał, a potem pragnie z niego się wyzwolić, musi, chcąc dojść do pożądanego celu, w zupełnie przeciwległym kierunku postępować naprzód. Musi złą zasadę mieć pod sobą, a od chwili, w której ją poznał, wchodzić coraz wyżej.,/ +Piął się po kudłach, aż trzęsły mną dreszcze,/ +Myśląc, że nazad idę w piekło jeszcze./ +«Trzymaj się dobrze, tą chyba drabiną»/ +Mówił wódz, dysząc z trudu i pośpiechu,/ +«Możemy zstąpić z tego gniazda grzechu»./ +StrachI wkrótce wyszedł skały rozpadliną,/ +Stanął, odetchnął piersią i co żywo/ +Roztropną stopę podstawił, i na nią/ +Rad mnie wysadził nad samą otchłanią./ +Podniosłem oczy i widziałem dziwo!/ +Wspak przewróconą postać Lucyfera,/ +Rzekłbyś, nogami w powietrze się wpiera./ +Czy byłem w strachu, niechaj ludzie prości/ +Zgadną, co nigdy z takiej wysokości/ +Schodzić nie mogli! Gdym ochłonął z trwogi,/ +Mistrz mówił do mnie: «Teraz wstań na nogi;/ +Droga daleka, a ścieżki nużące,/ +Już gwiazdy nocne, wschodząc, płoszy słońce»./ +Tam droga, którą miałem iść na nowo,/ +Nie była prostą ulicą zamkową,/ +Raczej jaskinią, co ma wejście krzywe,/ +Ściany chropawe, a światło wątpliwe./ +Szatan, Obraz świata«Mistrzu mój,» rzekłem, «gdym wart twego względu,/ +O przemów do mnie, wyprowadź mnie z błędu,/ +Gdzie są te lody? Ich grubą powłoką/ +Jak tam Lucyfer zapadł tak głęboko?/ +I jak to słońce, szybkość niesłychana,/ +Przebiegło drogę od wczoraj do ranajak to słońce, szybkość niesłychana, przebiegło drogę od wczoraj do rana --- Nim poeci przeszli środkowy punkt ziemi, Wergiliusz mówił: ,,Noc się przybliża" potem powiada: ,,I słońce wschodzące płoszy gwiazdy". Ta pozorna sprzeczność daje się tak tłumaczyć: uprzednio mówiąc, myślał Wergiliusz o wschodniej, a teraz, gdy przeszedł punkt środkowy ziemi, myśli o zachodniej półkuli, to jest o antypodach, u których już ranek świta, kiedy u nas noc nadchodzi.?»/ +A mistrz: «Myśl twoja jeszcze za punkt lata,/ +RobakGdzie stoi szczecią potwora kudłata,/ +Robak, co wierci i toczy rdzeń świata./ +Ilem w dół schodził, byłeś tam o tyle,/ +Gdym się obrócił, przeszedłeś w tę chwilę/ +Punkt, do którego ze wszech stron zebrane/ +Wszystkie ciężary ciężą pociągane./ +Ty pod półkulę zstąpiłeś z kolei,/ +Co przeciwległą jest względem Judei,/ +Wielkiej pustyni, wśród której oazy/ +Poczęty człowiek żył i zmarł bez zmazypoczęty człowiek żył i zmarł bez zmazy --- Jezus Chrystus, którego święte imię poeta w piekle tylko przez peryfrazę wspomina../ +Gdy tam jest wieczór, tu nam ranek świeci:/ +Ten, po którego szczeblowałemszczeblować --- wchodzić po stopniach (szczeblach). szczeciszczeć --- szczecina, sierść, włosy.,/ +Jak stał, tak stoi wbity między lody./ +Strącony, tędy snadź on z nieba spadałStrącony, tędy snadź on z nieba spadał --- Tu Dante wyobraża sobie, że Lucyfer na nieznajomą jeszcze za jego czasów stronę kuli ziemskiej i której jasnowidzeniem swojego geniuszu mógł się domyślać, spadł z nieba, że ląd stały przestraszony jego upadkiem, skrył się pod powierzchnię Oceanu i wynurzył się z głębokości jego na wschodniej półkuli, na której Góra Syjon tworzy punkt przeciwległy. Niemniej wszakże poruszyła się przestraszona ziemia w swoich wnętrznościach, kiedy Lucyfer spadając aż do jej środka sam sobą ją przewiercił. Część ziemi, jaką wyrzucił wiercąc ją sobą, utworzyła górę czyśćcową, ląd jedyny, jaki według pomysłu poety na owej półkuli się znajduje. W środku zaś ziemi jest piekło, z którego poeci w tej chwili wychodzą.,/ +Ląd, co z tej strony pokazał się wprzódy,/ +Ze strachu pasem otoczył się wody;/ +Od Lucyfera uciekając może,/ +Gdy bliżej naszej półkuli osiadał,/ +Zostawił tutaj to próżne wydroże./ +Jest tam nieznane miejsce dla nas obuJest tam nieznane miejsce dla nas obu --- Tu wskazuje górę czyśćcową.,/ +Stąd tak odlegle, wzniesione wysoko,/ +Jak cały przestwór Belzebuba grobu./ +Kędy jest, trudno go poznać na oko,/ +Chyba po szmerze małego strumykaChyba po szmerze małego strumyka --- Ponieważ na przeciwległej stronie kuli ziemskiej nie ma innego lądu prócz góry czyśćcowej, więc stamtąd spływać musi ten strumień korytem pochyłym i krętym. W pieśni XIV widzieliśmy, że łzy spadają przez szczeliny olbrzymiego posągu stojącego w grocie góry Idy. Możemy stąd wnioskować, że z łez dusz pokutujących na górze czyśćcowej utworzył się ten strumień.,/ +Który otworem przez siebie wyrżniętym,/ +Środkiem tej skały swe fale pomyka,/ +Płynąc korytem pochyłym i krętym»./ +Wódz i ja w otwór wstąpiliśmy ciasny;/ +Zniecierpliwieni oglądać świat jasny,/ +Nic się nie troszcząc o trud naszej jazdy,/ +Szliśmy bez przerwy, on pierwszy, ja drugi;/ +Przez otwór błysły niebios piękne smugi,/ +W końcu wychodząc, witaliśmy gwiazdywychodząc witaliśmy gwiazdy --- Każda część Boskiej komedii kończy się słowem: gwiazda, która tu jest symbolem naszego nieśmiertelnego ducha wchodzącego coraz wyżej do najwyższego dobra i wiekuistej prawdy, jakimi są: Niebo i Bóg! Myśl ta w tym symbolu ukryta głównym jest celem i ostatnim wyrazem tej arcy chrześcijańskiej epopei Dantowskiej.. + + + \ No newline at end of file diff --git a/src/wiki/tests/xslt/auto/data/unknown_tag.html b/src/wiki/tests/xslt/auto/data/unknown_tag.html new file mode 100644 index 00000000..9ce58f8e --- /dev/null +++ b/src/wiki/tests/xslt/auto/data/unknown_tag.html @@ -0,0 +1 @@ +ala \ No newline at end of file diff --git a/src/wiki/tests/xslt/auto/data/unknown_tag.xml b/src/wiki/tests/xslt/auto/data/unknown_tag.xml new file mode 100644 index 00000000..91e05ac8 --- /dev/null +++ b/src/wiki/tests/xslt/auto/data/unknown_tag.xml @@ -0,0 +1 @@ +d \ No newline at end of file diff --git a/src/wiki/urls.py b/src/wiki/urls.py new file mode 100644 index 00000000..0c73aed9 --- /dev/null +++ b/src/wiki/urls.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 +from django.conf.urls import patterns, url + + +urlpatterns = patterns('wiki.views', + url(r'^edit/(?P[^/]+)/(?:(?P[^/]+)/)?$', + 'editor', name="wiki_editor"), + + url(r'^readonly/(?P[^/]+)/(?:(?P[^/]+)/)?$', + 'editor_readonly', name="wiki_editor_readonly"), + + url(r'^gallery/(?P[^/]+)/$', + 'gallery', name="wiki_gallery"), + + url(r'^history/(?P\d+)/$', + 'history', name="wiki_history"), + + url(r'^rev/(?P\d+)/$', + 'revision', name="wiki_revision"), + + url(r'^text/(?P\d+)/$', + 'text', name="wiki_text"), + + url(r'^revert/(?P\d+)/$', + 'revert', name='wiki_revert'), + + url(r'^diff/(?P\d+)/$', 'diff', name="wiki_diff"), + url(r'^pubmark/(?P\d+)/$', 'pubmark', name="wiki_pubmark"), + + url(r'^themes$', 'themes', name="themes"), +) diff --git a/src/wiki/views.py b/src/wiki/views.py new file mode 100644 index 00000000..e1ef6aed --- /dev/null +++ b/src/wiki/views.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- +from datetime import datetime +import os +import logging +from time import mktime +import urllib + +from django.conf import settings +from django.core.urlresolvers import reverse +from django import http +from django.http import Http404, HttpResponseForbidden +from django.middleware.gzip import GZipMiddleware +from django.utils.decorators import decorator_from_middleware +from django.utils.encoding import smart_unicode +from django.utils.formats import localize +from django.utils.translation import ugettext as _ +from django.views.decorators.http import require_POST, require_GET +from django.shortcuts import get_object_or_404, render + +from catalogue.models import Book, Chunk +import nice_diff +from wiki import forms +from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError, + ajax_require_permission) +from wiki.models import Theme + +# +# Quick hack around caching problems, TODO: use ETags +# +from django.views.decorators.cache import never_cache + +logger = logging.getLogger("fnp.wiki") + +MAX_LAST_DOCS = 10 + + +@never_cache +def editor(request, slug, chunk=None, template_name='wiki/document_details.html'): + try: + chunk = Chunk.get(slug, chunk) + except Chunk.MultipleObjectsReturned: + # TODO: choice page + raise Http404 + except Chunk.DoesNotExist: + if chunk is None: + try: + book = Book.objects.get(slug=slug) + except Book.DoesNotExist: + return http.HttpResponseRedirect(reverse("catalogue_create_missing", args=[slug])) + else: + raise Http404 + if not chunk.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + access_time = datetime.now() + last_books = request.session.get("wiki_last_books", {}) + last_books[reverse(editor, args=[chunk.book.slug, chunk.slug])] = { + 'time': mktime(access_time.timetuple()), + 'title': chunk.pretty_name(), + } + + if len(last_books) > MAX_LAST_DOCS: + oldest_key = min(last_books, key=lambda x: last_books[x]['time']) + del last_books[oldest_key] + request.session['wiki_last_books'] = last_books + + return render(request, template_name, { + 'chunk': chunk, + 'forms': { + "text_save": forms.DocumentTextSaveForm(user=request.user, prefix="textsave"), + "text_revert": forms.DocumentTextRevertForm(prefix="textrevert"), + "pubmark": forms.DocumentPubmarkForm(prefix="pubmark"), + }, + 'can_pubmark': request.user.has_perm('catalogue.can_pubmark'), + 'REDMINE_URL': settings.REDMINE_URL, + }) + + +@require_GET +def editor_readonly(request, slug, chunk=None, template_name='wiki/document_details_readonly.html'): + try: + chunk = Chunk.get(slug, chunk) + revision = request.GET['revision'] + except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist, KeyError): + raise Http404 + if not chunk.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + access_time = datetime.now() + last_books = request.session.get("wiki_last_books", {}) + last_books[slug, chunk.slug] = { + 'time': mktime(access_time.timetuple()), + 'title': chunk.book.title, + } + + if len(last_books) > MAX_LAST_DOCS: + oldest_key = min(last_books, key=lambda x: last_books[x]['time']) + del last_books[oldest_key] + request.session['wiki_last_books'] = last_books + + return render(request, template_name, { + 'chunk': chunk, + 'revision': revision, + 'readonly': True, + 'REDMINE_URL': settings.REDMINE_URL, + }) + + +@never_cache +@decorator_from_middleware(GZipMiddleware) +def text(request, chunk_id): + doc = get_object_or_404(Chunk, pk=chunk_id) + if not doc.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + if request.method == 'POST': + form = forms.DocumentTextSaveForm(request.POST, user=request.user, prefix="textsave") + if form.is_valid(): + if request.user.is_authenticated(): + author = request.user + else: + author = None + text = form.cleaned_data['text'] + parent_revision = form.cleaned_data['parent_revision'] + if parent_revision is not None: + parent = doc.at_revision(parent_revision) + else: + parent = None + stage = form.cleaned_data['stage_completed'] + tags = [stage] if stage else [] + publishable = (form.cleaned_data['publishable'] and + request.user.has_perm('catalogue.can_pubmark')) + doc.commit(author=author, + text=text, + parent=parent, + description=form.cleaned_data['comment'], + tags=tags, + author_name=form.cleaned_data['author_name'], + author_email=form.cleaned_data['author_email'], + publishable=publishable, + ) + revision = doc.revision() + return JSONResponse({ + 'text': doc.materialize() if parent_revision != revision else None, + 'meta': {}, + 'revision': revision, + }) + else: + return JSONFormInvalid(form) + else: + revision = request.GET.get("revision", None) + + try: + revision = int(revision) + except (ValueError, TypeError): + revision = doc.revision() + + if revision is not None: + text = doc.at_revision(revision).materialize() + else: + text = '' + + return JSONResponse({ + 'text': text, + 'meta': {}, + 'revision': revision, + }) + + +@never_cache +@require_POST +def revert(request, chunk_id): + form = forms.DocumentTextRevertForm(request.POST, prefix="textrevert") + if form.is_valid(): + doc = get_object_or_404(Chunk, pk=chunk_id) + if not doc.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + revision = form.cleaned_data['revision'] + + comment = form.cleaned_data['comment'] + comment += "\n#revert to %s" % revision + + if request.user.is_authenticated(): + author = request.user + else: + author = None + + before = doc.revision() + logger.info("Reverting %s to %s", chunk_id, revision) + doc.at_revision(revision).revert(author=author, description=comment) + + return JSONResponse({ + 'text': doc.materialize() if before != doc.revision() else None, + 'meta': {}, + 'revision': doc.revision(), + }) + else: + return JSONFormInvalid(form) + + +@never_cache +def gallery(request, directory): + try: + base_url = ''.join(( + smart_unicode(settings.MEDIA_URL), + smart_unicode(settings.IMAGE_DIR), + smart_unicode(directory))) + + base_dir = os.path.join( + smart_unicode(settings.MEDIA_ROOT), + smart_unicode(settings.IMAGE_DIR), + smart_unicode(directory)) + + def map_to_url(filename): + return urllib.quote(("%s/%s" % (base_url, smart_unicode(filename))).encode('utf-8')) + + def is_image(filename): + return os.path.splitext(filename)[1].lower() in (u'.jpg', u'.jpeg', u'.png') + + images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)] + images.sort() + + books = Book.objects.filter(gallery=directory) + + if not all(book.public for book in books) and not request.user.is_authenticated(): + return HttpResponseForbidden("Not authorized.") + + return JSONResponse(images) + except (IndexError, OSError): + logger.exception("Unable to fetch gallery") + raise http.Http404 + + +@never_cache +def diff(request, chunk_id): + revA = int(request.GET.get('from', 0)) + revB = int(request.GET.get('to', 0)) + + if revA > revB: + revA, revB = revB, revA + + if revB == 0: + revB = None + + doc = get_object_or_404(Chunk, pk=chunk_id) + if not doc.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + # allow diff from the beginning + if revA: + docA = doc.at_revision(revA).materialize() + else: + docA = "" + docB = doc.at_revision(revB).materialize() + + return http.HttpResponse(nice_diff.html_diff_table(docA.splitlines(), + docB.splitlines(), context=3)) + + +@never_cache +def revision(request, chunk_id): + doc = get_object_or_404(Chunk, pk=chunk_id) + if not doc.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + return http.HttpResponse(str(doc.revision())) + + +@never_cache +def history(request, chunk_id): + # TODO: pagination + doc = get_object_or_404(Chunk, pk=chunk_id) + if not doc.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + changes = [] + for change in doc.history().reverse(): + changes.append({ + "version": change.revision, + "description": change.description, + "author": change.author_str(), + "date": localize(change.created_at), + "publishable": _("Publishable") + "\n" if change.publishable else "", + "tag": ',\n'.join(unicode(tag) for tag in change.tags.all()), + "published": _("Published") + ": " + \ + localize(change.publish_log.order_by('-book_record__timestamp')[0].book_record.timestamp) \ + if change.publish_log.exists() else "", + }) + return JSONResponse(changes) + + +@require_POST +@ajax_require_permission('catalogue.can_pubmark') +def pubmark(request, chunk_id): + form = forms.DocumentPubmarkForm(request.POST, prefix="pubmark") + if form.is_valid(): + doc = get_object_or_404(Chunk, pk=chunk_id) + if not doc.book.accessible(request): + return HttpResponseForbidden("Not authorized.") + + revision = form.cleaned_data['revision'] + publishable = form.cleaned_data['publishable'] + change = doc.at_revision(revision) + if publishable != change.publishable: + change.set_publishable(publishable) + return JSONResponse({"message": _("Revision marked")}) + else: + return JSONResponse({"message": _("Nothing changed")}) + else: + return JSONFormInvalid(form) + + +def themes(request): + prefix = request.GET.get('q', '') + return http.HttpResponse('\n'.join([str(t) for t in Theme.objects.filter(name__istartswith=prefix)])) diff --git a/src/wiki_img/__init__.py b/src/wiki_img/__init__.py new file mode 100644 index 00000000..c53f0e73 --- /dev/null +++ b/src/wiki_img/__init__.py @@ -0,0 +1 @@ + # pragma: no cover diff --git a/src/wiki_img/forms.py b/src/wiki_img/forms.py new file mode 100644 index 00000000..555f2647 --- /dev/null +++ b/src/wiki_img/forms.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django import forms +from django.utils.translation import ugettext_lazy as _ +from wiki.forms import DocumentTextSaveForm +from catalogue.models import Image + + +class ImageSaveForm(DocumentTextSaveForm): + """Form for saving document's text.""" + + stage_completed = forms.ModelChoiceField( + queryset=Image.tag_model.objects.all(), + required=False, + label=_(u"Completed"), + help_text=_(u"If you completed a life cycle stage, select it."), + ) diff --git a/src/wiki_img/locale/pl/LC_MESSAGES/django.mo b/src/wiki_img/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 00000000..39877286 Binary files /dev/null and b/src/wiki_img/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/wiki_img/locale/pl/LC_MESSAGES/django.po b/src/wiki_img/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 00000000..432d2899 --- /dev/null +++ b/src/wiki_img/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,307 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Platforma Redakcyjna\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-12-14 15:25+0100\n" +"PO-Revision-Date: 2011-12-14 15:26+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: Fundacja Nowoczesna Polska \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: forms.py:18 +msgid "Completed" +msgstr "Ukończono" + +#: forms.py:19 +msgid "If you completed a life cycle stage, select it." +msgstr "Jeśli został ukończony etap prac, wskaż go." + +#: views.py:126 +msgid "Publishable" +msgstr "Gotowe do publikacji" + +#: templates/wiki_img/base.html:15 +msgid "Platforma Redakcyjna" +msgstr "" + +#: templates/wiki_img/diff_table.html:5 +msgid "Old version" +msgstr "Stara wersja" + +#: templates/wiki_img/diff_table.html:6 +msgid "New version" +msgstr "Nowa wersja" + +#: templates/wiki_img/document_details_base.html:31 +msgid "Help" +msgstr "Pomoc" + +#: templates/wiki_img/document_details_base.html:33 +msgid "Version" +msgstr "Wersja" + +#: templates/wiki_img/document_details_base.html:33 +msgid "Unknown" +msgstr "nieznana" + +#: templates/wiki_img/document_details_base.html:35 +#: templates/wiki_img/tag_dialog.html:15 +msgid "Save" +msgstr "Zapisz" + +#: templates/wiki_img/document_details_base.html:36 +msgid "Save attempt in progress" +msgstr "Trwa zapisywanie" + +#: templates/wiki_img/document_details_base.html:37 +msgid "There is a newer version of this document!" +msgstr "Istnieje nowsza wersja tego dokumentu!" + +#: templates/wiki_img/tag_dialog.html:16 +msgid "Cancel" +msgstr "Anuluj" + +#: templates/wiki_img/tabs/history_view.html:5 +msgid "Compare versions" +msgstr "Porównaj wersje" + +#: templates/wiki_img/tabs/history_view.html:8 +msgid "Mark for publishing" +msgstr "Oznacz do publikacji" + +#: templates/wiki_img/tabs/history_view.html:11 +msgid "Revert document" +msgstr "Przywróć wersję" + +#: templates/wiki_img/tabs/history_view.html:14 +msgid "View version" +msgstr "Zobacz wersję" + +#: templates/wiki_img/tabs/motifs_editor.html:4 +#: templates/wiki_img/tabs/motifs_editor_item.html:3 +msgid "Motifs" +msgstr "Motywy" + +#: templates/wiki_img/tabs/motifs_editor.html:5 +#: templates/wiki_img/tabs/objects_editor.html:5 +msgid "Add" +msgstr "Dodaj" + +#: templates/wiki_img/tabs/objects_editor.html:4 +msgid "Object name" +msgstr "Nazwa obiektu" + +#: templates/wiki_img/tabs/objects_editor_item.html:3 +msgid "Objects" +msgstr "Obiekty" + +#: templates/wiki_img/tabs/source_editor_item.html:5 +msgid "Source code" +msgstr "Kod źródłowy" + +#: templates/wiki_img/tabs/summary_view.html:8 +msgid "Title" +msgstr "Tytuł" + +#: templates/wiki_img/tabs/summary_view.html:13 +msgid "Document ID" +msgstr "ID dokumentu" + +#: templates/wiki_img/tabs/summary_view.html:17 +msgid "Current version" +msgstr "Aktualna wersja" + +#: templates/wiki_img/tabs/summary_view.html:20 +msgid "Last edited by" +msgstr "Ostatnio edytowane przez" + +#: templates/wiki_img/tabs/summary_view_item.html:4 +msgid "Summary" +msgstr "Podsumowanie" + +#~ msgid "First correction" +#~ msgstr "Autokorekta" + +#~ msgid "Tagging" +#~ msgstr "Tagowanie" + +#~ msgid "Initial Proofreading" +#~ msgstr "Korekta" + +#~ msgid "Annotation Proofreading" +#~ msgstr "Sprawdzenie przypisów źródła" + +#~ msgid "Modernisation" +#~ msgstr "Uwspółcześnienie" + +#~ msgid "Annotations" +#~ msgstr "Przypisy" + +#~ msgid "Themes" +#~ msgstr "Motywy" + +#~ msgid "Editor's Proofreading" +#~ msgstr "Ostateczna redakcja literacka" + +#~ msgid "Technical Editor's Proofreading" +#~ msgstr "Ostateczna redakcja techniczna" + +#~ msgid "ZIP file" +#~ msgstr "Plik ZIP" + +#~ msgid "Author" +#~ msgstr "Autor" + +#~ msgid "Your name" +#~ msgstr "Imię i nazwisko" + +#~ msgid "Author's email" +#~ msgstr "E-mail autora" + +#~ msgid "Your email address, so we can show a gravatar :)" +#~ msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)" + +#~ msgid "Your comments" +#~ msgstr "Twój komentarz" + +#~ msgid "Describe changes you made." +#~ msgstr "Opisz swoje zmiany" + +#~ msgid "Finished stage: %s" +#~ msgstr "Ukończony etap: %s" + +#~ msgid "name" +#~ msgstr "nazwa" + +#~ msgid "theme" +#~ msgstr "motyw" + +#~ msgid "themes" +#~ msgstr "motywy" + +#~ msgid "Title already used for %s" +#~ msgstr "Nazwa taka sama jak dla pliku %s" + +#~ msgid "Title already used in repository." +#~ msgstr "Plik o tej nazwie już istnieje w repozytorium." + +#~ msgid "File should be UTF-8 encoded." +#~ msgstr "Plik powinien mieć kodowanie UTF-8." + +#~ msgid "Tag added" +#~ msgstr "Dodano tag" + +#~ msgid "Create document" +#~ msgstr "Utwórz dokument" + +#~ msgid "Click to open/close gallery" +#~ msgstr "Kliknij, aby (ro)zwinąć galerię" + +#~ msgid "Clear filter" +#~ msgstr "Wyczyść filtr" + +#~ msgid "Your last edited documents" +#~ msgstr "Twoje ostatnie edycje" + +#~ msgid "Bulk documents upload" +#~ msgstr "Hurtowe dodawanie dokumentów" + +#~ msgid "" +#~ "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with " +#~ ".xml will be ignored." +#~ msgstr "" +#~ "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie " +#~ "kończące się na .xml zostaną zignorowane." + +#~ msgid "Upload" +#~ msgstr "Dodaj" + +#~ msgid "" +#~ "There have been some errors. No files have been added to the repository." +#~ msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium." + +#~ msgid "Offending files" +#~ msgstr "Błędne pliki" + +#~ msgid "Correct files" +#~ msgstr "Poprawne pliki" + +#~ msgid "Files have been successfully uploaded to the repository." +#~ msgstr "Pliki zostały dodane do repozytorium." + +#~ msgid "Uploaded files" +#~ msgstr "Dodane pliki" + +#~ msgid "Skipped files" +#~ msgstr "Pominięte pliki" + +#~ msgid "Files skipped due to no .xml extension" +#~ msgstr "Pliki pominięte z powodu braku rozszerzenia .xml." + +#~ msgid "Refresh" +#~ msgstr "Odśwież" + +#~ msgid "Previous" +#~ msgstr "Poprzednie" + +#~ msgid "Next" +#~ msgstr "Następne" + +#~ msgid "Zoom in" +#~ msgstr "Powiększ" + +#~ msgid "Zoom out" +#~ msgstr "Zmniejsz" + +#~ msgid "Gallery" +#~ msgstr "Galeria" + +#~ msgid "Mark version" +#~ msgstr "Oznacz wersję" + +#~ msgid "History" +#~ msgstr "Historia" + +#~ msgid "Search" +#~ msgstr "Szukaj" + +#~ msgid "Replace with" +#~ msgstr "Zamień na" + +#~ msgid "Replace" +#~ msgstr "Zamień" + +#~ msgid "Options" +#~ msgstr "Opcje" + +#~ msgid "Case sensitive" +#~ msgstr "Rozróżniaj wielkość liter" + +#~ msgid "From cursor" +#~ msgstr "Zacznij od kursora" + +#~ msgid "Search and replace" +#~ msgstr "Znajdź i zamień" + +#~ msgid "Link to gallery" +#~ msgstr "Link do galerii" + +#~ msgid "Insert theme" +#~ msgstr "Wstaw motyw" + +#~ msgid "Insert annotation" +#~ msgstr "Wstaw przypis" + +#~ msgid "Insert special character" +#~ msgstr "Wstaw znak specjalny" + +#~ msgid "Visual editor" +#~ msgstr "Edytor wizualny" diff --git a/src/wiki_img/models.py b/src/wiki_img/models.py new file mode 100644 index 00000000..b685324b --- /dev/null +++ b/src/wiki_img/models.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# diff --git a/src/wiki_img/templates/wiki_img/diff_table.html b/src/wiki_img/templates/wiki_img/diff_table.html new file mode 100644 index 00000000..818c38cc --- /dev/null +++ b/src/wiki_img/templates/wiki_img/diff_table.html @@ -0,0 +1,21 @@ +{% load i18n %} + + + + + + + + +{% for an, a, bn, b, has_change in changes %} + + + + + + + + +{% endfor %} + +
    {% trans "Old version" %}{% trans "New version" %}
    {{an}}{{ a|safe }} {{bn}}{{ b|safe }} 
    \ No newline at end of file diff --git a/src/wiki_img/templates/wiki_img/document_details.html b/src/wiki_img/templates/wiki_img/document_details.html new file mode 100644 index 00000000..0dfc9aed --- /dev/null +++ b/src/wiki_img/templates/wiki_img/document_details.html @@ -0,0 +1,38 @@ +{% extends "wiki_img/document_details_base.html" %} +{% load i18n %} + +{% block extrabody %} +{{ block.super }} + + +{% endblock %} + +{% block tabs-menu %} + {% include "wiki_img/tabs/summary_view_item.html" %} + {% include "wiki_img/tabs/motifs_editor_item.html" %} + {% include "wiki_img/tabs/objects_editor_item.html" %} + {% include "wiki_img/tabs/source_editor_item.html" %} + {% include "wiki/tabs/history_view_item.html" %} +{% endblock %} + +{% block tabs-content %} + {% include "wiki_img/tabs/summary_view.html" %} + {% include "wiki_img/tabs/motifs_editor.html" %} + {% include "wiki_img/tabs/objects_editor.html" %} + {% include "wiki_img/tabs/source_editor.html" %} + {% include "wiki_img/tabs/history_view.html" %} +{% endblock %} + +{% block dialogs %} + {% include "wiki/save_dialog.html" %} + {% include "wiki/revert_dialog.html" %} + {% if can_pubmark %} + {% include "wiki/pubmark_dialog.html" %} + {% endif %} +{% endblock %} + +{% block editor-class %} + sideless +{% endblock %} + diff --git a/src/wiki_img/templates/wiki_img/document_details_base.html b/src/wiki_img/templates/wiki_img/document_details_base.html new file mode 100644 index 00000000..0bb58b2d --- /dev/null +++ b/src/wiki_img/templates/wiki_img/document_details_base.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% load toolbar_tags i18n %} + +{% block title %}{{ document.title }} - {{ block.super }}{% endblock %} +{% block extrahead %} +{% load pipeline %} +{% stylesheet 'detail' %} +{% endblock %} + +{% block extrabody %} + +{% javascript 'wiki_img' %} +{% endblock %} + +{% block maincontent %} + + + +
    +
    + {% block tabs-content %} {% endblock %} +
    +
    + +{% block dialogs %} {% endblock %} + +{% endblock %} diff --git a/src/wiki_img/templates/wiki_img/document_details_readonly.html b/src/wiki_img/templates/wiki_img/document_details_readonly.html new file mode 100644 index 00000000..ca38838e --- /dev/null +++ b/src/wiki_img/templates/wiki_img/document_details_readonly.html @@ -0,0 +1,26 @@ +{% extends "wiki_img/document_details_base.html" %} +{% load i18n %} + +{% block extrabody %} +{{ block.super }} + + +{% endblock %} + +{% block tabs-menu %} + {% include "wiki_img/tabs/motifs_editor_item.html" %} + {% include "wiki_img/tabs/objects_editor_item.html" %} + {% include "wiki_img/tabs/source_editor_item.html" %} +{% endblock %} + +{% block tabs-content %} + {% include "wiki_img/tabs/motifs_editor.html" %} + {% include "wiki_img/tabs/objects_editor.html" %} + {% include "wiki_img/tabs/source_editor.html" %} +{% endblock %} + +{% block editor-class %} + sideless +{% endblock %} + diff --git a/src/wiki_img/templates/wiki_img/tabs/history_view.html b/src/wiki_img/templates/wiki_img/tabs/history_view.html new file mode 100755 index 00000000..dcb62ec0 --- /dev/null +++ b/src/wiki_img/templates/wiki_img/tabs/history_view.html @@ -0,0 +1,40 @@ +{% load i18n %} + diff --git a/src/wiki_img/templates/wiki_img/tabs/motifs_editor.html b/src/wiki_img/templates/wiki_img/tabs/motifs_editor.html new file mode 100644 index 00000000..c064505d --- /dev/null +++ b/src/wiki_img/templates/wiki_img/tabs/motifs_editor.html @@ -0,0 +1,17 @@ +{% load i18n %} + diff --git a/src/wiki_img/templates/wiki_img/tabs/motifs_editor_item.html b/src/wiki_img/templates/wiki_img/tabs/motifs_editor_item.html new file mode 100644 index 00000000..a5a3c347 --- /dev/null +++ b/src/wiki_img/templates/wiki_img/tabs/motifs_editor_item.html @@ -0,0 +1,4 @@ +{% load i18n %} +
  • + {% trans "Motifs" %} +
  • diff --git a/src/wiki_img/templates/wiki_img/tabs/objects_editor.html b/src/wiki_img/templates/wiki_img/tabs/objects_editor.html new file mode 100644 index 00000000..b4149e25 --- /dev/null +++ b/src/wiki_img/templates/wiki_img/tabs/objects_editor.html @@ -0,0 +1,17 @@ +{% load i18n %} + diff --git a/src/wiki_img/templates/wiki_img/tabs/objects_editor_item.html b/src/wiki_img/templates/wiki_img/tabs/objects_editor_item.html new file mode 100644 index 00000000..9fc3af90 --- /dev/null +++ b/src/wiki_img/templates/wiki_img/tabs/objects_editor_item.html @@ -0,0 +1,4 @@ +{% load i18n %} +
  • + {% trans "Objects" %} +
  • diff --git a/src/wiki_img/templates/wiki_img/tabs/source_editor.html b/src/wiki_img/templates/wiki_img/tabs/source_editor.html new file mode 100644 index 00000000..a1316a7e --- /dev/null +++ b/src/wiki_img/templates/wiki_img/tabs/source_editor.html @@ -0,0 +1,4 @@ +{% load toolbar_tags i18n %} +
    + +
    \ No newline at end of file diff --git a/src/wiki_img/templates/wiki_img/tabs/source_editor_item.html b/src/wiki_img/templates/wiki_img/tabs/source_editor_item.html new file mode 100644 index 00000000..22b6d660 --- /dev/null +++ b/src/wiki_img/templates/wiki_img/tabs/source_editor_item.html @@ -0,0 +1,6 @@ +{% load i18n %} +
  • + {% trans "Source code" %} +
  • \ No newline at end of file diff --git a/src/wiki_img/templates/wiki_img/tabs/summary_view.html b/src/wiki_img/templates/wiki_img/tabs/summary_view.html new file mode 100644 index 00000000..a908f553 --- /dev/null +++ b/src/wiki_img/templates/wiki_img/tabs/summary_view.html @@ -0,0 +1,24 @@ +{% load i18n %} +{% load wiki %} + diff --git a/src/wiki_img/templates/wiki_img/tabs/summary_view_item.html b/src/wiki_img/templates/wiki_img/tabs/summary_view_item.html new file mode 100644 index 00000000..bae3ea5d --- /dev/null +++ b/src/wiki_img/templates/wiki_img/tabs/summary_view_item.html @@ -0,0 +1,5 @@ +{% load i18n %} +{% load wiki %} +
  • + {% trans "Summary" %} +
  • diff --git a/src/wiki_img/tests.py b/src/wiki_img/tests.py new file mode 100644 index 00000000..65777379 --- /dev/null +++ b/src/wiki_img/tests.py @@ -0,0 +1,19 @@ +from nose.tools import * +import wiki.models as models +import shutil +import tempfile + + +class TestStorageBase: + def setUp(self): + self.dirpath = tempfile.mkdtemp(prefix='nosetest_') + + def tearDown(self): + shutil.rmtree(self.dirpath) + + +class TestDocumentStorage(TestStorageBase): + + def test_storage_empty(self): + storage = models.DocumentStorage(self.dirpath) + eq_(storage.all(), []) diff --git a/src/wiki_img/urls.py b/src/wiki_img/urls.py new file mode 100644 index 00000000..6a516f36 --- /dev/null +++ b/src/wiki_img/urls.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 +from django.conf.urls import patterns, url + + +urlpatterns = patterns('wiki_img.views', + url(r'^edit/(?P[^/]+)/$', + 'editor', name="wiki_img_editor"), + + url(r'^readonly/(?P[^/]+)/$', + 'editor_readonly', name="wiki_img_editor_readonly"), + + url(r'^text/(?P\d+)/$', + 'text', name="wiki_img_text"), + + url(r'^history/(?P\d+)/$', + 'history', name="wiki_img_history"), + + url(r'^revert/(?P\d+)/$', + 'revert', name='wiki_img_revert'), + + url(r'^diff/(?P\d+)/$', 'diff', name="wiki_img_diff"), + url(r'^pubmark/(?P\d+)/$', 'pubmark', name="wiki_img_pubmark"), + +) diff --git a/src/wiki_img/views.py b/src/wiki_img/views.py new file mode 100644 index 00000000..2b8dd67e --- /dev/null +++ b/src/wiki_img/views.py @@ -0,0 +1,213 @@ +import os +import functools +import logging +logger = logging.getLogger("fnp.wiki_img") + +from django.core.urlresolvers import reverse +from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError, + ajax_require_permission) + +from django.http import Http404, HttpResponse, HttpResponseForbidden +from django.shortcuts import get_object_or_404, render +from django.views.decorators.http import require_GET, require_POST +from django.conf import settings +from django.utils.formats import localize +from django.utils.translation import ugettext as _ + +from catalogue.models import Image +from wiki import forms +from wiki import nice_diff +from wiki_img.forms import ImageSaveForm + +# +# Quick hack around caching problems, TODO: use ETags +# +from django.views.decorators.cache import never_cache + + +@never_cache +def editor(request, slug, template_name='wiki_img/document_details.html'): + doc = get_object_or_404(Image, slug=slug) + + return render(request, template_name, { + 'document': doc, + 'forms': { + "text_save": ImageSaveForm(user=request.user, prefix="textsave"), + "text_revert": forms.DocumentTextRevertForm(prefix="textrevert"), + "pubmark": forms.DocumentPubmarkForm(prefix="pubmark"), + }, + 'can_pubmark': request.user.has_perm('catalogue.can_pubmark_image'), + 'REDMINE_URL': settings.REDMINE_URL, + }) + + +@require_GET +def editor_readonly(request, slug, template_name='wiki_img/document_details_readonly.html'): + doc = get_object_or_404(Image, slug=slug) + try: + revision = request.GET['revision'] + except (KeyError): + raise Http404 + + return render(request, template_name, { + 'document': doc, + 'revision': revision, + 'readonly': True, + 'REDMINE_URL': settings.REDMINE_URL, + }) + + +@never_cache +def text(request, image_id): + doc = get_object_or_404(Image, pk=image_id) + if request.method == 'POST': + form = ImageSaveForm(request.POST, user=request.user, prefix="textsave") + if form.is_valid(): + if request.user.is_authenticated(): + author = request.user + else: + author = None + text = form.cleaned_data['text'] + parent_revision = form.cleaned_data['parent_revision'] + if parent_revision is not None: + parent = doc.at_revision(parent_revision) + else: + parent = None + stage = form.cleaned_data['stage_completed'] + tags = [stage] if stage else [] + publishable = (form.cleaned_data['publishable'] and + request.user.has_perm('catalogue.can_pubmark_image')) + doc.commit(author=author, + text=text, + parent=parent, + description=form.cleaned_data['comment'], + tags=tags, + author_name=form.cleaned_data['author_name'], + author_email=form.cleaned_data['author_email'], + publishable=publishable, + ) + revision = doc.revision() + return JSONResponse({ + 'text': doc.materialize() if parent_revision != revision else None, + 'meta': {}, + 'revision': revision, + }) + else: + return JSONFormInvalid(form) + else: + revision = request.GET.get("revision", None) + + try: + revision = int(revision) + except (ValueError, TypeError): + revision = doc.revision() + + if revision is not None: + text = doc.at_revision(revision).materialize() + else: + text = '' + + return JSONResponse({ + 'text': text, + 'meta': {}, + 'revision': revision, + }) + + +@never_cache +def history(request, object_id): + # TODO: pagination + doc = get_object_or_404(Image, pk=object_id) + if not doc.accessible(request): + return HttpResponseForbidden("Not authorized.") + + changes = [] + for change in doc.history().reverse(): + changes.append({ + "version": change.revision, + "description": change.description, + "author": change.author_str(), + "date": localize(change.created_at), + "publishable": _("Publishable") + "\n" if change.publishable else "", + "tag": ',\n'.join(unicode(tag) for tag in change.tags.all()), + }) + return JSONResponse(changes) + + +@never_cache +@require_POST +def revert(request, object_id): + form = forms.DocumentTextRevertForm(request.POST, prefix="textrevert") + if form.is_valid(): + doc = get_object_or_404(Image, pk=object_id) + if not doc.accessible(request): + return HttpResponseForbidden("Not authorized.") + + revision = form.cleaned_data['revision'] + + comment = form.cleaned_data['comment'] + comment += "\n#revert to %s" % revision + + if request.user.is_authenticated(): + author = request.user + else: + author = None + + before = doc.revision() + logger.info("Reverting %s to %s", object_id, revision) + doc.at_revision(revision).revert(author=author, description=comment) + + return JSONResponse({ + 'text': doc.materialize() if before != doc.revision() else None, + 'meta': {}, + 'revision': doc.revision(), + }) + else: + return JSONFormInvalid(form) + + +@never_cache +def diff(request, object_id): + revA = int(request.GET.get('from', 0)) + revB = int(request.GET.get('to', 0)) + + if revA > revB: + revA, revB = revB, revA + + if revB == 0: + revB = None + + doc = get_object_or_404(Image, pk=object_id) + if not doc.accessible(request): + return HttpResponseForbidden("Not authorized.") + + # allow diff from the beginning + if revA: + docA = doc.at_revision(revA).materialize() + else: + docA = "" + docB = doc.at_revision(revB).materialize() + + return HttpResponse(nice_diff.html_diff_table(docA.splitlines(), + docB.splitlines(), context=3)) + + +@require_POST +@ajax_require_permission('catalogue.can_pubmark_image') +def pubmark(request, object_id): + form = forms.DocumentPubmarkForm(request.POST, prefix="pubmark") + if form.is_valid(): + doc = get_object_or_404(Image, pk=object_id) + if not doc.accessible(request): + return HttpResponseForbidden("Not authorized.") + + revision = form.cleaned_data['revision'] + publishable = form.cleaned_data['publishable'] + change = doc.at_revision(revision) + if publishable != change.publishable: + change.set_publishable(publishable) + return JSONResponse({"message": _("Revision marked")}) + else: + return JSONResponse({"message": _("Nothing changed")}) + else: + return JSONFormInvalid(form)