Skip to content

Commit

Permalink
Implement automatic loading of new micro-updates using AJAX calls
Browse files Browse the repository at this point in the history
  • Loading branch information
hvelarde committed Aug 27, 2014
1 parent 3b713ac commit 0777933
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 65 deletions.
3 changes: 3 additions & 0 deletions src/collective/liveblog/browser/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from .view import View # noqa
from .add_microupdate import AddMicroUpdateView # noqa
39 changes: 39 additions & 0 deletions src/collective/liveblog/browser/add_microupdate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from collective.liveblog import _
from collective.liveblog.adapters import IMicroUpdateContainer
from collective.liveblog.adapters import MicroUpdate
from collective.liveblog.interfaces import IBrowserLayer
from collective.liveblog.interfaces import ILiveblog
from five import grok
from plone import api
from zope.event import notify
from zope.lifecycleevent import ObjectModifiedEvent

grok.templatedir('templates')


class AddMicroUpdateView(grok.View):

"""Add a micro-update to the Liveblog."""

grok.context(ILiveblog)
grok.layer(IBrowserLayer)
grok.name('add-microupdate')
grok.require('collective.liveblog.AddMicroUpdate')

def render(self):
title = self.request.form.get('title', None)
text = self.request.form.get('text', None)
if not text:
msg = _(u'There were some errors. Required input is missing.')
api.portal.show_message(msg, self.request, type='error')
else:
adapter = IMicroUpdateContainer(self.context)
adapter.add(MicroUpdate(title, text))

# notify the Liveblog has a new micro-update
notify(ObjectModifiedEvent(self.context))
msg = _(u'Item published.')
api.portal.show_message(msg, self.request)

self.request.response.redirect(self.context.absolute_url())
7 changes: 7 additions & 0 deletions src/collective/liveblog/browser/configure.zcml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:i18n="http://namespaces.zope.org/i18n"
i18n_domain="collective.liveblog">

</configure>
22 changes: 22 additions & 0 deletions src/collective/liveblog/browser/templates/updates.pt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
tal:omit-tag=""
i18n:domain="collective.liveblog">
<body tal:omit-tag="">
<div id="micro-updates" tal:omit-tag="">
<div class="microupdate" data-timestamp=""
tal:repeat="update view/updates_since_timestamp"
tal:attributes="data-timestamp update/timestamp">
<p class="microupdate-title">
<strong tal:content="update/title" />
</p>
<div class="microupdate-text" tal:content="structure update/text" />
<p class="microupdate-byline" i18n:translate="">
By <span tal:content="update/creator" />
<span tal:content="update/time_only" tal:attributes="title update/created" />
</p>
</div>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="collective.liveblog">
<body>
<!-- TODO: https://schema.org/BlogPosting -->
<metal:block fill-slot="content-core">
<div tal:condition="view/can_add_microupdate">
<h2 i18n:translate="">Add micro-update</h2>
Expand All @@ -28,21 +29,37 @@
</form>
</div>

<div tal:condition="view/has_updates" tal:repeat="update view/updates">
<div>
<strong tal:content="update/title" />
<div id="micro-updates">
<div class="microupdate" data-timestamp=""
tal:repeat="update view/updates"
tal:attributes="data-timestamp update/timestamp">
<p class="microupdate-title">
<strong tal:content="update/title" />
</p>
<div class="microupdate-text" tal:content="structure update/text" />
<p class="microupdate-byline" i18n:translate="">
By <span tal:content="update/creator" />
<span tal:content="update/time_only" tal:attributes="title update/created" />
</p>
</div>
<div>
<p tal:content="structure update/text" />
</div>
<div i18n:translate="">
By <span tal:content="update/creator" />
<span tal:content="update/time_only" tal:attributes="title update/created" />
<div class="microupdate" data-timestamp=""
tal:condition="not:view/has_updates"
tal:attributes="data-timestamp view/now">
<p class="microupdate-title">
<strong i18n:translate="">No micro-updates yet for this Liveblog.</strong>
</p>
<div class="microupdate-text" />
<p class="microupdate-byline" />
</div>
</div>
<p tal:condition="not:view/has_updates" i18n:translate="">
No micro-updates yet for this Liveblog.
</p>
<script type="text/javascript">
setInterval(function() {
var timestamp = $(".microupdate").attr("data-timestamp");
$.get("updates?timestamp=" + timestamp, function(data) {
$("#micro-updates").prepend(data).fadeIn("slow");
});
}, 1000 * 60);
</script>
</metal:block>
</body>
</html>
38 changes: 38 additions & 0 deletions src/collective/liveblog/browser/updates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from collective.liveblog.interfaces import IBrowserLayer
from collective.liveblog.interfaces import ILiveblog
from five import grok
from plone.memoize import ram
from time import time

grok.templatedir('templates')


class Updates(grok.View):

"""Helper view for Liveblog."""

grok.context(ILiveblog)
grok.layer(IBrowserLayer)
grok.require('zope2.View')

def _updates_since_timestamp(self):
"""Return the list of micro-updates since the specified timestamp."""
timestamp = self.request.get('timestamp', None)

# timestamp should a string representing a float
try:
float(timestamp)
except (TypeError, ValueError):
return []

updates = self.context.restrictedTraverse('view').updates()
updates = [u for u in updates if u['timestamp'] > timestamp]
return updates

@ram.cache(lambda *args: time() // 20)
def updates_since_timestamp(self):
"""Return the list of micro-updates since the specified timestamp;
the list is cached for 20 seconds.
"""
return self._updates_since_timestamp()
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
# -*- coding: utf-8 -*-
from collective.liveblog import _
from collective.liveblog.adapters import IMicroUpdateContainer
from collective.liveblog.adapters import MicroUpdate
from collective.liveblog.interfaces import IBrowserLayer
from collective.liveblog.interfaces import ILiveblog
from collective.liveblog.utils import _timestamp
from datetime import datetime
from five import grok
from plone import api
from plone.memoize import ram
from zope.event import notify
from zope.lifecycleevent import ObjectModifiedEvent

grok.templatedir('templates')

Expand Down Expand Up @@ -42,6 +40,7 @@ def _updates(self):
updates.append(dict(
id=id + 1,
creator=update.creator,
timestamp=_timestamp(update.created),
created=api.portal.get_localized_time(update.created, True),
time_only=api.portal.get_localized_time(update.created, time_only=True),
title=update.title,
Expand All @@ -52,35 +51,17 @@ def _updates(self):

@ram.cache(_render_updates_cachekey)
def updates(self):
"""Return the list of micro-updates in the Liveblog in reverse order;
the list is cached until a new update is published.
"""
return self._updates()

@property
def has_updates(self):
"""Return True if Liveblog has updates."""
return len(self.updates()) > 0


class AddMicroUpdateView(grok.View):

"""Add a micro-update to the Liveblog."""

grok.context(ILiveblog)
grok.layer(IBrowserLayer)
grok.name('add-microupdate')
grok.require('collective.liveblog.AddMicroUpdate')

def render(self):
title = self.request.form.get('title', None)
text = self.request.form.get('text', None)
if not text:
msg = _(u'There were some errors. Required input is missing.')
api.portal.show_message(msg, self.request, type='error')
else:
adapter = IMicroUpdateContainer(self.context)
adapter.add(MicroUpdate(title, text))

# notify the Liveblog has a new micro-update
notify(ObjectModifiedEvent(self.context))
msg = _(u'Item published.')
api.portal.show_message(msg, self.request)

self.request.response.redirect(self.context.absolute_url())
@property
def now(self):
"""Return a timestamp for the current date and time."""
return _timestamp(datetime.now())
1 change: 1 addition & 0 deletions src/collective/liveblog/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<include file="profiles.zcml" />

<browser:resourceDirectory name="collective.liveblog" directory="static" />
<include package=".browser" />

<adapter
for=".interfaces.ILiveblog"
Expand Down
39 changes: 39 additions & 0 deletions src/collective/liveblog/tests/test_add_microupdate_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from collective.liveblog.interfaces import IBrowserLayer
from collective.liveblog.testing import FUNCTIONAL_TESTING
from plone import api
from Products.statusmessages.interfaces import IStatusMessage
from zope.interface import alsoProvides

import unittest


class AddMicroUpdateViewTestCase(unittest.TestCase):

layer = FUNCTIONAL_TESTING

def setUp(self):
self.portal = self.layer['portal']
self.request = self.layer['request']
alsoProvides(self.request, IBrowserLayer)
with api.env.adopt_roles(['Manager']):
self.liveblog = api.content.create(
self.portal, 'Liveblog', 'liveblog')

def test_add_microupdate_no_parameters(self):
# invoke the view and check the status message
self.liveblog.unrestrictedTraverse('add-microupdate')()
msg = IStatusMessage(self.request).show()
self.assertEqual(len(msg), 1)
expected = u'There were some errors. Required input is missing.'
self.assertEqual(msg[0].message, expected)

def test_add_microupdate(self):
self.request.form['title'] = ''
self.request.form['text'] = 'Extra! Extra! Read All About It!'
# invoke the view and check the status message
self.liveblog.unrestrictedTraverse('add-microupdate')()
msg = IStatusMessage(self.request).show()
self.assertEqual(len(msg), 1)
expected = u'Item published.'
self.assertEqual(msg[0].message, expected)

0 comments on commit 0777933

Please sign in to comment.