From c2de733944de9c66c25f61fafa9e295d2c29f32c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Mon, 29 Jul 2013 13:09:02 +0200 Subject: [PATCH 01/16] Fixing flickr parsing For some images/authors 'photo-name-line-2' is empty. --- apps/cover/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cover/forms.py b/apps/cover/forms.py index e373a6cb..0173134a 100755 --- a/apps/cover/forms.py +++ b/apps/cover/forms.py @@ -63,7 +63,7 @@ class FlickrForm(forms.Form): except AssertionError: raise forms.ValidationError('Error reading license name.') - m = re.search(r'([^<]*)', html) + m = re.search(r'"ownername":"([^"]*)', html) if m: self.cleaned_data['author'] = "%s@Flickr" % m.group(1) else: -- 2.20.1 From cf5fb6a43bc06db42e9b96c8574442b7093fbf9e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 14 Aug 2013 15:16:21 +0200 Subject: [PATCH 02/16] Allow for empty catalogue.Book.project value in forms --- apps/catalogue/models/book.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/catalogue/models/book.py b/apps/catalogue/models/book.py index b1b2730f..7c2bccc4 100755 --- a/apps/catalogue/models/book.py +++ b/apps/catalogue/models/book.py @@ -29,7 +29,7 @@ class Book(models.Model): 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) + 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) -- 2.20.1 From 6c7d6a5cf471f39a5e8f8e4f42fb428fb4c42e4e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 9 Oct 2013 12:04:38 +0200 Subject: [PATCH 03/16] Allow for adding covers from a disk --- apps/cover/forms.py | 14 ++++++ ...0002_auto__chg_field_image_download_url.py | 34 ++++++++++++++ apps/cover/models.py | 4 +- apps/cover/templates/cover/add_image.html | 47 +++++++++++++++++-- apps/cover/views.py | 2 +- 5 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 apps/cover/migrations/0002_auto__chg_field_image_download_url.py diff --git a/apps/cover/forms.py b/apps/cover/forms.py index 0173134a..67a0c531 100755 --- a/apps/cover/forms.py +++ b/apps/cover/forms.py @@ -13,6 +13,20 @@ class ImageAddForm(forms.ModelForm): class Meta: model = Image + def __init__(self, *args, **kwargs): + super(ImageAddForm, self).__init__(*args, **kwargs) + self.fields['file'].required = self.fields['download_url'].required = False + + def clean_download_url(self): + return self.cleaned_data['download_url'] or None + + def clean(self): + cleaned_data = super(ImageAddForm, self).clean() + if not cleaned_data.get('download_url', None) and not cleaned_data.get('file', None): + raise forms.ValidationError('No image specified') + return cleaned_data + + class ImageEditForm(forms.ModelForm): """Form used for editing a Book.""" class Meta: 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 new file mode 100644 index 00000000..8a64c39d --- /dev/null +++ b/apps/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/apps/cover/models.py b/apps/cover/models.py index 6900e78e..5574f345 100644 --- a/apps/cover/models.py +++ b/apps/cover/models.py @@ -21,8 +21,8 @@ class Image(models.Model): 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')) - download_url = models.URLField(unique=True, verbose_name=_('image download URL')) - file = models.ImageField(upload_to='cover/image', editable=False, verbose_name=_('file')) + download_url = models.URLField(unique=True, verbose_name=_('image download URL'), null = True) + file = models.ImageField(upload_to='cover/image', editable=True, verbose_name=_('file')) class Meta: verbose_name = _('cover image') diff --git a/apps/cover/templates/cover/add_image.html b/apps/cover/templates/cover/add_image.html index ed7adfbb..1854555f 100755 --- a/apps/cover/templates/cover/add_image.html +++ b/apps/cover/templates/cover/add_image.html @@ -1,6 +1,33 @@ {% extends "catalogue/base.html" %} {% load i18n %} +{% block add_js %} + {{block.super}} + +{% endblock %} {% block content %}

{% trans "Add image" %}

@@ -14,10 +41,24 @@ - -
{% csrf_token %} +{% csrf_token %} +{{ form.non_field_errors }} - {{ form.as_table }} + {% 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}}
diff --git a/apps/cover/views.py b/apps/cover/views.py index 2a21b5c5..3d30a2a5 100644 --- a/apps/cover/views.py +++ b/apps/cover/views.py @@ -125,7 +125,7 @@ def add_image(request): if ff.is_valid(): form = forms.ImageAddForm(ff.cleaned_data) else: - form = forms.ImageAddForm(request.POST) + form = forms.ImageAddForm(request.POST, request.FILES) if form.is_valid(): obj = form.save() return HttpResponseRedirect(obj.get_absolute_url()) -- 2.20.1 From 2320a8487601e534b6d1456573b0c484cb9817a7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 10 Oct 2013 10:10:58 +0200 Subject: [PATCH 04/16] Adding covers: make source_url field not required --- apps/cover/forms.py | 5 ++- .../0003_auto__chg_field_image_source_url.py | 34 +++++++++++++++++++ apps/cover/models.py | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 apps/cover/migrations/0003_auto__chg_field_image_source_url.py diff --git a/apps/cover/forms.py b/apps/cover/forms.py index 67a0c531..4c718862 100755 --- a/apps/cover/forms.py +++ b/apps/cover/forms.py @@ -15,11 +15,14 @@ class ImageAddForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(ImageAddForm, self).__init__(*args, **kwargs) - self.fields['file'].required = self.fields['download_url'].required = False + self.fields['file'].required = self.fields['download_url'].required = self.fields['source_url'].required = False def clean_download_url(self): return self.cleaned_data['download_url'] or None + def clean_source_url(self): + return self.cleaned_data['source_url'] or None + def clean(self): cleaned_data = super(ImageAddForm, self).clean() if not cleaned_data.get('download_url', None) and not cleaned_data.get('file', None): 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 new file mode 100644 index 00000000..98951e35 --- /dev/null +++ b/apps/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/apps/cover/models.py b/apps/cover/models.py index 5574f345..d4432c2c 100644 --- a/apps/cover/models.py +++ b/apps/cover/models.py @@ -20,7 +20,7 @@ class Image(models.Model): 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')) + source_url = models.URLField(verbose_name=_('source URL'), null = True) download_url = models.URLField(unique=True, verbose_name=_('image download URL'), null = True) file = models.ImageField(upload_to='cover/image', editable=True, verbose_name=_('file')) -- 2.20.1 From 0c502601ba7d9e9ee0a4d6a543c534edfad9c22e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 12 Dec 2013 22:27:58 +0100 Subject: [PATCH 05/16] integration wip: showing + saving document, debug mode only, editor via symlink --- apps/wiki/templates/wiki/bootstrap.html | 67 +++++++++++++++++++++++++ apps/wiki/views.py | 53 +++++++++++-------- redakcja/context_processors.py | 1 + redakcja/urls.py | 2 + 4 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 apps/wiki/templates/wiki/bootstrap.html diff --git a/apps/wiki/templates/wiki/bootstrap.html b/apps/wiki/templates/wiki/bootstrap.html new file mode 100644 index 00000000..5d59d784 --- /dev/null +++ b/apps/wiki/templates/wiki/bootstrap.html @@ -0,0 +1,67 @@ +{% load staticfiles %} +{% load i18n %} + + + + + + + + + {% if DEBUG %} + + + + + {% else %} + + + {% endif %} + + + +
+ + diff --git a/apps/wiki/views.py b/apps/wiki/views.py index f9594261..2bb168fe 100644 --- a/apps/wiki/views.py +++ b/apps/wiki/views.py @@ -14,6 +14,7 @@ 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 django.utils import simplejson from catalogue.models import Book, Chunk import nice_diff @@ -32,8 +33,25 @@ logger = logging.getLogger("fnp.wiki") MAX_LAST_DOCS = 10 +def get_history(chunk): + changes = [] + for change in chunk.history(): + 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 changes + + @never_cache -def editor(request, slug, chunk=None, template_name='wiki/document_details.html'): +def editor(request, slug, chunk=None, template_name='wiki/bootstrap.html'): try: chunk = Chunk.get(slug, chunk) except Chunk.MultipleObjectsReturned: @@ -62,15 +80,21 @@ def editor(request, slug, chunk=None, template_name='wiki/document_details.html' del last_books[oldest_key] request.session['wiki_last_books'] = last_books + save_form = forms.DocumentTextSaveForm(user=request.user, prefix="textsave") return render(request, template_name, { - 'chunk': chunk, + 'serialized_document_data': simplejson.dumps({ + 'document': chunk.materialize(), + 'document_id': chunk.id, + 'title': chunk.book.title, + 'history': get_history(chunk), + 'version': chunk.revision() + }), 'forms': { - "text_save": forms.DocumentTextSaveForm(user=request.user, prefix="textsave"), - "text_revert": forms.DocumentTextRevertForm(prefix="textrevert"), - "pubmark": forms.DocumentPubmarkForm(prefix="pubmark"), + "text_save": save_form, + "text_revert": forms.DocumentTextRevertForm(prefix="textrevert") }, + 'tags': list(save_form.fields['stage_completed'].choices), 'can_pubmark': request.user.has_perm('catalogue.can_pubmark'), - 'REDMINE_URL': settings.REDMINE_URL, }) @@ -141,7 +165,7 @@ def text(request, chunk_id): return JSONResponse({ 'text': doc.materialize() if parent_revision != revision else None, 'meta': {}, - 'revision': revision, + 'version': revision, }) else: return JSONFormInvalid(form) @@ -269,20 +293,7 @@ def history(request, 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) + return JSONResponse(get_history(doc)) @require_POST diff --git a/redakcja/context_processors.py b/redakcja/context_processors.py index 5e3372ea..2724f832 100644 --- a/redakcja/context_processors.py +++ b/redakcja/context_processors.py @@ -15,5 +15,6 @@ def settings(request): return { 'MEDIA_URL': settings.MEDIA_URL, 'STATIC_URL': settings.STATIC_URL, + 'DEBUG': settings.DEBUG, 'APP_VERSION': VERSION, } diff --git a/redakcja/urls.py b/redakcja/urls.py index b3da1ee6..1163d162 100644 --- a/redakcja/urls.py +++ b/redakcja/urls.py @@ -28,6 +28,8 @@ urlpatterns = patterns('', url(r'^apiclient/', include('apiclient.urls')), url(r'^editor/', include('wiki.urls')), url(r'^cover/', include('cover.urls')), + (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', dict(packages=['apps.wiki'])), + ) if settings.DEBUG: -- 2.20.1 From 12eddaa1ac5d62be5645a1edc46c78311b734dfe Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Sun, 15 Dec 2013 23:39:51 +0100 Subject: [PATCH 06/16] translations --- apps/wiki/locale/pl/LC_MESSAGES/djangojs.mo | Bin 0 -> 780 bytes apps/wiki/locale/pl/LC_MESSAGES/djangojs.po | 51 ++++++++++++++++++++ redakcja/urls.py | 2 +- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 apps/wiki/locale/pl/LC_MESSAGES/djangojs.mo create mode 100644 apps/wiki/locale/pl/LC_MESSAGES/djangojs.po diff --git a/apps/wiki/locale/pl/LC_MESSAGES/djangojs.mo b/apps/wiki/locale/pl/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..33da0e006a0dd7f0bdff159ffc5210d6b1e97d4e GIT binary patch literal 780 zcmYL`L2DC16vxNfs$E62UOmjk(o!bwrWVU4Tf0p*ZE!aU$+lqeGH%8=CEX3Po5Yl! z3Vs9+f>%F*APC-Upq{*m7rlA&0|;LHPa@>OkN?b@|C@Qc@8{C|Yl652?t-V_I(Q3o zT!0(k3%CNlfveyLSOUMmBKQOJ`P`+cE<)ddTi`bGALgd{3#fkUEA$TZJM<>>H&oyA z7pm`BxJ(F2C!ycdfg`d2^trwR7BUYeQR*ut;*7>gTM(};vqX_SnLqK#)7mX z+ia)bNm-cK)M(U0R#Jjzcz~68CTyBFt#xMQnYBUlmc8+C&9qDtM%EMqsT=w4d*ZoG z7SvhUrF(TdsIDE>Yo6irG-ILSaoXkR-=>v)-={S<@MzHs+^WCrmdj1A)!KqJ?(}&_ zuu$-0oBD2TcVD+$^!@Fw%q6Nb^PttB8hEqsvvQ_UO5zN^kA;29FOHHnex*$}pUKQ, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-12-15 21:57+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \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" + +#: static/wiki/rng/src/editor/modules/data/data.js:117 +#: static/wiki/rng/src/editor/modules/data/data.js:130 +#: static/wiki/rng/src/editor/modules/rng/rng.js:231 +msgid "editor" +msgstr "edytor" + +#: static/wiki/rng/src/editor/modules/nodePane/template.html:3 +msgid "Current node" +msgstr "Bieżący węzeł" + +#: static/wiki/rng/src/editor/modules/rng/rng.js:62 +msgid "Editor" +msgstr "Edytor" + +#: static/wiki/rng/src/editor/modules/rng/rng.js:63 +#: static/wiki/rng/src/editor/modules/rng/rng.js:77 +msgid "Source" +msgstr "Źródło" + +#: static/wiki/rng/src/editor/modules/rng/rng.js:64 +msgid "History" +msgstr "Historia" + +#: static/wiki/rng/src/editor/modules/rng/rng.js:92 +msgid "Saving..." +msgstr "Zapisywanie..." + +#: static/wiki/rng/src/editor/modules/rng/rng.js:102 +msgid "Restoring version " +msgstr "Przywracanie wersji" diff --git a/redakcja/urls.py b/redakcja/urls.py index 1163d162..504c828e 100644 --- a/redakcja/urls.py +++ b/redakcja/urls.py @@ -28,7 +28,7 @@ urlpatterns = patterns('', url(r'^apiclient/', include('apiclient.urls')), url(r'^editor/', include('wiki.urls')), url(r'^cover/', include('cover.urls')), - (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', dict(packages=['apps.wiki'])), + (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', dict(packages=['wiki'])), ) -- 2.20.1 From 3e82fe190f3d1fbfb7300a98ff1a3a0497d9e237 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Thu, 12 Dec 2013 12:35:01 +0100 Subject: [PATCH 07/16] Build and deployment - build command - deployment via fnpdjango --- .gitignore | 5 ++- apps/build/__init__.py | 0 apps/build/management/__init__.py | 0 apps/build/management/commands/__init__.py | 0 apps/build/management/commands/build.py | 43 ++++++++++++++++++++++ deployment/__init__.py | 0 deployment/base.py | 26 +++++++++++++ deployment/environment_template.py | 14 +++++++ fabfile.py | 2 + redakcja/settings/common.py | 1 + redakcja/urls.py | 6 +++ requirements-dev.txt | 4 ++ 12 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 apps/build/__init__.py create mode 100644 apps/build/management/__init__.py create mode 100644 apps/build/management/commands/__init__.py create mode 100644 apps/build/management/commands/build.py create mode 100644 deployment/__init__.py create mode 100644 deployment/base.py create mode 100644 deployment/environment_template.py create mode 100644 fabfile.py create mode 100644 requirements-dev.txt diff --git a/.gitignore b/.gitignore index 318e02d0..156648d6 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,7 @@ nbproject/* node_modules /static_test -chromedriver.log \ No newline at end of file +chromedriver.log + +deployment/environments.py +apps/wiki/static/wiki/build diff --git a/apps/build/__init__.py b/apps/build/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/build/management/__init__.py b/apps/build/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/build/management/commands/__init__.py b/apps/build/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/build/management/commands/build.py b/apps/build/management/commands/build.py new file mode 100644 index 00000000..416893d4 --- /dev/null +++ b/apps/build/management/commands/build.py @@ -0,0 +1,43 @@ +import os +from subprocess import call +from optparse import make_option + +from django.core.management.base import BaseCommand, CommandError +from django.core.management import call_command + + +class Command(BaseCommand): + + option_list = BaseCommand.option_list + ( + make_option('--node-bin-path', + action='store', + dest='node_bin_path', + type='string', + default=None, + help='Path to node binary'), + make_option('--npm-bin', + action='store', + dest='npm_bin', + type='string', + default='npm', + help='Path to npm binary'), + ) + + def handle(self, **options): + wiki_base_dir = os.path.join(os.getcwd(), 'apps', 'wiki', 'static', 'wiki') + rng_base_dir = os.path.join(wiki_base_dir, 'rng') + build_dir = os.path.join(wiki_base_dir, 'build') + + self.stdout.write('Installing editor dependencies') + try: + call([options['npm_bin'], 'install'], cwd = rng_base_dir) + except OSError: + raise CommandError('Something went wrong, propably npm binary not found. Tried: %s' % options['npm_bin']) + + self.stdout.write('Building editor') + if options['node_bin_path']: + # grunt needs npm binary to be foundable in PATH + os.environ['PATH'] = '%s:%s' % (options['node_bin_path'], os.environ['PATH']) + call(['./node_modules/.bin/grunt', 'build', '--output-dir=%s' % build_dir], cwd = rng_base_dir) + + call_command('collectstatic', interactive = False, ignore_patterns = ['rng']) diff --git a/deployment/__init__.py b/deployment/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/deployment/base.py b/deployment/base.py new file mode 100644 index 00000000..54333259 --- /dev/null +++ b/deployment/base.py @@ -0,0 +1,26 @@ +from fabric.api import env +from fabric.tasks import Task +from fnpdjango.deploy import Command + + +class Environment(Task): + def __init__(self, *args, **kwargs): + super(Environment, self).__init__(*args, **kwargs) + self.npm_bin = kwargs.pop('npm_bin', 'npm') + self.host = kwargs.pop('host') + self.env_vars = kwargs + self.env_vars['skip_collect_static'] = True + + def run(self, *args, **kwargs): + env.project_name = 'redakcja' + env.hosts = [self.host] + for k,v in self.env_vars.items(): + env[k] = v + + build_cmd = '../../ve/bin/python manage.py build --npm-bin=%s' % self.npm_bin + if 'node_bin_path' in self.env_vars: + build_cmd += ' --node-bin-path=%s' % self.env_vars['node_bin_path'] + + env.pre_collectstatic = [ + Command([build_cmd], '') + ] diff --git a/deployment/environment_template.py b/deployment/environment_template.py new file mode 100644 index 00000000..581b45c6 --- /dev/null +++ b/deployment/environment_template.py @@ -0,0 +1,14 @@ +from fnpdjango.deploy import DebianGunicorn +from base import Environment + + +env1 = Environment( + host = '', + user = '', + app_path = '', + services = [ + DebianGunicorn('') + ], + node_bin_path = '/usr/bin', + npm_bin = 'npm', +) \ No newline at end of file diff --git a/fabfile.py b/fabfile.py new file mode 100644 index 00000000..6738894d --- /dev/null +++ b/fabfile.py @@ -0,0 +1,2 @@ +from fnpdjango.deploy import * +from deployment.environments import * diff --git a/redakcja/settings/common.py b/redakcja/settings/common.py index 4565fe4b..c9a4616f 100644 --- a/redakcja/settings/common.py +++ b/redakcja/settings/common.py @@ -124,6 +124,7 @@ INSTALLED_APPS = ( 'toolbar', 'apiclient', 'email_mangler', + 'build' ) LOGIN_REDIRECT_URL = '/documents/user' diff --git a/redakcja/urls.py b/redakcja/urls.py index 504c828e..d9297e29 100644 --- a/redakcja/urls.py +++ b/redakcja/urls.py @@ -35,3 +35,9 @@ urlpatterns = patterns('', if settings.DEBUG: urlpatterns += staticfiles_urlpatterns() urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + +if getattr(settings, 'SERVE_FILES_WITH_DEBUG_FALSE', False): + urlpatterns += patterns('', + (r'^%s(?P.*)$' % settings.STATIC_URL[1:], 'django.views.static.serve', {'document_root': settings.STATIC_ROOT}), + (r'^%s(?P.*)$' % settings.MEDIA_URL[1:], 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}), +) diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..5e655130 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,4 @@ +-i http://pypi.nowoczesnapolska.org.pl/simple + +fabric +fnpdjango>=0.1.10,<0.2 -- 2.20.1 From 840b9bd7443e0048253423f7553809b5542d731c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 11 Dec 2013 10:12:49 +0100 Subject: [PATCH 08/16] Defining templates, creating new document from a template --- apps/catalogue/admin.py | 1 + apps/catalogue/forms.py | 8 +- .../migrations/0012_auto__add_template.py | 157 ++++++++++++++++++ apps/catalogue/models/__init__.py | 1 + apps/catalogue/models/template.py | 14 ++ 5 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 apps/catalogue/migrations/0012_auto__add_template.py create mode 100644 apps/catalogue/models/template.py diff --git a/apps/catalogue/admin.py b/apps/catalogue/admin.py index 7fbacad9..f8cda9fe 100644 --- a/apps/catalogue/admin.py +++ b/apps/catalogue/admin.py @@ -12,5 +12,6 @@ class BookAdmin(admin.ModelAdmin): admin.site.register(models.Project) admin.site.register(models.Book, BookAdmin) admin.site.register(models.Chunk) +admin.site.register(models.Template) admin.site.register(models.Chunk.tag_model) diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py index 7ae7ff40..9d18a921 100644 --- a/apps/catalogue/forms.py +++ b/apps/catalogue/forms.py @@ -9,13 +9,14 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from catalogue.constants import MASTERS -from catalogue.models import Book, Chunk +from catalogue.models import Book, Chunk, Template class DocumentCreateForm(forms.ModelForm): """ Form used for creating new documents. """ file = forms.FileField(required=False) + template = forms.ModelChoiceField(Template.objects, required=False) text = forms.CharField(required=False, widget=forms.Textarea) class Meta: @@ -31,15 +32,18 @@ class DocumentCreateForm(forms.ModelForm): def clean(self): super(DocumentCreateForm, self).clean() file = self.cleaned_data['file'] + template = self.cleaned_data['template'] 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.")) + elif template is not None: + self.cleaned_data['text'] = template.content if not self.cleaned_data["text"]: - self._errors["file"] = self.error_class([_("You must either enter text or upload a file")]) + self._errors["file"] = self.error_class([_("You must enter text, upload a file or select a template")]) return self.cleaned_data diff --git a/apps/catalogue/migrations/0012_auto__add_template.py b/apps/catalogue/migrations/0012_auto__add_template.py new file mode 100644 index 00000000..f9ce8989 --- /dev/null +++ b/apps/catalogue/migrations/0012_auto__add_template.py @@ -0,0 +1,157 @@ +# -*- 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 model 'Template' + db.create_table(u'catalogue_template', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('content', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal(u'catalogue', ['Template']) + + + def backwards(self, orm): + # Deleting model 'Template' + db.delete_table(u'catalogue_template') + + + 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.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'catalogue.template': { + 'Meta': {'object_name': 'Template'}, + 'content': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 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'}), + '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 = ['catalogue'] \ No newline at end of file diff --git a/apps/catalogue/models/__init__.py b/apps/catalogue/models/__init__.py index bd069f1c..faa2d384 100755 --- a/apps/catalogue/models/__init__.py +++ b/apps/catalogue/models/__init__.py @@ -3,6 +3,7 @@ # 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.template import Template from catalogue.models.project import Project from catalogue.models.chunk import Chunk from catalogue.models.publish_log import BookPublishRecord, ChunkPublishRecord diff --git a/apps/catalogue/models/template.py b/apps/catalogue/models/template.py new file mode 100644 index 00000000..f8439df0 --- /dev/null +++ b/apps/catalogue/models/template.py @@ -0,0 +1,14 @@ +from django.db import models + + +class Template(models.Model): + """ Template of a document or its part """ + + name = models.CharField(max_length=255) + content = models.TextField() + + class Meta: + app_label = 'catalogue' + + def __unicode__(self): + return self.name -- 2.20.1 From ad1af543b9587dd19cf0b921c581264fbd78e84a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 11 Dec 2013 10:20:05 +0100 Subject: [PATCH 09/16] Differentiate between main and partial templates --- apps/catalogue/forms.py | 1 + ..._is_main__add_field_template_is_partial.py | 164 ++++++++++++++++++ apps/catalogue/models/template.py | 4 +- 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 apps/catalogue/migrations/0013_auto__add_field_template_is_main__add_field_template_is_partial.py diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py index 9d18a921..285e58e5 100644 --- a/apps/catalogue/forms.py +++ b/apps/catalogue/forms.py @@ -28,6 +28,7 @@ class DocumentCreateForm(forms.ModelForm): self.fields['slug'].widget.attrs={'class': 'autoslug'} self.fields['gallery'].widget.attrs={'class': 'autoslug'} self.fields['title'].widget.attrs={'class': 'autoslug-source'} + self.fields['template'].queryset = Template.objects.filter(is_main=True) def clean(self): super(DocumentCreateForm, self).clean() diff --git a/apps/catalogue/migrations/0013_auto__add_field_template_is_main__add_field_template_is_partial.py b/apps/catalogue/migrations/0013_auto__add_field_template_is_main__add_field_template_is_partial.py new file mode 100644 index 00000000..0d83e6d1 --- /dev/null +++ b/apps/catalogue/migrations/0013_auto__add_field_template_is_main__add_field_template_is_partial.py @@ -0,0 +1,164 @@ +# -*- 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 'Template.is_main' + db.add_column(u'catalogue_template', 'is_main', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + # Adding field 'Template.is_partial' + db.add_column(u'catalogue_template', 'is_partial', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Template.is_main' + db.delete_column(u'catalogue_template', 'is_main') + + # Deleting field 'Template.is_partial' + db.delete_column(u'catalogue_template', 'is_partial') + + + 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.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'}) + }, + 'catalogue.template': { + 'Meta': {'object_name': 'Template'}, + 'content': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_main': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_partial': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 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'}), + '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 = ['catalogue'] \ No newline at end of file diff --git a/apps/catalogue/models/template.py b/apps/catalogue/models/template.py index f8439df0..dded859e 100644 --- a/apps/catalogue/models/template.py +++ b/apps/catalogue/models/template.py @@ -3,9 +3,11 @@ from django.db import models class Template(models.Model): """ Template of a document or its part """ - + name = models.CharField(max_length=255) content = models.TextField() + is_main = models.BooleanField() + is_partial = models.BooleanField() class Meta: app_label = 'catalogue' -- 2.20.1 From 6bf8f9bc3d930eaac1ca1439d5dcee8585bf7f80 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Wed, 11 Dec 2013 13:29:06 +0100 Subject: [PATCH 10/16] Push partial templates to the editor --- apps/wiki/templates/wiki/bootstrap.html | 6 +++++- apps/wiki/views.py | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/wiki/templates/wiki/bootstrap.html b/apps/wiki/templates/wiki/bootstrap.html index 5d59d784..159a4f4e 100644 --- a/apps/wiki/templates/wiki/bootstrap.html +++ b/apps/wiki/templates/wiki/bootstrap.html @@ -55,10 +55,14 @@ ); {% endif %} - var data = {% autoescape off%}{{serialized_document_data}}{%endautoescape%}; + {% autoescape off%} + var data = {{serialized_document_data}}, + templates = {{serialized_templates}} + {%endautoescape%} var editor_init = function(Editor) { Editor.setBootstrappedData('data', data); + Editor.setBootstrappedData('documentToolbar', templates); Editor.start(config); }; diff --git a/apps/wiki/views.py b/apps/wiki/views.py index 2bb168fe..b88d2df1 100644 --- a/apps/wiki/views.py +++ b/apps/wiki/views.py @@ -16,7 +16,7 @@ from django.views.decorators.http import require_POST, require_GET from django.shortcuts import get_object_or_404, render from django.utils import simplejson -from catalogue.models import Book, Chunk +from catalogue.models import Book, Chunk, Template import nice_diff from wiki import forms from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError, @@ -89,6 +89,9 @@ def editor(request, slug, chunk=None, template_name='wiki/bootstrap.html'): 'history': get_history(chunk), 'version': chunk.revision() }), + 'serialized_templates': simplejson.dumps([ + {'id': t.id, 'name': t.name, 'content': t.content} for t in Template.objects.filter(is_partial=True) + ]), 'forms': { "text_save": save_form, "text_revert": forms.DocumentTextRevertForm(prefix="textrevert") -- 2.20.1 From 27e1f96bba499132675df3e5ad17326d751144cf Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 12 Aug 2014 11:46:58 +0200 Subject: [PATCH 11/16] adding editor as a submodule --- .gitmodules | 3 +++ apps/build/management/commands/build.py | 4 ++-- apps/wiki/static/wiki/editor | 1 + apps/wiki/templates/wiki/bootstrap.html | 6 +++--- 4 files changed, 9 insertions(+), 5 deletions(-) create mode 160000 apps/wiki/static/wiki/editor diff --git a/.gitmodules b/.gitmodules index 004f6f37..ab1e93c9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/librarian"] path = lib/librarian url = git://git.nowoczesnapolska.org.pl/librarian.git +[submodule "apps/wiki/static/wiki/editor"] + path = apps/wiki/static/wiki/editor + url = git://git.nowoczesnapolska.org.pl/fnpeditor.git diff --git a/apps/build/management/commands/build.py b/apps/build/management/commands/build.py index 416893d4..8fca3208 100644 --- a/apps/build/management/commands/build.py +++ b/apps/build/management/commands/build.py @@ -25,7 +25,7 @@ class Command(BaseCommand): def handle(self, **options): wiki_base_dir = os.path.join(os.getcwd(), 'apps', 'wiki', 'static', 'wiki') - rng_base_dir = os.path.join(wiki_base_dir, 'rng') + rng_base_dir = os.path.join(wiki_base_dir, 'editor') build_dir = os.path.join(wiki_base_dir, 'build') self.stdout.write('Installing editor dependencies') @@ -40,4 +40,4 @@ class Command(BaseCommand): os.environ['PATH'] = '%s:%s' % (options['node_bin_path'], os.environ['PATH']) call(['./node_modules/.bin/grunt', 'build', '--output-dir=%s' % build_dir], cwd = rng_base_dir) - call_command('collectstatic', interactive = False, ignore_patterns = ['rng']) + call_command('collectstatic', interactive = False, ignore_patterns = ['editor']) diff --git a/apps/wiki/static/wiki/editor b/apps/wiki/static/wiki/editor new file mode 160000 index 00000000..b7d1baeb --- /dev/null +++ b/apps/wiki/static/wiki/editor @@ -0,0 +1 @@ +Subproject commit b7d1baeb57e59c30907d272eea8053ab74a8cfbd diff --git a/apps/wiki/templates/wiki/bootstrap.html b/apps/wiki/templates/wiki/bootstrap.html index 159a4f4e..5ed9a39c 100644 --- a/apps/wiki/templates/wiki/bootstrap.html +++ b/apps/wiki/templates/wiki/bootstrap.html @@ -9,10 +9,10 @@ {% if DEBUG %} - + - - + + {% else %} -- 2.20.1 From 2ab7896d760d01a4e4e177c8f389c15a1ca09378 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Mon, 16 Dec 2013 17:07:11 +0100 Subject: [PATCH 12/16] editor update - fixes --- apps/wiki/static/wiki/editor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wiki/static/wiki/editor b/apps/wiki/static/wiki/editor index b7d1baeb..9ff18dde 160000 --- a/apps/wiki/static/wiki/editor +++ b/apps/wiki/static/wiki/editor @@ -1 +1 @@ -Subproject commit b7d1baeb57e59c30907d272eea8053ab74a8cfbd +Subproject commit 9ff18dded676416427e4e727313dd2e23673feb9 -- 2.20.1 From a55bceb9cae1afaf3c6742ac605758d574672795 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 17 Dec 2013 15:29:21 +0100 Subject: [PATCH 13/16] update editor --- apps/wiki/static/wiki/editor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wiki/static/wiki/editor b/apps/wiki/static/wiki/editor index 9ff18dde..db307d84 160000 --- a/apps/wiki/static/wiki/editor +++ b/apps/wiki/static/wiki/editor @@ -1 +1 @@ -Subproject commit 9ff18dded676416427e4e727313dd2e23673feb9 +Subproject commit db307d848f244e349d6fd3bbfc5acef24d698862 -- 2.20.1 From 08e71162a5088f63c7f499b25cfb7310cd0ec468 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 17 Dec 2013 15:31:07 +0100 Subject: [PATCH 14/16] Configure editors diff url --- apps/wiki/templates/wiki/bootstrap.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/wiki/templates/wiki/bootstrap.html b/apps/wiki/templates/wiki/bootstrap.html index 5ed9a39c..1b4737a4 100644 --- a/apps/wiki/templates/wiki/bootstrap.html +++ b/apps/wiki/templates/wiki/bootstrap.html @@ -27,6 +27,7 @@ documentSaveUrl: function(id) { return '/editor/text/' + id + '/'; }, documentHistoryUrl: function(id) { return '/editor/history/' + id + '/'}, + documentDiffUrl: function(id) { return '/editor/diff/' + id + '/'; }, documentSaveForm: { fields: [ -- 2.20.1 From 4a8e5fa4a67bac21d0a3ffba48093df259050608 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 17 Dec 2013 17:10:14 +0100 Subject: [PATCH 15/16] Additional translation for editor --- apps/wiki/locale/pl/LC_MESSAGES/djangojs.mo | Bin 780 -> 839 bytes apps/wiki/locale/pl/LC_MESSAGES/djangojs.po | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/wiki/locale/pl/LC_MESSAGES/djangojs.mo b/apps/wiki/locale/pl/LC_MESSAGES/djangojs.mo index 33da0e006a0dd7f0bdff159ffc5210d6b1e97d4e..18cfa7a581f114719f6d1fc4089d34d01d7aee4d 100644 GIT binary patch delta 387 zcmeBSJI+>rPl#nI0}yZmu?!HW05LBRuK{8ZcmTxgK>QGh`GNQ)5DNkEPatLiVjf0_ zdO;u!GD99nvjJ&MC~W|wmjbal5OV>=?=mqk2m|RC5W1e>50C-UAixZ-iV0wBvl;vh93zzW1bqrd>D6zpg)Cn&YJB)=$?0ZJ=`r4|)u z=I2593PqWD=?Y~KAq9ru#IjT{q2Q9AT$-DjS2FQ?0lT?^p_!G5;pRL>4@S>`qN>XB uqQjeyv@<}MiOGq1nW+lpKr^y3v5G|{7GxGzF+eDVl>BV4$&+6*DFOiFQb{NP delta 319 zcmX@k*27kRPl#nI0}yZku?!H$05LZZ&jDf(I03{wKztI2*?{;O5VHgET_6?$;-^5& z3B*5u7$ncc2%$NF^im)e0AfL)_%O3f=#$jeVjWpGW&EXglo@W?C% zQk4ursUSKtFI}N5wWv5VKTm-nII#@KpRCH5$!@A(WN2k-zIiI62cx1>X6n&BM_Q9B c70QpytV%uF1T?D>VOD11\n" "Language-Team: LANGUAGE \n" @@ -19,16 +19,29 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" +#: static/wiki/editor/src/editor/modules/data/data.js:79 +msgid "Save Document" +msgstr "Zapisz dokument" + +#: static/wiki/editor/src/editor/modules/data/data.js:80 +msgid "Save" +msgstr "Zapisz" #: static/wiki/rng/src/editor/modules/data/data.js:117 #: static/wiki/rng/src/editor/modules/data/data.js:130 #: static/wiki/rng/src/editor/modules/rng/rng.js:231 msgid "editor" msgstr "edytor" +#: static/wiki/editor/src/editor/modules/data/data.js:135 +msgid "Restore Version" +msgstr "Przywracanie wersji" #: static/wiki/rng/src/editor/modules/nodePane/template.html:3 msgid "Current node" msgstr "Bieżący węzeł" +#: static/wiki/editor/src/editor/modules/data/data.js:136 +msgid "Restore" +msgstr "Przywróć" #: static/wiki/rng/src/editor/modules/rng/rng.js:62 msgid "Editor" msgstr "Edytor" -- 2.20.1 From 24817832933e76ec501753bb23d680995b3af7a1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksander=20=C5=81ukasz?= Date: Tue, 17 Dec 2013 17:10:56 +0100 Subject: [PATCH 16/16] Return correct structore on revert --- apps/wiki/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/wiki/views.py b/apps/wiki/views.py index b88d2df1..bd9e2c57 100644 --- a/apps/wiki/views.py +++ b/apps/wiki/views.py @@ -216,9 +216,9 @@ def revert(request, chunk_id): doc.at_revision(revision).revert(author=author, description=comment) return JSONResponse({ - 'text': doc.materialize() if before != doc.revision() else None, + 'document': doc.materialize() if before != doc.revision() else None, 'meta': {}, - 'revision': doc.revision(), + 'version': doc.revision(), }) else: return JSONFormInvalid(form) -- 2.20.1