From a6f296226a2a2ab56489001429c08edd5eeb00ba Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Sun, 10 Apr 2016 19:47:36 +0200 Subject: [PATCH 01/14] Add experimental liveblogging support --- djangocms_blog/admin.py | 5 + djangocms_blog/liveblog/__init__.py | 2 + djangocms_blog/liveblog/apps.py | 10 + djangocms_blog/liveblog/cms_plugins.py | 30 +++ djangocms_blog/liveblog/consumers.py | 50 ++++ .../liveblog/migrations/0001_initial.py | 32 +++ .../migrations/0002_liveblog_title.py | 20 ++ .../liveblog/migrations/__init__.py | 0 djangocms_blog/liveblog/models.py | 67 ++++++ djangocms_blog/liveblog/routing.py | 17 ++ .../liveblog/static/liveblog/js/liveblog.js | 27 +++ .../liveblog/js/reconnecting-websocket.min.js | 1 + .../liveblog/includes/post_detail.html | 11 + .../templates/liveblog/plugins/liveblog.html | 4 + .../liveblog/plugins/unpublished.html | 0 .../migrations/0016_post_liveblog.py | 21 ++ djangocms_blog/models.py | 9 + djangocms_blog/settings.py | 2 + .../0018_auto__add_field_post_liveblog.py | 222 ++++++++++++++++++ djangocms_blog/views.py | 6 + 20 files changed, 536 insertions(+) create mode 100644 djangocms_blog/liveblog/__init__.py create mode 100644 djangocms_blog/liveblog/apps.py create mode 100644 djangocms_blog/liveblog/cms_plugins.py create mode 100644 djangocms_blog/liveblog/consumers.py create mode 100644 djangocms_blog/liveblog/migrations/0001_initial.py create mode 100644 djangocms_blog/liveblog/migrations/0002_liveblog_title.py create mode 100644 djangocms_blog/liveblog/migrations/__init__.py create mode 100644 djangocms_blog/liveblog/models.py create mode 100644 djangocms_blog/liveblog/routing.py create mode 100644 djangocms_blog/liveblog/static/liveblog/js/liveblog.js create mode 100644 djangocms_blog/liveblog/static/liveblog/js/reconnecting-websocket.min.js create mode 100644 djangocms_blog/liveblog/templates/liveblog/includes/post_detail.html create mode 100644 djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html create mode 100644 djangocms_blog/liveblog/templates/liveblog/plugins/unpublished.html create mode 100644 djangocms_blog/migrations/0016_post_liveblog.py create mode 100644 djangocms_blog/south_migrations/0018_auto__add_field_post_liveblog.py diff --git a/djangocms_blog/admin.py b/djangocms_blog/admin.py index 1e834bc..51d32a1 100755 --- a/djangocms_blog/admin.py +++ b/djangocms_blog/admin.py @@ -89,6 +89,11 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, urls.extend(super(PostAdmin, self).get_urls()) return urls + def post_add_plugin(self, request, placeholder, plugin): + if plugin.plugin_type in get_setting('LIVEBLOG_PLUGINS'): + plugin = plugin.move(plugin.get_siblings().first(), 'first-sibling') + return super(PostAdmin, self).post_add_plugin(request, placeholder, plugin) + def publish_post(self, request, pk): """ Admin view to publish a single post diff --git a/djangocms_blog/liveblog/__init__.py b/djangocms_blog/liveblog/__init__.py new file mode 100644 index 0000000..ba25ec7 --- /dev/null +++ b/djangocms_blog/liveblog/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals diff --git a/djangocms_blog/liveblog/apps.py b/djangocms_blog/liveblog/apps.py new file mode 100644 index 0000000..7655e85 --- /dev/null +++ b/djangocms_blog/liveblog/apps.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + + +class LiveBlogAppConfig(AppConfig): + name = 'djangocms_blog.liveblog' + verbose_name = _('Liveblog') diff --git a/djangocms_blog/liveblog/cms_plugins.py b/djangocms_blog/liveblog/cms_plugins.py new file mode 100644 index 0000000..5487a85 --- /dev/null +++ b/djangocms_blog/liveblog/cms_plugins.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +from cms.plugin_pool import plugin_pool +from django.utils.translation import ugettext_lazy as _ +from djangocms_text_ckeditor.cms_plugins import TextPlugin + +from djangocms_blog.settings import get_setting + +from .models import Liveblog + + +class LiveblogPlugin(TextPlugin): + module = get_setting('PLUGIN_MODULE_NAME') + name = _('Liveblog item') + model = Liveblog + + def _get_render_template(self, context, instance, placeholder): + if instance.publish: + return 'liveblog/plugins/liveblog.html' + else: + return 'liveblog/plugins/unpublished.html' + + def render(self, context, instance, placeholder): + context = super(LiveblogPlugin, self).render(context, instance, placeholder) + instance.content = context['body'] + context['instance'] = instance + return context + +plugin_pool.register_plugin(LiveblogPlugin) diff --git a/djangocms_blog/liveblog/consumers.py b/djangocms_blog/liveblog/consumers.py new file mode 100644 index 0000000..91041e9 --- /dev/null +++ b/djangocms_blog/liveblog/consumers.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +import json + +from channels import Group + +from djangocms_blog.models import Post + + +def liveblog_connect(message, apphook, lang, post): + """ + Connect users to the group of the given post according to the given language + + Return with an error message if a post cannot be found + + :param message: channel connect message + :param apphook: apphook config namespace + :param lang: language + :param post: post slug + """ + try: + post = Post.objects.namespace(apphook).language(lang).active_translations(slug=post).get() + except Post.DoesNotExist: + message.reply_channel.send({ + 'text': json.dumps({'error': 'no_post'}), + }) + return + Group(post.liveblog_group).add(message.reply_channel) + + +def liveblog_disconnect(message, apphook, lang, post): + """ + Disconnect users to the group of the given post according to the given language + + Return with an error message if a post cannot be found + + :param message: channel connect message + :param apphook: apphook config namespace + :param lang: language + :param post: post slug + """ + try: + post = Post.objects.namespace(apphook).language(lang).active_translations(slug=post).get() + except Post.DoesNotExist: + message.reply_channel.send({ + 'text': json.dumps({'error': 'no_post'}), + }) + return + Group(post.liveblog_group).discard(message.reply_channel) diff --git a/djangocms_blog/liveblog/migrations/0001_initial.py b/djangocms_blog/liveblog/migrations/0001_initial.py new file mode 100644 index 0000000..6ceb53e --- /dev/null +++ b/djangocms_blog/liveblog/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import filer.fields.image +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0013_urlconfrevision'), + ('filer', '0003_thumbnailoption'), + ] + + operations = [ + migrations.CreateModel( + name='Liveblog', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin')), + ('body', models.TextField(verbose_name='body')), + ('publish', models.BooleanField(default=False, verbose_name='publish liveblog entry')), + ('image', filer.fields.image.FilerImageField(related_name='djangocms_blog_liveblog_image', on_delete=django.db.models.deletion.SET_NULL, verbose_name='image', blank=True, to='filer.Image', null=True)), + ('thumbnail', models.ForeignKey(related_name='djangocms_blog_liveblog_thumbnail', on_delete=django.db.models.deletion.SET_NULL, verbose_name='thumbnail size', blank=True, to='filer.ThumbnailOption', null=True)), + ], + options={ + 'verbose_name': 'liveblog entry', + 'verbose_name_plural': 'liveblog entries', + }, + bases=('cms.cmsplugin',), + ), + ] diff --git a/djangocms_blog/liveblog/migrations/0002_liveblog_title.py b/djangocms_blog/liveblog/migrations/0002_liveblog_title.py new file mode 100644 index 0000000..b59c5ad --- /dev/null +++ b/djangocms_blog/liveblog/migrations/0002_liveblog_title.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('liveblog', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='liveblog', + name='title', + field=models.CharField(default='', max_length=255, verbose_name='title'), + preserve_default=False, + ), + ] diff --git a/djangocms_blog/liveblog/migrations/__init__.py b/djangocms_blog/liveblog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/djangocms_blog/liveblog/models.py b/djangocms_blog/liveblog/models.py new file mode 100644 index 0000000..b0172f0 --- /dev/null +++ b/djangocms_blog/liveblog/models.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +import json + +from channels import Group +from cms.models import CMSPlugin +from cms.utils.plugins import reorder_plugins +from django.db import models +from djangocms_text_ckeditor.models import AbstractText +from django.utils.translation import ugettext_lazy as _ +from filer.fields.image import FilerImageField + +from djangocms_blog.models import thumbnail_model, Post + +DATE_FORMAT = "%a %d %b %Y %H:%M" + + +class Liveblog(AbstractText): + title = models.CharField(_('title'), max_length=255) + image = FilerImageField( + verbose_name=_('image'), blank=True, null=True, on_delete=models.SET_NULL, + related_name='djangocms_blog_liveblog_image' + ) + thumbnail = models.ForeignKey( + thumbnail_model, verbose_name=_('thumbnail size'), on_delete=models.SET_NULL, + blank=True, null=True, related_name='djangocms_blog_liveblog_thumbnail' + ) + publish = models.BooleanField(_('publish liveblog entry'), default=False) + node_order_by = '-changed_date' + + class Meta: + verbose_name = _('liveblog entry') + verbose_name_plural = _('liveblog entries') + + def save(self, no_signals=False, *args, **kwargs): + if not self.pk: + self.position = 0 + saved = super(Liveblog, self).save(*args, **kwargs) + if self.publish: + self.send() + order = CMSPlugin.objects.filter(placeholder=self.placeholder).order_by('placeholder', 'path').values_list('pk', flat=True) + reorder_plugins(self.placeholder, None, self.language, order) + return saved + + @property + def liveblog_group(self): + post = Post.objects.language(self.language).filter(liveblog=self.placeholder).first() + return post.liveblog_group + + def render(self): + print(self.position, self.path) + return self.render_plugin() + + def send(self): + """ + Render the content and send to the related group + """ + notification = { + 'id': self.pk, + 'content': self.render(), + 'creation_date': self.creation_date.strftime(DATE_FORMAT), + 'changed_date': self.changed_date.strftime(DATE_FORMAT), + } + Group(self.liveblog_group).send({ + 'text': json.dumps(notification), + }) diff --git a/djangocms_blog/liveblog/routing.py b/djangocms_blog/liveblog/routing.py new file mode 100644 index 0000000..7751c4c --- /dev/null +++ b/djangocms_blog/liveblog/routing.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +from channels import route + +from .consumers import liveblog_connect, liveblog_disconnect + +channel_routing = [ + route( + 'websocket.connect', liveblog_connect, + path=r'^/liveblog/(?P[a-zA-Z0-9_-]+)/(?P[a-zA-Z_-]+)/(?P[a-zA-Z0-9_-]+)/$' + ), + route( + 'websocket.disconnect', liveblog_disconnect, + path=r'^/liveblog/(?P[a-zA-Z0-9_-]+)/(?P[a-zA-Z_-]+)/(?P[a-zA-Z0-9_-]+)/$' + ), +] diff --git a/djangocms_blog/liveblog/static/liveblog/js/liveblog.js b/djangocms_blog/liveblog/static/liveblog/js/liveblog.js new file mode 100644 index 0000000..1a2105f --- /dev/null +++ b/djangocms_blog/liveblog/static/liveblog/js/liveblog.js @@ -0,0 +1,27 @@ +$(function () { + // Correctly decide between ws:// and wss:// + var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws"; + var ws_path = ws_scheme + '://' + window.location.host + "/liveblog/liveblog/" + liveblog_apphook + "/" + liveblog_language + "/" + liveblog_post + "/"; + console.log("Connecting to " + ws_path); + var socket = new ReconnectingWebSocket(ws_path); + // Handle incoming messages + socket.onmessage = function (message) { + // Decode the JSON + console.log("Got message " + message.data); + var data = JSON.parse(message.data); + // See if there's a div to replace it in, or if we should add a new one + var existing = $("div[data-post-id=" + data.id + "]"); + if (existing.length) { + existing.replaceWith(data.content); + } else { + $("#liveblog-posts").prepend(data.content); + } + }; + // Helpful debugging + socket.onopen = function () { + console.log("Connected to notification socket"); + } + socket.onclose = function () { + console.log("Disconnected to notification socket"); + } +}); diff --git a/djangocms_blog/liveblog/static/liveblog/js/reconnecting-websocket.min.js b/djangocms_blog/liveblog/static/liveblog/js/reconnecting-websocket.min.js new file mode 100644 index 0000000..3015099 --- /dev/null +++ b/djangocms_blog/liveblog/static/liveblog/js/reconnecting-websocket.min.js @@ -0,0 +1 @@ +!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a}); diff --git a/djangocms_blog/liveblog/templates/liveblog/includes/post_detail.html b/djangocms_blog/liveblog/templates/liveblog/includes/post_detail.html new file mode 100644 index 0000000..a318eb6 --- /dev/null +++ b/djangocms_blog/liveblog/templates/liveblog/includes/post_detail.html @@ -0,0 +1,11 @@ +{% load cms_tags sekizai_tags %} +{% add_data "js-script" "liveblog/js/reconnecting-websocket.min.js" %} +{% add_data "js-script" "liveblog/js/liveblog.js" %} + +
+ {% render_placeholder post.liveblog %} +
diff --git a/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html b/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html new file mode 100644 index 0000000..526af0e --- /dev/null +++ b/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html @@ -0,0 +1,4 @@ +
+

{{ instance.title }}{{ instance.creation_date|date:"D d M Y H:i" }}

+ {{ instance.content|safe }} +
diff --git a/djangocms_blog/liveblog/templates/liveblog/plugins/unpublished.html b/djangocms_blog/liveblog/templates/liveblog/plugins/unpublished.html new file mode 100644 index 0000000..e69de29 diff --git a/djangocms_blog/migrations/0016_post_liveblog.py b/djangocms_blog/migrations/0016_post_liveblog.py new file mode 100644 index 0000000..52555d3 --- /dev/null +++ b/djangocms_blog/migrations/0016_post_liveblog.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import cms.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0013_urlconfrevision'), + ('djangocms_blog', '0015_auto_20160408_1849'), + ] + + operations = [ + migrations.AddField( + model_name='post', + name='liveblog', + field=cms.models.fields.PlaceholderField(related_name='live_blog', slotname='live_blog', editable=False, to='cms.Placeholder', null=True), + ), + ] diff --git a/djangocms_blog/models.py b/djangocms_blog/models.py index 3a62aee..afa8212 100644 --- a/djangocms_blog/models.py +++ b/djangocms_blog/models.py @@ -163,6 +163,7 @@ class Post(KnockerModel, ModelMeta, TranslatableModel): meta={'unique_together': (('language_code', 'slug'),)} ) content = PlaceholderField('post_content', related_name='post_content') + liveblog = PlaceholderField('live_blog', related_name='live_blog') objects = GenericDateTaggedManager() tags = TaggableManager(blank=True, related_name='djangocms_blog_tags') @@ -350,6 +351,14 @@ class Post(KnockerModel, ModelMeta, TranslatableModel): def get_cache_key(self, language, prefix): return 'djangocms-blog:{2}:{0}:{1}'.format(language, self.guid, prefix) + @property + def liveblog_group(self): + return 'liveblog/{apphook}/{lang}/{post}'.format( + lang=self.get_current_language(), + apphook=self.app_config.namespace, + post=self.safe_translation_getter('slug', any_language=True) + ) + class BasePostPlugin(CMSPlugin): app_config = AppHookConfigField( diff --git a/djangocms_blog/settings.py b/djangocms_blog/settings.py index de47dfb..a1b220c 100644 --- a/djangocms_blog/settings.py +++ b/djangocms_blog/settings.py @@ -129,6 +129,8 @@ def get_setting(name): settings, 'BLOG_FEED_LATEST_ITEMS', 10), 'BLOG_FEED_TAGS_ITEMS': getattr( settings, 'BLOG_FEED_TAGS_ITEMS', 10), + 'BLOG_LIVEBLOG_PLUGINS': getattr( + settings, 'BLOG_LIVEBLOG_PLUGINS', ('LiveblogPlugin',)), } return default['BLOG_%s' % name] diff --git a/djangocms_blog/south_migrations/0018_auto__add_field_post_liveblog.py b/djangocms_blog/south_migrations/0018_auto__add_field_post_liveblog.py new file mode 100644 index 0000000..4cebb73 --- /dev/null +++ b/djangocms_blog/south_migrations/0018_auto__add_field_post_liveblog.py @@ -0,0 +1,222 @@ +# -*- 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 'Post.liveblog' + db.add_column('djangocms_blog_post', 'liveblog', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cms.Placeholder'], null=True, related_name='live_blog'), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Post.liveblog' + db.delete_column('djangocms_blog_post', 'liveblog_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']", 'blank': 'True', 'symmetrical': 'False'}) + }, + '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', [], {'blank': 'True', 'max_length': '75'}), + 'first_name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '30'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True', 'related_name': "'user_set'", 'symmetrical': 'False'}), + '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', [], {'blank': 'True', 'max_length': '30'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True', 'related_name': "'user_set'", 'symmetrical': 'False'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'cms.cmsplugin': { + 'Meta': {'object_name': 'CMSPlugin'}, + 'changed_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), + 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['cms.CMSPlugin']", 'null': 'True'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), + 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True', 'null': 'True'}) + }, + 'cms.placeholder': { + 'Meta': {'object_name': 'Placeholder'}, + 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'cmsplugin_filer_image.thumbnailoption': { + 'Meta': {'ordering': "('width', 'height')", 'object_name': 'ThumbnailOption'}, + 'crop': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'height': ('django.db.models.fields.IntegerField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'upscale': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'width': ('django.db.models.fields.IntegerField', [], {}) + }, + 'contenttypes.contenttype': { + 'Meta': {'db_table': "'django_content_type'", 'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType'}, + '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'}) + }, + 'djangocms_blog.authorentriesplugin': { + 'Meta': {'object_name': 'AuthorEntriesPlugin'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'blank': 'True', 'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True'}), + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}), + 'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'primary_key': 'True', 'unique': 'True'}), + 'latest_posts': ('django.db.models.fields.IntegerField', [], {'default': '5'}) + }, + 'djangocms_blog.blogcategory': { + 'Meta': {'object_name': 'BlogCategory'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'date_modified': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['djangocms_blog.BlogCategory']", 'null': 'True'}) + }, + 'djangocms_blog.blogcategorytranslation': { + 'Meta': {'db_table': "'djangocms_blog_blogcategory_translation'", 'unique_together': "[('language_code', 'slug'), ('language_code', 'master')]", 'object_name': 'BlogCategoryTranslation'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + 'master': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangocms_blog.BlogCategory']", 'null': 'True', 'related_name': "'translations'"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.SlugField', [], {'blank': 'True', 'max_length': '50'}) + }, + 'djangocms_blog.blogconfig': { + 'Meta': {'object_name': 'BlogConfig'}, + 'app_data': ('app_data.fields.AppDataField', [], {'default': "'{}'"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'namespace': ('django.db.models.fields.CharField', [], {'unique': 'True', 'default': 'None', 'max_length': '100'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'djangocms_blog.blogconfigtranslation': { + 'Meta': {'db_table': "'djangocms_blog_blogconfig_translation'", 'unique_together': "[('language_code', 'master')]", 'object_name': 'BlogConfigTranslation'}, + 'app_title': ('django.db.models.fields.CharField', [], {'max_length': '234'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + 'master': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True', 'related_name': "'translations'"}), + 'object_name': ('django.db.models.fields.CharField', [], {'default': "'Article'", 'max_length': '234'}) + }, + 'djangocms_blog.genericblogplugin': { + 'Meta': {'object_name': 'GenericBlogPlugin'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'blank': 'True', 'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True'}), + 'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'primary_key': 'True', 'unique': 'True'}) + }, + 'djangocms_blog.latestpostsplugin': { + 'Meta': {'object_name': 'LatestPostsPlugin'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'blank': 'True', 'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True'}), + 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djangocms_blog.BlogCategory']", 'blank': 'True', 'symmetrical': 'False'}), + 'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'primary_key': 'True', 'unique': 'True'}), + 'latest_posts': ('django.db.models.fields.IntegerField', [], {'default': '5'}) + }, + 'djangocms_blog.post': { + 'Meta': {'ordering': "('-date_published', '-date_created')", 'object_name': 'Post'}, + 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['auth.User']", 'null': 'True', 'related_name': "'djangocms_blog_post_author'"}), + 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djangocms_blog.BlogCategory']", 'blank': 'True', 'related_name': "'blog_posts'", 'symmetrical': 'False'}), + 'content': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True', 'related_name': "'post_content'"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'date_modified': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), + 'date_published': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'null': 'True'}), + 'date_published_end': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'null': 'True'}), + 'enable_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'liveblog': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True', 'related_name': "'live_blog'"}), + 'main_image': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['filer.Image']", 'on_delete': 'models.SET_NULL', 'null': 'True', 'related_name': "'djangocms_blog_post_image'"}), + 'main_image_full': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['cmsplugin_filer_image.ThumbnailOption']", 'on_delete': 'models.SET_NULL', 'null': 'True', 'related_name': "'djangocms_blog_post_full'"}), + 'main_image_thumbnail': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['cmsplugin_filer_image.ThumbnailOption']", 'on_delete': 'models.SET_NULL', 'null': 'True', 'related_name': "'djangocms_blog_post_thumbnail'"}), + 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'blank': 'True', 'symmetrical': 'False'}) + }, + 'djangocms_blog.posttranslation': { + 'Meta': {'db_table': "'djangocms_blog_post_translation'", 'unique_together': "[('language_code', 'slug'), ('language_code', 'master')]", 'object_name': 'PostTranslation'}, + 'abstract': ('djangocms_text_ckeditor.fields.HTMLField', [], {'blank': 'True', 'default': "''"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + 'master': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangocms_blog.Post']", 'null': 'True', 'related_name': "'translations'"}), + 'meta_description': ('django.db.models.fields.TextField', [], {'blank': 'True', 'default': "''"}), + 'meta_keywords': ('django.db.models.fields.TextField', [], {'blank': 'True', 'default': "''"}), + 'meta_title': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", 'max_length': '255'}), + 'post_text': ('djangocms_text_ckeditor.fields.HTMLField', [], {'blank': 'True', 'default': "''"}), + 'slug': ('django.db.models.fields.SlugField', [], {'blank': 'True', 'max_length': '50'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'filer.file': { + 'Meta': {'object_name': 'File'}, + '_file_size': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True', 'null': 'True'}), + 'file': ('django.db.models.fields.files.FileField', [], {'blank': 'True', 'null': 'True', 'max_length': '255'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['filer.Folder']", 'null': 'True', 'related_name': "'all_files'"}), + 'has_all_mandatory_data': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", 'max_length': '255'}), + 'original_filename': ('django.db.models.fields.CharField', [], {'blank': 'True', 'null': 'True', 'max_length': '255'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['auth.User']", 'null': 'True', 'related_name': "'owned_files'"}), + 'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'related_name': "'polymorphic_filer.file_set+'"}), + 'sha1': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", 'max_length': '40'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'filer.folder': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('parent', 'name'),)", 'object_name': 'Folder'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'modified_at': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['auth.User']", 'null': 'True', 'related_name': "'filer_owned_folders'"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['filer.Folder']", 'null': 'True', 'related_name': "'children'"}), + 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'filer.image': { + 'Meta': {'object_name': 'Image'}, + '_height': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True'}), + '_width': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True'}), + 'author': ('django.db.models.fields.CharField', [], {'blank': 'True', 'null': 'True', 'max_length': '255'}), + 'date_taken': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'null': 'True'}), + 'default_alt_text': ('django.db.models.fields.CharField', [], {'blank': 'True', 'null': 'True', 'max_length': '255'}), + 'default_caption': ('django.db.models.fields.CharField', [], {'blank': 'True', 'null': 'True', 'max_length': '255'}), + 'file_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['filer.File']", 'primary_key': 'True', 'unique': 'True'}), + 'must_always_publish_author_credit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'must_always_publish_copyright': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'subject_location': ('django.db.models.fields.CharField', [], {'blank': 'True', 'null': 'True', 'default': 'None', 'max_length': '64'}) + }, + 'sites.site': { + 'Meta': {'db_table': "'django_site'", 'ordering': "('domain',)", 'object_name': 'Site'}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + } + } + + complete_apps = ['djangocms_blog'] \ No newline at end of file diff --git a/djangocms_blog/views.py b/djangocms_blog/views.py index 6e09503..2e99e14 100644 --- a/djangocms_blog/views.py +++ b/djangocms_blog/views.py @@ -51,6 +51,12 @@ class BaseBlogView(AppConfigMixin, ViewUrlMixin): template_path = (self.config and self.config.template_prefix) or 'djangocms_blog' return os.path.join(template_path, self.base_template_name) + def liveblog_enabled(self): + try: + from django.apps import apps + return apps.is_installed('djangocms_blog.liveblog') + except ImportError: + return False class BaseBlogListView(BaseBlogView): context_object_name = 'post_list' From c0002ddd5fbbdf21d9ee7074c95dba4e18e19caf Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Sun, 17 Apr 2016 12:02:04 +0200 Subject: [PATCH 02/14] cleanup PoC --- djangocms_blog/liveblog/cms_plugins.py | 1 + djangocms_blog/liveblog/models.py | 1 - .../liveblog/templates/liveblog/plugins/liveblog.html | 3 ++- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/djangocms_blog/liveblog/cms_plugins.py b/djangocms_blog/liveblog/cms_plugins.py index 5487a85..6c1895d 100644 --- a/djangocms_blog/liveblog/cms_plugins.py +++ b/djangocms_blog/liveblog/cms_plugins.py @@ -14,6 +14,7 @@ class LiveblogPlugin(TextPlugin): module = get_setting('PLUGIN_MODULE_NAME') name = _('Liveblog item') model = Liveblog + fields = ('title', 'body', 'publish') def _get_render_template(self, context, instance, placeholder): if instance.publish: diff --git a/djangocms_blog/liveblog/models.py b/djangocms_blog/liveblog/models.py index b0172f0..7a3df89 100644 --- a/djangocms_blog/liveblog/models.py +++ b/djangocms_blog/liveblog/models.py @@ -49,7 +49,6 @@ class Liveblog(AbstractText): return post.liveblog_group def render(self): - print(self.position, self.path) return self.render_plugin() def send(self): diff --git a/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html b/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html index 526af0e..402a2a5 100644 --- a/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html +++ b/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html @@ -1,4 +1,5 @@
-

{{ instance.title }}{{ instance.creation_date|date:"D d M Y H:i" }}

+

{{ instance.title }}

+

{{ instance.creation_date|date:"D d M Y H:i" }}

{{ instance.content|safe }}
From eb5d98c9e34f8aa93a4ad9c717f4c505505f8cca Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Sun, 5 Jun 2016 18:22:48 +0200 Subject: [PATCH 03/14] Fix migrations --- .../{0016_post_liveblog.py => 0018_post_liveblog.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename djangocms_blog/migrations/{0016_post_liveblog.py => 0018_post_liveblog.py} (83%) diff --git a/djangocms_blog/migrations/0016_post_liveblog.py b/djangocms_blog/migrations/0018_post_liveblog.py similarity index 83% rename from djangocms_blog/migrations/0016_post_liveblog.py rename to djangocms_blog/migrations/0018_post_liveblog.py index 52555d3..db8d1aa 100644 --- a/djangocms_blog/migrations/0016_post_liveblog.py +++ b/djangocms_blog/migrations/0018_post_liveblog.py @@ -8,8 +8,8 @@ import cms.models.fields class Migration(migrations.Migration): dependencies = [ - ('cms', '0013_urlconfrevision'), - ('djangocms_blog', '0015_auto_20160408_1849'), + ('cms', '__first__'), + ('djangocms_blog', '0017_thumbnail_move'), ] operations = [ From d9dd192ca829690d59e91f213da202235cc0a0a3 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Sun, 5 Jun 2016 22:49:17 +0200 Subject: [PATCH 04/14] Update channels configuration --- cms_helper.py | 4 +++- tests/test_utils/routing.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/test_utils/routing.py diff --git a/cms_helper.py b/cms_helper.py index 7a2b661..1643ff7 100755 --- a/cms_helper.py +++ b/cms_helper.py @@ -110,10 +110,12 @@ except ImportError: try: import knocker # pragma: no cover # NOQA HELPER_SETTINGS['INSTALLED_APPS'].append('knocker') + HELPER_SETTINGS['INSTALLED_APPS'].append('channels') + HELPER_SETTINGS['INSTALLED_APPS'].append('djangocms_blog.liveblog',) HELPER_SETTINGS['CHANNEL_LAYERS'] = { 'default': { 'BACKEND': 'asgiref.inmemory.ChannelLayer', - 'ROUTING': 'knocker.routing.channel_routing', + 'ROUTING': 'tests.test_utils.routing.channel_routing', }, } except ImportError: diff --git a/tests/test_utils/routing.py b/tests/test_utils/routing.py new file mode 100644 index 0000000..94188ea --- /dev/null +++ b/tests/test_utils/routing.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +from channels import include + +from djangocms_blog.liveblog.routing import channel_routing as djangocms_blog_routing +from knocker.routing import channel_routing as knocker_routing + +channel_routing = [ + include(djangocms_blog_routing, path=r'^/liveblog'), + include(knocker_routing, path=r'^/knocker'), +] From 3c4eebdd02fe24e79fcc0d1eb4c2a38efd754af7 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Sun, 5 Jun 2016 22:49:54 +0200 Subject: [PATCH 05/14] Remove jQuery dependency --- .../liveblog/static/liveblog/js/liveblog.js | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/djangocms_blog/liveblog/static/liveblog/js/liveblog.js b/djangocms_blog/liveblog/static/liveblog/js/liveblog.js index 1a2105f..3f7ff3a 100644 --- a/djangocms_blog/liveblog/static/liveblog/js/liveblog.js +++ b/djangocms_blog/liveblog/static/liveblog/js/liveblog.js @@ -1,27 +1,24 @@ -$(function () { +document.addEventListener("DOMContentLoaded", function() { // Correctly decide between ws:// and wss:// var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws"; var ws_path = ws_scheme + '://' + window.location.host + "/liveblog/liveblog/" + liveblog_apphook + "/" + liveblog_language + "/" + liveblog_post + "/"; - console.log("Connecting to " + ws_path); var socket = new ReconnectingWebSocket(ws_path); // Handle incoming messages socket.onmessage = function (message) { // Decode the JSON - console.log("Got message " + message.data); var data = JSON.parse(message.data); // See if there's a div to replace it in, or if we should add a new one - var existing = $("div[data-post-id=" + data.id + "]"); + var existing = document.querySelectorAll("div[data-post-id*='" + data.id + "']"); if (existing.length) { - existing.replaceWith(data.content); + existing.parentNode.replaceChild(data.content, existing); } else { - $("#liveblog-posts").prepend(data.content); + var item = document.createElement('div'); + item.innerHTML = data.content; + document.getElementById("liveblog-posts").insertBefore( + item.children[0], document.getElementById("liveblog-posts").children[0] + ); } }; - // Helpful debugging - socket.onopen = function () { - console.log("Connected to notification socket"); - } - socket.onclose = function () { - console.log("Disconnected to notification socket"); - } -}); + +}, false); + From eab9083d900bdf0f60bbda3fbcf4233b44f510b0 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Sun, 5 Jun 2016 22:50:26 +0200 Subject: [PATCH 06/14] Update code --- djangocms_blog/liveblog/models.py | 14 +++++++------- .../templates/djangocms_blog/post_detail.html | 3 +++ djangocms_blog/views.py | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/djangocms_blog/liveblog/models.py b/djangocms_blog/liveblog/models.py index 7a3df89..48fe277 100644 --- a/djangocms_blog/liveblog/models.py +++ b/djangocms_blog/liveblog/models.py @@ -34,19 +34,18 @@ class Liveblog(AbstractText): verbose_name_plural = _('liveblog entries') def save(self, no_signals=False, *args, **kwargs): - if not self.pk: - self.position = 0 saved = super(Liveblog, self).save(*args, **kwargs) if self.publish: self.send() - order = CMSPlugin.objects.filter(placeholder=self.placeholder).order_by('placeholder', 'path').values_list('pk', flat=True) + order = CMSPlugin.objects.filter(placeholder=self.placeholder).order_by('placeholder', '-path').values_list('pk', flat=True) reorder_plugins(self.placeholder, None, self.language, order) return saved @property def liveblog_group(self): post = Post.objects.language(self.language).filter(liveblog=self.placeholder).first() - return post.liveblog_group + if post: + return post.liveblog_group def render(self): return self.render_plugin() @@ -61,6 +60,7 @@ class Liveblog(AbstractText): 'creation_date': self.creation_date.strftime(DATE_FORMAT), 'changed_date': self.changed_date.strftime(DATE_FORMAT), } - Group(self.liveblog_group).send({ - 'text': json.dumps(notification), - }) + if self.liveblog_group: + Group(self.liveblog_group).send({ + 'text': json.dumps(notification), + }) diff --git a/djangocms_blog/templates/djangocms_blog/post_detail.html b/djangocms_blog/templates/djangocms_blog/post_detail.html index 6c5fc8b..d9ab105 100644 --- a/djangocms_blog/templates/djangocms_blog/post_detail.html +++ b/djangocms_blog/templates/djangocms_blog/post_detail.html @@ -21,6 +21,9 @@ {% endif %} {% endspaceless %} + {% if view.liveblog_enabled %} + {% include "liveblog/includes/post_detail.html" %} + {% endif %} {% if post.app_config.use_placeholder %}
{% render_placeholder post.content %}
{% else %} diff --git a/djangocms_blog/views.py b/djangocms_blog/views.py index 2e99e14..ee6845f 100644 --- a/djangocms_blog/views.py +++ b/djangocms_blog/views.py @@ -58,6 +58,7 @@ class BaseBlogView(AppConfigMixin, ViewUrlMixin): except ImportError: return False + class BaseBlogListView(BaseBlogView): context_object_name = 'post_list' base_template_name = 'post_list.html' From 13a26650ad604b267fdaf7fc1df25c561d1d2d38 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Sun, 5 Jun 2016 23:11:58 +0200 Subject: [PATCH 07/14] Add flag to enable liveblog --- djangocms_blog/admin.py | 8 ++++- .../migrations/0019_auto_20160605_2305.py | 31 +++++++++++++++++++ djangocms_blog/models.py | 1 + djangocms_blog/views.py | 11 +++---- 4 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 djangocms_blog/migrations/0019_auto_20160605_2305.py diff --git a/djangocms_blog/admin.py b/djangocms_blog/admin.py index 51d32a1..e74e6f1 100755 --- a/djangocms_blog/admin.py +++ b/djangocms_blog/admin.py @@ -6,6 +6,7 @@ from copy import deepcopy from aldryn_apphooks_config.admin import BaseAppHookConfig, ModelAppHookConfig from cms.admin.placeholderadmin import FrontendEditableAdminMixin, PlaceholderAdminMixin from django import forms +from django.apps import apps from django.conf import settings from django.conf.urls import url from django.contrib import admin @@ -60,7 +61,12 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, }), ('Info', { 'fields': (['slug', 'tags'], - ('date_published', 'date_published_end', 'enable_comments')), + ('date_published', 'date_published_end',), + ( + 'enable_comments', + 'enable_liveblog' if apps.is_installed('djangocms_blog.liveblog') + else None + )), 'classes': ('collapse',) }), ('Images', { diff --git a/djangocms_blog/migrations/0019_auto_20160605_2305.py b/djangocms_blog/migrations/0019_auto_20160605_2305.py new file mode 100644 index 0000000..8d43417 --- /dev/null +++ b/djangocms_blog/migrations/0019_auto_20160605_2305.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-06-05 21:05 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('djangocms_blog', '0018_post_liveblog'), + ] + + operations = [ + migrations.AddField( + model_name='post', + name='enable_liveblog', + field=models.BooleanField(default=False, verbose_name='enable liveblog on post'), + ), + migrations.AlterField( + model_name='post', + name='main_image_full', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='djangocms_blog_post_full', to='cmsplugin_filer_image.ThumbnailOption', verbose_name='main image full'), + ), + migrations.AlterField( + model_name='post', + name='main_image_thumbnail', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='djangocms_blog_post_thumbnail', to='cmsplugin_filer_image.ThumbnailOption', verbose_name='main image thumbnail'), + ), + ] diff --git a/djangocms_blog/models.py b/djangocms_blog/models.py index afa8212..e8ef178 100644 --- a/djangocms_blog/models.py +++ b/djangocms_blog/models.py @@ -164,6 +164,7 @@ class Post(KnockerModel, ModelMeta, TranslatableModel): ) content = PlaceholderField('post_content', related_name='post_content') liveblog = PlaceholderField('live_blog', related_name='live_blog') + enable_liveblog = models.BooleanField(verbose_name=_('enable liveblog on post'), default=False) objects = GenericDateTaggedManager() tags = TaggableManager(blank=True, related_name='djangocms_blog_tags') diff --git a/djangocms_blog/views.py b/djangocms_blog/views.py index ee6845f..10ee0b0 100644 --- a/djangocms_blog/views.py +++ b/djangocms_blog/views.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, print_function, unicode_literals import os.path from aldryn_apphooks_config.mixins import AppConfigMixin +from django.apps import apps from django.contrib.auth import get_user_model from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse @@ -51,13 +52,6 @@ class BaseBlogView(AppConfigMixin, ViewUrlMixin): template_path = (self.config and self.config.template_prefix) or 'djangocms_blog' return os.path.join(template_path, self.base_template_name) - def liveblog_enabled(self): - try: - from django.apps import apps - return apps.is_installed('djangocms_blog.liveblog') - except ImportError: - return False - class BaseBlogListView(BaseBlogView): context_object_name = 'post_list' @@ -79,6 +73,9 @@ class PostDetailView(TranslatableSlugMixin, BaseBlogView, DetailView): view_url_name = 'djangocms_blog:post-detail' instant_article = False + def liveblog_enabled(self): + return self.object.enable_liveblog and apps.is_installed('djangocms_blog.liveblog') + def get_template_names(self): if self.instant_article: template_path = (self.config and self.config.template_prefix) or 'djangocms_blog' From 0f4056f67856f702de5f010f8752fc5ceb9bcce2 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Wed, 22 Jun 2016 23:43:16 +0200 Subject: [PATCH 08/14] Fix migrations --- djangocms_blog/migrations/0019_auto_20160605_2305.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/djangocms_blog/migrations/0019_auto_20160605_2305.py b/djangocms_blog/migrations/0019_auto_20160605_2305.py index 8d43417..f2b0f8d 100644 --- a/djangocms_blog/migrations/0019_auto_20160605_2305.py +++ b/djangocms_blog/migrations/0019_auto_20160605_2305.py @@ -18,14 +18,4 @@ class Migration(migrations.Migration): name='enable_liveblog', field=models.BooleanField(default=False, verbose_name='enable liveblog on post'), ), - migrations.AlterField( - model_name='post', - name='main_image_full', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='djangocms_blog_post_full', to='cmsplugin_filer_image.ThumbnailOption', verbose_name='main image full'), - ), - migrations.AlterField( - model_name='post', - name='main_image_thumbnail', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='djangocms_blog_post_thumbnail', to='cmsplugin_filer_image.ThumbnailOption', verbose_name='main image thumbnail'), - ), ] From a6feb4629ddc87171b41c36b4857e0c44e352ad9 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Wed, 22 Jun 2016 23:55:34 +0200 Subject: [PATCH 09/14] Fix lieblog channels group --- djangocms_blog/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangocms_blog/models.py b/djangocms_blog/models.py index e8ef178..ba72459 100644 --- a/djangocms_blog/models.py +++ b/djangocms_blog/models.py @@ -354,7 +354,7 @@ class Post(KnockerModel, ModelMeta, TranslatableModel): @property def liveblog_group(self): - return 'liveblog/{apphook}/{lang}/{post}'.format( + return 'liveblog-{apphook}-{lang}-{post}'.format( lang=self.get_current_language(), apphook=self.app_config.namespace, post=self.safe_translation_getter('slug', any_language=True) From 9bf5fa88952f4dd9e751401c1701091539708e37 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Wed, 22 Jun 2016 23:55:51 +0200 Subject: [PATCH 10/14] Fix plugins order --- djangocms_blog/liveblog/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/djangocms_blog/liveblog/models.py b/djangocms_blog/liveblog/models.py index 48fe277..161db29 100644 --- a/djangocms_blog/liveblog/models.py +++ b/djangocms_blog/liveblog/models.py @@ -37,7 +37,9 @@ class Liveblog(AbstractText): saved = super(Liveblog, self).save(*args, **kwargs) if self.publish: self.send() - order = CMSPlugin.objects.filter(placeholder=self.placeholder).order_by('placeholder', '-path').values_list('pk', flat=True) + order = CMSPlugin.objects.filter( + placeholder=self.placeholder + ).order_by('placeholder', 'path').values_list('pk', flat=True) reorder_plugins(self.placeholder, None, self.language, order) return saved From 413c9bb3aaf960c1cd5ebefcc83e1b678a869d6f Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Wed, 22 Jun 2016 23:57:29 +0200 Subject: [PATCH 11/14] Fix code style --- djangocms_blog/admin.py | 2 +- djangocms_blog/liveblog/models.py | 4 +- djangocms_blog/liveblog/routing.py | 6 +- .../0018_auto__add_field_post_liveblog.py | 222 ------------------ tests/test_utils/routing.py | 2 +- 5 files changed, 8 insertions(+), 228 deletions(-) delete mode 100644 djangocms_blog/south_migrations/0018_auto__add_field_post_liveblog.py diff --git a/djangocms_blog/admin.py b/djangocms_blog/admin.py index e74e6f1..3a21949 100755 --- a/djangocms_blog/admin.py +++ b/djangocms_blog/admin.py @@ -65,7 +65,7 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, ( 'enable_comments', 'enable_liveblog' if apps.is_installed('djangocms_blog.liveblog') - else None + else None )), 'classes': ('collapse',) }), diff --git a/djangocms_blog/liveblog/models.py b/djangocms_blog/liveblog/models.py index 161db29..f6a4396 100644 --- a/djangocms_blog/liveblog/models.py +++ b/djangocms_blog/liveblog/models.py @@ -7,11 +7,11 @@ from channels import Group from cms.models import CMSPlugin from cms.utils.plugins import reorder_plugins from django.db import models -from djangocms_text_ckeditor.models import AbstractText from django.utils.translation import ugettext_lazy as _ +from djangocms_text_ckeditor.models import AbstractText from filer.fields.image import FilerImageField -from djangocms_blog.models import thumbnail_model, Post +from djangocms_blog.models import Post, thumbnail_model DATE_FORMAT = "%a %d %b %Y %H:%M" diff --git a/djangocms_blog/liveblog/routing.py b/djangocms_blog/liveblog/routing.py index 7751c4c..fb1ff06 100644 --- a/djangocms_blog/liveblog/routing.py +++ b/djangocms_blog/liveblog/routing.py @@ -8,10 +8,12 @@ from .consumers import liveblog_connect, liveblog_disconnect channel_routing = [ route( 'websocket.connect', liveblog_connect, - path=r'^/liveblog/(?P[a-zA-Z0-9_-]+)/(?P[a-zA-Z_-]+)/(?P[a-zA-Z0-9_-]+)/$' + path=r'^/liveblog/(?P[a-zA-Z0-9_-]+)/' + r'(?P[a-zA-Z_-]+)/(?P[a-zA-Z0-9_-]+)/$' ), route( 'websocket.disconnect', liveblog_disconnect, - path=r'^/liveblog/(?P[a-zA-Z0-9_-]+)/(?P[a-zA-Z_-]+)/(?P[a-zA-Z0-9_-]+)/$' + path=r'^/liveblog/(?P[a-zA-Z0-9_-]+)/' + r'(?P[a-zA-Z_-]+)/(?P[a-zA-Z0-9_-]+)/$' ), ] diff --git a/djangocms_blog/south_migrations/0018_auto__add_field_post_liveblog.py b/djangocms_blog/south_migrations/0018_auto__add_field_post_liveblog.py deleted file mode 100644 index 4cebb73..0000000 --- a/djangocms_blog/south_migrations/0018_auto__add_field_post_liveblog.py +++ /dev/null @@ -1,222 +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 'Post.liveblog' - db.add_column('djangocms_blog_post', 'liveblog', - self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cms.Placeholder'], null=True, related_name='live_blog'), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'Post.liveblog' - db.delete_column('djangocms_blog_post', 'liveblog_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']", 'blank': 'True', 'symmetrical': 'False'}) - }, - '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', [], {'blank': 'True', 'max_length': '75'}), - 'first_name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '30'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True', 'related_name': "'user_set'", 'symmetrical': 'False'}), - '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', [], {'blank': 'True', 'max_length': '30'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True', 'related_name': "'user_set'", 'symmetrical': 'False'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'cms.cmsplugin': { - 'Meta': {'object_name': 'CMSPlugin'}, - 'changed_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), - 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), - 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['cms.CMSPlugin']", 'null': 'True'}), - 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), - 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), - 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), - 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True', 'null': 'True'}) - }, - 'cms.placeholder': { - 'Meta': {'object_name': 'Placeholder'}, - 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) - }, - 'cmsplugin_filer_image.thumbnailoption': { - 'Meta': {'ordering': "('width', 'height')", 'object_name': 'ThumbnailOption'}, - 'crop': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'height': ('django.db.models.fields.IntegerField', [], {}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'upscale': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'width': ('django.db.models.fields.IntegerField', [], {}) - }, - 'contenttypes.contenttype': { - 'Meta': {'db_table': "'django_content_type'", 'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType'}, - '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'}) - }, - 'djangocms_blog.authorentriesplugin': { - 'Meta': {'object_name': 'AuthorEntriesPlugin'}, - 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'blank': 'True', 'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True'}), - 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}), - 'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'primary_key': 'True', 'unique': 'True'}), - 'latest_posts': ('django.db.models.fields.IntegerField', [], {'default': '5'}) - }, - 'djangocms_blog.blogcategory': { - 'Meta': {'object_name': 'BlogCategory'}, - 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True'}), - 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'date_modified': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['djangocms_blog.BlogCategory']", 'null': 'True'}) - }, - 'djangocms_blog.blogcategorytranslation': { - 'Meta': {'db_table': "'djangocms_blog_blogcategory_translation'", 'unique_together': "[('language_code', 'slug'), ('language_code', 'master')]", 'object_name': 'BlogCategoryTranslation'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), - 'master': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangocms_blog.BlogCategory']", 'null': 'True', 'related_name': "'translations'"}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'slug': ('django.db.models.fields.SlugField', [], {'blank': 'True', 'max_length': '50'}) - }, - 'djangocms_blog.blogconfig': { - 'Meta': {'object_name': 'BlogConfig'}, - 'app_data': ('app_data.fields.AppDataField', [], {'default': "'{}'"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'namespace': ('django.db.models.fields.CharField', [], {'unique': 'True', 'default': 'None', 'max_length': '100'}), - 'type': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'djangocms_blog.blogconfigtranslation': { - 'Meta': {'db_table': "'djangocms_blog_blogconfig_translation'", 'unique_together': "[('language_code', 'master')]", 'object_name': 'BlogConfigTranslation'}, - 'app_title': ('django.db.models.fields.CharField', [], {'max_length': '234'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), - 'master': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True', 'related_name': "'translations'"}), - 'object_name': ('django.db.models.fields.CharField', [], {'default': "'Article'", 'max_length': '234'}) - }, - 'djangocms_blog.genericblogplugin': { - 'Meta': {'object_name': 'GenericBlogPlugin'}, - 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'blank': 'True', 'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True'}), - 'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'primary_key': 'True', 'unique': 'True'}) - }, - 'djangocms_blog.latestpostsplugin': { - 'Meta': {'object_name': 'LatestPostsPlugin'}, - 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'blank': 'True', 'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True'}), - 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djangocms_blog.BlogCategory']", 'blank': 'True', 'symmetrical': 'False'}), - 'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'primary_key': 'True', 'unique': 'True'}), - 'latest_posts': ('django.db.models.fields.IntegerField', [], {'default': '5'}) - }, - 'djangocms_blog.post': { - 'Meta': {'ordering': "('-date_published', '-date_created')", 'object_name': 'Post'}, - 'app_config': ('aldryn_apphooks_config.fields.AppHookConfigField', [], {'to': "orm['djangocms_blog.BlogConfig']", 'null': 'True'}), - 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['auth.User']", 'null': 'True', 'related_name': "'djangocms_blog_post_author'"}), - 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djangocms_blog.BlogCategory']", 'blank': 'True', 'related_name': "'blog_posts'", 'symmetrical': 'False'}), - 'content': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True', 'related_name': "'post_content'"}), - 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'date_modified': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), - 'date_published': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'null': 'True'}), - 'date_published_end': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'null': 'True'}), - 'enable_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'liveblog': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True', 'related_name': "'live_blog'"}), - 'main_image': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['filer.Image']", 'on_delete': 'models.SET_NULL', 'null': 'True', 'related_name': "'djangocms_blog_post_image'"}), - 'main_image_full': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['cmsplugin_filer_image.ThumbnailOption']", 'on_delete': 'models.SET_NULL', 'null': 'True', 'related_name': "'djangocms_blog_post_full'"}), - 'main_image_thumbnail': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['cmsplugin_filer_image.ThumbnailOption']", 'on_delete': 'models.SET_NULL', 'null': 'True', 'related_name': "'djangocms_blog_post_thumbnail'"}), - 'publish': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'blank': 'True', 'symmetrical': 'False'}) - }, - 'djangocms_blog.posttranslation': { - 'Meta': {'db_table': "'djangocms_blog_post_translation'", 'unique_together': "[('language_code', 'slug'), ('language_code', 'master')]", 'object_name': 'PostTranslation'}, - 'abstract': ('djangocms_text_ckeditor.fields.HTMLField', [], {'blank': 'True', 'default': "''"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'language_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), - 'master': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangocms_blog.Post']", 'null': 'True', 'related_name': "'translations'"}), - 'meta_description': ('django.db.models.fields.TextField', [], {'blank': 'True', 'default': "''"}), - 'meta_keywords': ('django.db.models.fields.TextField', [], {'blank': 'True', 'default': "''"}), - 'meta_title': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", 'max_length': '255'}), - 'post_text': ('djangocms_text_ckeditor.fields.HTMLField', [], {'blank': 'True', 'default': "''"}), - 'slug': ('django.db.models.fields.SlugField', [], {'blank': 'True', 'max_length': '50'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) - }, - 'filer.file': { - 'Meta': {'object_name': 'File'}, - '_file_size': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True', 'null': 'True'}), - 'file': ('django.db.models.fields.files.FileField', [], {'blank': 'True', 'null': 'True', 'max_length': '255'}), - 'folder': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['filer.Folder']", 'null': 'True', 'related_name': "'all_files'"}), - 'has_all_mandatory_data': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", 'max_length': '255'}), - 'original_filename': ('django.db.models.fields.CharField', [], {'blank': 'True', 'null': 'True', 'max_length': '255'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['auth.User']", 'null': 'True', 'related_name': "'owned_files'"}), - 'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'related_name': "'polymorphic_filer.file_set+'"}), - 'sha1': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", 'max_length': '40'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) - }, - 'filer.folder': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('parent', 'name'),)", 'object_name': 'Folder'}, - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'modified_at': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['auth.User']", 'null': 'True', 'related_name': "'filer_owned_folders'"}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'to': "orm['filer.Folder']", 'null': 'True', 'related_name': "'children'"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) - }, - 'filer.image': { - 'Meta': {'object_name': 'Image'}, - '_height': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True'}), - '_width': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True'}), - 'author': ('django.db.models.fields.CharField', [], {'blank': 'True', 'null': 'True', 'max_length': '255'}), - 'date_taken': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'null': 'True'}), - 'default_alt_text': ('django.db.models.fields.CharField', [], {'blank': 'True', 'null': 'True', 'max_length': '255'}), - 'default_caption': ('django.db.models.fields.CharField', [], {'blank': 'True', 'null': 'True', 'max_length': '255'}), - 'file_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['filer.File']", 'primary_key': 'True', 'unique': 'True'}), - 'must_always_publish_author_credit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'must_always_publish_copyright': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'subject_location': ('django.db.models.fields.CharField', [], {'blank': 'True', 'null': 'True', 'default': 'None', 'max_length': '64'}) - }, - 'sites.site': { - 'Meta': {'db_table': "'django_site'", 'ordering': "('domain',)", 'object_name': 'Site'}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['djangocms_blog'] \ No newline at end of file diff --git a/tests/test_utils/routing.py b/tests/test_utils/routing.py index 94188ea..38d1771 100644 --- a/tests/test_utils/routing.py +++ b/tests/test_utils/routing.py @@ -2,9 +2,9 @@ from __future__ import absolute_import, print_function, unicode_literals from channels import include +from knocker.routing import channel_routing as knocker_routing from djangocms_blog.liveblog.routing import channel_routing as djangocms_blog_routing -from knocker.routing import channel_routing as knocker_routing channel_routing = [ include(djangocms_blog_routing, path=r'^/liveblog'), From 52146269f62796dacbe3f35b25829109488e2111 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Thu, 23 Jun 2016 09:57:08 +0200 Subject: [PATCH 12/14] Fix admin --- djangocms_blog/__init__.py | 2 ++ djangocms_blog/admin.py | 18 ++++++++---------- djangocms_blog/liveblog/__init__.py | 1 - 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/djangocms_blog/__init__.py b/djangocms_blog/__init__.py index 61f6854..17ba9e4 100644 --- a/djangocms_blog/__init__.py +++ b/djangocms_blog/__init__.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + __author__ = 'Iacopo Spalletti' __email__ = 'i.spalletti@nephila.it' __version__ = '0.9.pre4' diff --git a/djangocms_blog/admin.py b/djangocms_blog/admin.py index 3a21949..7ea2e7c 100755 --- a/djangocms_blog/admin.py +++ b/djangocms_blog/admin.py @@ -57,24 +57,20 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, enhance_exclude = ('main_image', 'tags') _fieldsets = [ (None, { - 'fields': [('title', 'categories', 'publish', 'app_config')] + 'fields': [['title', 'categories', 'publish', 'app_config']] }), ('Info', { - 'fields': (['slug', 'tags'], - ('date_published', 'date_published_end',), - ( - 'enable_comments', - 'enable_liveblog' if apps.is_installed('djangocms_blog.liveblog') - else None - )), + 'fields': [['slug', 'tags'], + ['date_published', 'date_published_end'], + ['enable_comments']], 'classes': ('collapse',) }), ('Images', { - 'fields': (('main_image', 'main_image_thumbnail', 'main_image_full'),), + 'fields': [['main_image', 'main_image_thumbnail', 'main_image_full']], 'classes': ('collapse',) }), ('SEO', { - 'fields': [('meta_description', 'meta_title', 'meta_keywords')], + 'fields': [['meta_description', 'meta_title', 'meta_keywords']], 'classes': ('collapse',) }), ] @@ -198,6 +194,8 @@ class PostAdmin(PlaceholderAdminMixin, FrontendEditableAdminMixin, fsets[1][1]['fields'][0].append('sites') if request.user.is_superuser: fsets[1][1]['fields'][0].append('author') + if apps.is_installed('djangocms_blog.liveblog'): + fsets[1][1]['fields'][2].append('enable_liveblog') filter_function = get_setting('ADMIN_POST_FIELDSET_FILTER') if callable(filter_function): fsets = filter_function(fsets, request, obj=obj) diff --git a/djangocms_blog/liveblog/__init__.py b/djangocms_blog/liveblog/__init__.py index ba25ec7..40a96af 100644 --- a/djangocms_blog/liveblog/__init__.py +++ b/djangocms_blog/liveblog/__init__.py @@ -1,2 +1 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals From abf2c1bb6102089f2ec67f4c389791143688cce5 Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Sun, 26 Jun 2016 11:54:40 +0200 Subject: [PATCH 13/14] Add tests for liveblog --- djangocms_blog/liveblog/__init__.py | 3 + djangocms_blog/liveblog/models.py | 57 ++++-- ...post_liveblog.py => 0021_post_liveblog.py} | 2 +- ...605_2305.py => 0022_auto_20160605_2305.py} | 2 +- tests/test_liveblog.py | 178 ++++++++++++++++++ tox.ini | 1 + 6 files changed, 222 insertions(+), 21 deletions(-) rename djangocms_blog/migrations/{0018_post_liveblog.py => 0021_post_liveblog.py} (90%) rename djangocms_blog/migrations/{0019_auto_20160605_2305.py => 0022_auto_20160605_2305.py} (90%) create mode 100644 tests/test_liveblog.py diff --git a/djangocms_blog/liveblog/__init__.py b/djangocms_blog/liveblog/__init__.py index 40a96af..f3a5710 100644 --- a/djangocms_blog/liveblog/__init__.py +++ b/djangocms_blog/liveblog/__init__.py @@ -1 +1,4 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +default_app_config = 'djangocms_blog.liveblog.apps.LiveBlogAppConfig' diff --git a/djangocms_blog/liveblog/models.py b/djangocms_blog/liveblog/models.py index f6a4396..0f50605 100644 --- a/djangocms_blog/liveblog/models.py +++ b/djangocms_blog/liveblog/models.py @@ -16,32 +16,28 @@ from djangocms_blog.models import Post, thumbnail_model DATE_FORMAT = "%a %d %b %Y %H:%M" -class Liveblog(AbstractText): - title = models.CharField(_('title'), max_length=255) - image = FilerImageField( - verbose_name=_('image'), blank=True, null=True, on_delete=models.SET_NULL, - related_name='djangocms_blog_liveblog_image' - ) - thumbnail = models.ForeignKey( - thumbnail_model, verbose_name=_('thumbnail size'), on_delete=models.SET_NULL, - blank=True, null=True, related_name='djangocms_blog_liveblog_thumbnail' - ) +class LiveblogInterface(models.Model): + """ + Abstract Liveblog plugin model, reusable to customize the liveblogging plugins. + + When implementing this, you **must** call ``self._post_save()`` in the concrete + plugin model ``save`` method. + """ publish = models.BooleanField(_('publish liveblog entry'), default=False) node_order_by = '-changed_date' class Meta: verbose_name = _('liveblog entry') verbose_name_plural = _('liveblog entries') + abstract = True - def save(self, no_signals=False, *args, **kwargs): - saved = super(Liveblog, self).save(*args, **kwargs) + def _post_save(self): if self.publish: self.send() order = CMSPlugin.objects.filter( placeholder=self.placeholder ).order_by('placeholder', 'path').values_list('pk', flat=True) reorder_plugins(self.placeholder, None, self.language, order) - return saved @property def liveblog_group(self): @@ -56,13 +52,36 @@ class Liveblog(AbstractText): """ Render the content and send to the related group """ - notification = { - 'id': self.pk, - 'content': self.render(), - 'creation_date': self.creation_date.strftime(DATE_FORMAT), - 'changed_date': self.changed_date.strftime(DATE_FORMAT), - } if self.liveblog_group: + notification = { + 'id': self.pk, + 'content': self.render(), + 'creation_date': self.creation_date.strftime(DATE_FORMAT), + 'changed_date': self.changed_date.strftime(DATE_FORMAT), + } Group(self.liveblog_group).send({ 'text': json.dumps(notification), }) + + +class Liveblog(LiveblogInterface, AbstractText): + """ + Basic liveblog plugin model + """ + title = models.CharField(_('title'), max_length=255) + image = FilerImageField( + verbose_name=_('image'), blank=True, null=True, on_delete=models.SET_NULL, + related_name='djangocms_blog_liveblog_image' + ) + thumbnail = models.ForeignKey( + thumbnail_model, verbose_name=_('thumbnail size'), on_delete=models.SET_NULL, + blank=True, null=True, related_name='djangocms_blog_liveblog_thumbnail' + ) + + class Meta: + verbose_name = _('liveblog entry') + verbose_name_plural = _('liveblog entries') + + def save(self, *args, **kwargs): + super(Liveblog, self).save(*args, **kwargs) + self._post_save() diff --git a/djangocms_blog/migrations/0018_post_liveblog.py b/djangocms_blog/migrations/0021_post_liveblog.py similarity index 90% rename from djangocms_blog/migrations/0018_post_liveblog.py rename to djangocms_blog/migrations/0021_post_liveblog.py index db8d1aa..28e9aa4 100644 --- a/djangocms_blog/migrations/0018_post_liveblog.py +++ b/djangocms_blog/migrations/0021_post_liveblog.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): dependencies = [ ('cms', '__first__'), - ('djangocms_blog', '0017_thumbnail_move'), + ('djangocms_blog', '0020_thumbnail_move4'), ] operations = [ diff --git a/djangocms_blog/migrations/0019_auto_20160605_2305.py b/djangocms_blog/migrations/0022_auto_20160605_2305.py similarity index 90% rename from djangocms_blog/migrations/0019_auto_20160605_2305.py rename to djangocms_blog/migrations/0022_auto_20160605_2305.py index f2b0f8d..25e7892 100644 --- a/djangocms_blog/migrations/0019_auto_20160605_2305.py +++ b/djangocms_blog/migrations/0022_auto_20160605_2305.py @@ -9,7 +9,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('djangocms_blog', '0018_post_liveblog'), + ('djangocms_blog', '0021_post_liveblog'), ] operations = [ diff --git a/tests/test_liveblog.py b/tests/test_liveblog.py new file mode 100644 index 0000000..09ab014 --- /dev/null +++ b/tests/test_liveblog.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +import json +from unittest import SkipTest + +try: + from channels import Channel + from channels.tests import ChannelTestCase + from cms.api import add_plugin + + from djangocms_blog.liveblog.consumers import liveblog_connect, liveblog_disconnect + from djangocms_blog.liveblog.models import DATE_FORMAT + from .base import BaseTest + + + class LiveBlogTest(BaseTest, ChannelTestCase): + + @classmethod + def setUpClass(cls): + try: + import knocker + super(LiveBlogTest, cls).setUpClass() + except ImportError: + raise SkipTest('channels not installed, skipping tests') + + def test_add_plugin(self): + posts = self.get_posts() + self.get_pages() + post = posts[0] + post.enable_liveblog = True + post.save() + + Channel('setup').send({'connect': 1, 'reply_channel': 'reply'}) + message = self.get_next_message('setup', require=True) + liveblog_connect(message, self.app_config_1.namespace, 'en', post.slug) + + plugin = add_plugin( + post.liveblog, 'LiveblogPlugin', language='en', body='live text', publish=True + ) + result = self.get_next_message(message.reply_channel.name, require=True) + self.assertTrue(result['text']) + + rendered = json.loads(result['text']) + self.assertEqual(plugin.pk, rendered['id']) + self.assertEqual(plugin.creation_date.strftime(DATE_FORMAT), rendered['creation_date']) + self.assertEqual(plugin.changed_date.strftime(DATE_FORMAT), rendered['changed_date']) + self.assertTrue(rendered['content'].find('data-post-id="{}"'.format(plugin.pk)) > -1) + self.assertTrue(rendered['content'].find('live text') > -1) + + plugin.body = 'modified text' + plugin.save() + + result = self.get_next_message(message.reply_channel.name, require=True) + self.assertTrue(result['text']) + + rendered = json.loads(result['text']) + self.assertEqual(plugin.pk, rendered['id']) + self.assertEqual(plugin.creation_date.strftime(DATE_FORMAT), rendered['creation_date']) + self.assertEqual(plugin.changed_date.strftime(DATE_FORMAT), rendered['changed_date']) + self.assertTrue(rendered['content'].find('data-post-id="{}"'.format(plugin.pk)) > -1) + self.assertTrue(rendered['content'].find('modified text') > -1) + self.assertTrue(rendered['content'].find('live text') == -1) + + def test_add_plugin_no_publish(self): + posts = self.get_posts() + self.get_pages() + post = posts[0] + post.enable_liveblog = True + post.save() + + Channel('setup').send({'connect': 1, 'reply_channel': 'reply'}) + message = self.get_next_message('setup', require=True) + liveblog_connect(message, self.app_config_1.namespace, 'en', post.slug) + + plugin = add_plugin( + post.liveblog, 'LiveblogPlugin', language='en', body='live text', publish=False + ) + result = self.get_next_message(message.reply_channel.name, require=False) + self.assertIsNone(result) + + plugin.publish = True + plugin.save() + + result = self.get_next_message(message.reply_channel.name, require=True) + self.assertTrue(result['text']) + + rendered = json.loads(result['text']) + self.assertEqual(plugin.pk, rendered['id']) + self.assertEqual(plugin.creation_date.strftime(DATE_FORMAT), rendered['creation_date']) + self.assertEqual(plugin.changed_date.strftime(DATE_FORMAT), rendered['changed_date']) + self.assertTrue(rendered['content'].find('data-post-id="{}"'.format(plugin.pk)) > -1) + self.assertTrue(rendered['content'].find('live text') > -1) + + def test_disconnect(self): + posts = self.get_posts() + self.get_pages() + post = posts[0] + post.enable_liveblog = True + post.save() + + Channel('setup').send({'connect': 1, 'reply_channel': 'reply'}) + message = self.get_next_message('setup', require=True) + liveblog_connect(message, self.app_config_1.namespace, 'en', post.slug) + + plugin = add_plugin( + post.liveblog, 'LiveblogPlugin', language='en', body='live text', publish=True + ) + result = self.get_next_message(message.reply_channel.name, require=True) + self.assertTrue(result['text']) + + liveblog_disconnect(message, self.app_config_1.namespace, 'en', post.slug) + + plugin.body = 'modified text' + plugin.save() + + result = self.get_next_message(message.reply_channel.name, require=False) + self.assertIsNone(result) + + def test_nopost(self): + + self.get_pages() + + Channel('setup').send({'connect': 1, 'reply_channel': 'reply'}) + message = self.get_next_message('setup', require=True) + liveblog_connect(message, self.app_config_1.namespace, 'en', 'random-post') + + result = self.get_next_message(message.reply_channel.name, require=True) + self.assertTrue(result['text']) + rendered = json.loads(result['text']) + self.assertTrue(rendered['error'], 'no_post') + + liveblog_disconnect(message, self.app_config_1.namespace, 'en', 'random-post') + result = self.get_next_message(message.reply_channel.name, require=True) + self.assertTrue(result['text']) + rendered = json.loads(result['text']) + self.assertTrue(rendered['error'], 'no_post') + + def test_plugin_without_post(self): + + pages = self.get_pages() + + placeholder = pages[0].get_placeholders().get(slot='content') + + Channel('setup').send({'connect': 1, 'reply_channel': 'reply'}) + message = self.get_next_message('setup', require=True) + liveblog_connect(message, self.app_config_1.namespace, 'en', 'random post') + self.get_next_message(message.reply_channel.name, require=True) + + plugin = add_plugin( + placeholder, 'LiveblogPlugin', language='en', body='live text', publish=True + ) + self.assertIsNone(plugin.liveblog_group) + result = self.get_next_message(message.reply_channel.name, require=False) + self.assertIsNone(result) + + def test_plugin_render(self): + posts = self.get_posts() + pages = self.get_pages() + post = posts[0] + post.enable_liveblog = True + post.save() + plugin = add_plugin( + post.liveblog, 'LiveblogPlugin', language='en', body='live text', publish=False + ) + context = self.get_plugin_context(pages[0], 'en', plugin, edit=False) + rendered = plugin.render_plugin(context, post.liveblog) + self.assertFalse(rendered) + + plugin.publish = True + plugin.save() + context = self.get_plugin_context(pages[0], 'en', plugin, edit=False) + rendered = plugin.render_plugin(context, post.liveblog) + self.assertTrue(rendered.find('data-post-id="{}"'.format(plugin.pk)) > -1) + self.assertTrue(rendered.find('live text') > -1) + +except ImportError: # pragma: no cover + pass diff --git a/tox.ini b/tox.ini index 454f361..e1f679f 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ deps = cms33: https://github.com/divio/django-cms/archive/release/3.3.x.zip cms33: djangocms-text-ckeditor>=3.0 knocker: https://github.com/divio/django-cms/archive/release/3.2.x.zip + knocker: channels>=0.15 knocker: https://github.com/nephila/django-knocker/archive/master.zip?0.1.1 knocker: djangocms-text-ckeditor<3.0 django-meta>=1.2 From 41834f6bf931beb903cac8618a88cd5993910a9f Mon Sep 17 00:00:00 2001 From: Iacopo Spalletti Date: Sun, 26 Jun 2016 12:48:40 +0200 Subject: [PATCH 14/14] Better plugin templates --- djangocms_blog/liveblog/cms_plugins.py | 7 +------ djangocms_blog/liveblog/models.py | 9 ++++++++- .../liveblog/templates/liveblog/plugins/liveblog.html | 2 ++ tests/test_liveblog.py | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/djangocms_blog/liveblog/cms_plugins.py b/djangocms_blog/liveblog/cms_plugins.py index 6c1895d..75a90ba 100644 --- a/djangocms_blog/liveblog/cms_plugins.py +++ b/djangocms_blog/liveblog/cms_plugins.py @@ -15,12 +15,7 @@ class LiveblogPlugin(TextPlugin): name = _('Liveblog item') model = Liveblog fields = ('title', 'body', 'publish') - - def _get_render_template(self, context, instance, placeholder): - if instance.publish: - return 'liveblog/plugins/liveblog.html' - else: - return 'liveblog/plugins/unpublished.html' + render_template = 'liveblog/plugins/liveblog.html' def render(self, context, instance, placeholder): context = super(LiveblogPlugin, self).render(context, instance, placeholder) diff --git a/djangocms_blog/liveblog/models.py b/djangocms_blog/liveblog/models.py index 0f50605..87fa8d1 100644 --- a/djangocms_blog/liveblog/models.py +++ b/djangocms_blog/liveblog/models.py @@ -4,7 +4,7 @@ from __future__ import absolute_import, print_function, unicode_literals import json from channels import Group -from cms.models import CMSPlugin +from cms.models import CMSPlugin, python_2_unicode_compatible from cms.utils.plugins import reorder_plugins from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -16,6 +16,7 @@ from djangocms_blog.models import Post, thumbnail_model DATE_FORMAT = "%a %d %b %Y %H:%M" +@python_2_unicode_compatible class LiveblogInterface(models.Model): """ Abstract Liveblog plugin model, reusable to customize the liveblogging plugins. @@ -31,6 +32,9 @@ class LiveblogInterface(models.Model): verbose_name_plural = _('liveblog entries') abstract = True + def __str__(self): + return str(self.pk) + def _post_save(self): if self.publish: self.send() @@ -85,3 +89,6 @@ class Liveblog(LiveblogInterface, AbstractText): def save(self, *args, **kwargs): super(Liveblog, self).save(*args, **kwargs) self._post_save() + + def __str__(self): + return AbstractText.__str__(self) diff --git a/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html b/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html index 402a2a5..2fdd5a6 100644 --- a/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html +++ b/djangocms_blog/liveblog/templates/liveblog/plugins/liveblog.html @@ -1,5 +1,7 @@ +{% spaceless %}{% if instance.publish %}

{{ instance.title }}

{{ instance.creation_date|date:"D d M Y H:i" }}

{{ instance.content|safe }}
+{% endif %}{% endspaceless %} diff --git a/tests/test_liveblog.py b/tests/test_liveblog.py index 09ab014..e9b06ed 100644 --- a/tests/test_liveblog.py +++ b/tests/test_liveblog.py @@ -165,7 +165,7 @@ try: ) context = self.get_plugin_context(pages[0], 'en', plugin, edit=False) rendered = plugin.render_plugin(context, post.liveblog) - self.assertFalse(rendered) + self.assertFalse(rendered.strip()) plugin.publish = True plugin.save()