Add experimental liveblogging support

This commit is contained in:
Iacopo Spalletti 2016-04-10 19:47:36 +02:00
parent f2beec480b
commit a6f296226a
No known key found for this signature in database
GPG key ID: BDCBC2EB289F60C6
20 changed files with 536 additions and 0 deletions

View file

@ -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

View file

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals

View file

@ -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')

View file

@ -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)

View file

@ -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)

View file

@ -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',),
),
]

View file

@ -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,
),
]

View file

@ -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),
})

View file

@ -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<apphook>[a-zA-Z0-9_-]+)/(?P<lang>[a-zA-Z_-]+)/(?P<post>[a-zA-Z0-9_-]+)/$'
),
route(
'websocket.disconnect', liveblog_disconnect,
path=r'^/liveblog/(?P<apphook>[a-zA-Z0-9_-]+)/(?P<lang>[a-zA-Z_-]+)/(?P<post>[a-zA-Z0-9_-]+)/$'
),
]

View file

@ -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");
}
});

View file

@ -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});

View file

@ -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" %}
<script>
var liveblog_apphook = '{{ post.app_config.namespace }}';
var liveblog_language = '{{ post.get_current_language }}';
var liveblog_post = '{{ post.slug }}';
</script>
<div class="blog-content--live" id="liveblog-posts">
{% render_placeholder post.liveblog %}
</div>

View file

@ -0,0 +1,4 @@
<div class="post" data-post-id="{{ instance.id }}">
<h2>{{ instance.title }}{{ instance.creation_date|date:"D d M Y H:i" }}</h2>
{{ instance.content|safe }}
</div>

View file

@ -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),
),
]

View file

@ -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(

View file

@ -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]

View file

@ -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']

View file

@ -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'