diff --git a/combo/apps/momo/management/__init__.py b/combo/apps/momo/management/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/combo/apps/momo/management/commands/__init__.py b/combo/apps/momo/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/combo/apps/momo/management/commands/update_momo_manifest.py b/combo/apps/momo/management/commands/update_momo_manifest.py
new file mode 100644
index 00000000..8a2526f2
--- /dev/null
+++ b/combo/apps/momo/management/commands/update_momo_manifest.py
@@ -0,0 +1,50 @@
+# combo - content management system
+# Copyright (C) 2016 Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+from urlparse import urlparse
+
+from django.conf import settings
+from django.core.management.base import BaseCommand, CommandError
+from django.db import connection
+from django.test.client import RequestFactory
+from django.utils import translation
+
+from combo.apps.momo.utils import generate_manifest, GenerationError, GenerationInfo
+
+class Command(BaseCommand):
+ def handle(self, *args, **kwargs):
+ if not getattr(settings, 'ENABLE_MOMO', False):
+ return
+ tenant = connection.get_tenant()
+ parsed_base_url = urlparse(tenant.get_base_url())
+ if ':' in parsed_base_url.netloc:
+ server_name, server_port = parsed_base_url.netloc.split(':')
+ else:
+ server_name = parsed_base_url.netloc
+ server_port = '80' if parsed_base_url.scheme == 'http' else '443'
+ request = RequestFactory().get('/', SERVER_NAME=server_name,
+ SERVER_PORT=server_port)
+ request._get_scheme = lambda: parsed_base_url.scheme
+
+ translation.activate(settings.LANGUAGE_CODE)
+ try:
+ generate_manifest(request)
+ except GenerationError as e:
+ raise CommandError(e.message)
+ except GenerationInfo as e:
+ if kwargs.get('verbosity') > 0:
+ print e.message
+ translation.deactivate()
diff --git a/combo/apps/momo/utils.py b/combo/apps/momo/utils.py
new file mode 100644
index 00000000..5814c3f7
--- /dev/null
+++ b/combo/apps/momo/utils.py
@@ -0,0 +1,268 @@
+# combo - content management system
+# Copyright (C) 2016 Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+import datetime
+import json
+import os
+import shutil
+import zipfile
+
+from django.core.files.storage import default_storage
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+
+import ckeditor
+import ckeditor.views
+
+from combo.data.models import CellBase, LinkCell, FeedCell, Page
+from .models import MomoIconCell, MomoOptions
+
+
+class GenerationError(Exception):
+ pass
+
+
+class GenerationInfo(Exception):
+ pass
+
+
+def render_cell(cell, context):
+ classnames = ['cell', cell.css_class_name]
+ if cell.slug:
+ classnames.append(cell.slug)
+ return '
%s
' % (' '.join(classnames), cell.render(context))
+
+
+def get_page_dict(request, page, manifest):
+ cells = [x for x in CellBase.get_cells(page_id=page.id) if x.placeholder != 'footer']
+
+ page_dict = {
+ 'title': page.title,
+ 'id': 'page-%s-%s' % (page.slug, page.id),
+ }
+
+ link_cells = [x for x in cells if isinstance(x, LinkCell)]
+ icon_cells = [x for x in cells if isinstance(x, MomoIconCell)]
+ feed_cells = [x for x in cells if isinstance(x, FeedCell)]
+ cells = [x for x in cells if not (isinstance(x, LinkCell) or isinstance(x, FeedCell))]
+
+ if cells:
+ context = RequestContext(request, {
+ 'synchronous': True,
+ 'page': page,
+ 'page_cells': cells,
+ 'request': request,
+ 'site_base': request.build_absolute_uri('/')[:-1],
+ })
+ page_dict['content'] = '\n'.join([render_cell(cell, context) for cell in cells])
+
+ if link_cells:
+ page_dict['seealso'] = []
+ for cell in link_cells:
+ if cell.link_page:
+ # internal link
+ page_dict['seealso'].append('page-%s-%s' %
+ (cell.link_page.slug, cell.link_page.id))
+ else:
+ # external link
+ page_dict['seealso'].append('seealso-%s' % cell.id)
+ manifest['_pages'].append({
+ 'title': cell.title,
+ 'external': True,
+ 'url': cell.url,
+ 'id': 'seealso-%s' % cell.id})
+
+ if page.redirect_url:
+ page_dict['external'] = True
+ page_dict['url'] = page.redirect_url
+
+ if icon_cells:
+ page_dict['icon'] = icon_cells[0].icon
+ page_dict['style'] = icon_cells[0].style
+ page_dict['description'] = icon_cells[0].description
+ if page_dict.get('external') and icon_cells[0].embed_page:
+ page_dict['external'] = False
+
+ if hasattr(page, '_children') and page._children:
+ children = page._children
+ else:
+ children = page.get_children()
+
+ if children:
+ page_dict['pages'] = []
+ for child in children:
+ page_dict['pages'].append(get_page_dict(request, child, manifest))
+
+ if feed_cells:
+ if not 'pages' in page_dict:
+ page_dict['pages'] = []
+ # turn feed entries in external pages
+ for feed_cell in feed_cells:
+ feed_context = feed_cell.get_cell_extra_context({})
+ if feed_context.get('feed'):
+ for entry in feed_context.get('feed').entries:
+ feed_entry_page = {
+ 'title': entry.title,
+ 'id': 'feed-entry-%s-%s' % (feed_cell.id, entry.id),
+ 'url': entry.link,
+ 'external': True,
+ }
+ if entry.description:
+ feed_entry_page['description'] = entry.description
+ page_dict['pages'].append(feed_entry_page)
+
+ return page_dict
+
+
+
+
+def generate_manifest(request):
+ if not default_storage.exists('assets-base.zip'):
+ raise GenerationError(_('Missing base assets file'))
+
+ manifest = {
+ 'menu': [],
+ '_pages': []
+ }
+ level0_pages = Page.objects.filter(parent=None)
+
+ # the application hierarchy is structured that way:
+ # - the home screen is the homepage
+ # - the application pages are created from the homepage siblings and their
+ # children
+ # - the application menu is created from direct children of the homepage
+ children = []
+ homepage = None
+ for page in level0_pages:
+ if page.slug == 'index':
+ homepage = page
+ else:
+ children.append(page)
+
+ if not homepage:
+ raise GenerationError(_('The homepage needs to be created first.'))
+
+ homepage._children = children
+ manifest.update(get_page_dict(request, homepage, manifest))
+
+ # footer
+ footer_cells = CellBase.get_cells(page_id=homepage.id, placeholder='footer')
+ if footer_cells:
+ context = RequestContext(request, {
+ 'synchronous': True,
+ 'page': homepage,
+ 'page_cells': footer_cells,
+ 'request': request,
+ 'site_base': request.build_absolute_uri('/')[:-1],
+ })
+ manifest['footer'] = '\n'.join([
+ '' % (cell.slug, cell.render(context)) for cell in footer_cells])
+
+ # construct the application menu
+ manifest['menu'].append('home') # link to home screen
+
+ # add real homepage children
+ menu_children = homepage.get_children()
+ for menu_child in menu_children:
+ link_cells = LinkCell.objects.filter(page_id=menu_child.id)
+ if link_cells:
+ # use link info instead of redirect url
+ link_cell = link_cells[0]
+ if link_cell.link_page: # internal link
+ menu_id = 'page-%s-%s' % (link_cell.link_page.slug, link_cell.link_page.id)
+ else:
+ menu_id = 'menu-%s-%s' % (menu_child.slug, menu_child.id)
+ link_context = link_cell.get_cell_extra_context({})
+ manifest['_pages'].append({
+ 'title': link_context['title'],
+ 'external': True,
+ 'url': link_context['url'],
+ 'id': menu_id,
+ })
+ else:
+ menu_id = 'menu-%s-%s' % (menu_child.slug, menu_child.id)
+ manifest['_pages'].append({
+ 'title': menu_child.title,
+ 'external': True,
+ 'url': menu_child.redirect_url,
+ 'id': menu_id,
+ })
+ manifest['menu'].append(menu_id)
+
+ # last item, application refresh
+ manifest['menu'].append({
+ 'icon': 'fa-refresh',
+ 'id': 'momo-update',
+ 'title': _('Update Application')})
+
+ options = MomoOptions.get_object()
+ manifest['meta'] = {
+ 'title': options.title or homepage.title,
+ 'icon': 'icon.png',
+ 'contact': options.contact_email or 'info@entrouvert.com',
+ 'updateFreq': options.update_freq or 86400,
+ 'manifestUrl': request.build_absolute_uri(default_storage.url('index.json')),
+ 'assetsUrl': request.build_absolute_uri(default_storage.url('assets.zip')),
+ 'stylesheets': ["assets/index.css"],
+ }
+
+ if options.extra_css:
+ manifest['meta']['stylesheets'].append('assets/%s' % options.extra_css)
+
+ if options.icons_on_homepage:
+ manifest['display'] = 'icons'
+
+ current_manifest = None
+ if default_storage.exists('index.json'):
+ with default_storage.open('index.json', mode='r') as fp:
+ current_manifest = fp.read()
+
+ new_manifest = json.dumps(manifest, indent=2)
+ if new_manifest != current_manifest:
+ with default_storage.open('index.json', mode='w') as fp:
+ fp.write(new_manifest)
+ else:
+ raise GenerationInfo(_('No changes were detected.'))
+
+ # assets.zip
+ if default_storage.exists('assets.zip'):
+ zf = zipfile.ZipFile(default_storage.open('assets.zip'))
+ existing_files = set([x for x in zf.namelist() if x[0] != '/' and x[-1] != '/'])
+ zf.close()
+ assets_mtime = default_storage.modified_time('assets.zip')
+ else:
+ existing_files = set([])
+ assets_mtime = datetime.datetime(2015, 1, 1)
+
+ ckeditor_filenames = set(ckeditor.views.get_image_files())
+ media_ckeditor_filenames = set(['media/' + x for x in ckeditor_filenames])
+
+ if not media_ckeditor_filenames.issubset(existing_files) or default_storage.modified_time('assets-base.zip') > assets_mtime:
+ # if there are new files, or if the base assets file changed, we
+ # generate a new assets.zip
+ shutil.copy(default_storage.path('assets-base.zip'),
+ default_storage.path('assets.zip.tmp'))
+ zf = zipfile.ZipFile(default_storage.path('assets.zip.tmp'), 'a')
+ for filename in ckeditor_filenames:
+ zf.write(default_storage.path(filename), 'media/' + filename)
+ zf.close()
+ if os.path.exists(default_storage.path('assets.zip')):
+ os.unlink(default_storage.path('assets.zip'))
+ os.rename(default_storage.path('assets.zip.tmp'), default_storage.path('assets.zip'))
+
+ raise GenerationInfo(_('A new update (including new assets) has been generated.'))
+ else:
+ raise GenerationInfo(_('A new update has been generated.'))
diff --git a/combo/apps/momo/views.py b/combo/apps/momo/views.py
index 7fc4aa3b..b1f57ff6 100644
--- a/combo/apps/momo/views.py
+++ b/combo/apps/momo/views.py
@@ -14,259 +14,27 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-import datetime
-import json
-import shutil
-import os
-import zipfile
-
from django.conf import settings
from django.contrib import messages
-from django.core.files.storage import default_storage
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
-from django.template import RequestContext
from django.utils.translation import ugettext as _
from django.views.generic import TemplateView, UpdateView
-import ckeditor
-
-from combo.data.models import CellBase, LinkCell, FeedCell, Page
-from .models import MomoIconCell, MomoOptions
+from .models import MomoOptions
+from .utils import generate_manifest, GenerationError, GenerationInfo
class MomoManagerView(TemplateView):
template_name = 'momo/manager_home.html'
-
-def render_cell(cell, context):
- classnames = ['cell', cell.css_class_name]
- if cell.slug:
- classnames.append(cell.slug)
- return '%s
' % (' '.join(classnames), cell.render(context))
-
-def get_page_dict(request, page, manifest):
- cells = [x for x in CellBase.get_cells(page_id=page.id) if x.placeholder != 'footer']
-
- page_dict = {
- 'title': page.title,
- 'id': 'page-%s-%s' % (page.slug, page.id),
- }
-
- link_cells = [x for x in cells if isinstance(x, LinkCell)]
- icon_cells = [x for x in cells if isinstance(x, MomoIconCell)]
- feed_cells = [x for x in cells if isinstance(x, FeedCell)]
- cells = [x for x in cells if not (isinstance(x, LinkCell) or isinstance(x, FeedCell))]
-
- if cells:
- context = RequestContext(request, {
- 'synchronous': True,
- 'page': page,
- 'page_cells': cells,
- 'request': request,
- 'site_base': request.build_absolute_uri('/')[:-1],
- })
- page_dict['content'] = '\n'.join([render_cell(cell, context) for cell in cells])
-
- if link_cells:
- page_dict['seealso'] = []
- for cell in link_cells:
- if cell.link_page:
- # internal link
- page_dict['seealso'].append('page-%s-%s' %
- (cell.link_page.slug, cell.link_page.id))
- else:
- # external link
- page_dict['seealso'].append('seealso-%s' % cell.id)
- manifest['_pages'].append({
- 'title': cell.title,
- 'external': True,
- 'url': cell.url,
- 'id': 'seealso-%s' % cell.id})
-
- if page.redirect_url:
- page_dict['external'] = True
- page_dict['url'] = page.redirect_url
-
- if icon_cells:
- page_dict['icon'] = icon_cells[0].icon
- page_dict['style'] = icon_cells[0].style
- page_dict['description'] = icon_cells[0].description
- if page_dict.get('external') and icon_cells[0].embed_page:
- page_dict['external'] = False
-
- if hasattr(page, '_children') and page._children:
- children = page._children
- else:
- children = page.get_children()
-
- if children:
- page_dict['pages'] = []
- for child in children:
- page_dict['pages'].append(get_page_dict(request, child, manifest))
-
- if feed_cells:
- if not 'pages' in page_dict:
- page_dict['pages'] = []
- # turn feed entries in external pages
- for feed_cell in feed_cells:
- feed_context = feed_cell.get_cell_extra_context({})
- if feed_context.get('feed'):
- for entry in feed_context.get('feed').entries:
- feed_entry_page = {
- 'title': entry.title,
- 'id': 'feed-entry-%s-%s' % (feed_cell.id, entry.id),
- 'url': entry.link,
- 'external': True,
- }
- if entry.description:
- feed_entry_page['description'] = entry.description
- page_dict['pages'].append(feed_entry_page)
-
- return page_dict
-
-
def generate(request, **kwargs):
- if not default_storage.exists('assets-base.zip'):
- messages.error(request, _('Missing base assets file'))
- return HttpResponseRedirect(reverse('momo-manager-homepage'))
-
- manifest = {
- 'menu': [],
- '_pages': []
- }
- level0_pages = Page.objects.filter(parent=None)
-
- # the application hierarchy is structured that way:
- # - the home screen is the homepage
- # - the application pages are created from the homepage siblings and their
- # children
- # - the application menu is created from direct children of the homepage
- children = []
- homepage = None
- for page in level0_pages:
- if page.slug == 'index':
- homepage = page
- else:
- children.append(page)
-
- if not homepage:
- messages.error(request, _('The homepage needs to be created first.'))
- return HttpResponseRedirect(reverse('momo-manager-homepage'))
-
- homepage._children = children
- manifest.update(get_page_dict(request, homepage, manifest))
-
- # footer
- footer_cells = CellBase.get_cells(page_id=homepage.id, placeholder='footer')
- if footer_cells:
- context = RequestContext(request, {
- 'synchronous': True,
- 'page': homepage,
- 'page_cells': footer_cells,
- 'request': request,
- 'site_base': request.build_absolute_uri('/')[:-1],
- })
- manifest['footer'] = '\n'.join([
- '' % (cell.slug, cell.render(context)) for cell in footer_cells])
-
- # construct the application menu
- manifest['menu'].append('home') # link to home screen
-
- # add real homepage children
- menu_children = homepage.get_children()
- for menu_child in menu_children:
- link_cells = LinkCell.objects.filter(page_id=menu_child.id)
- if link_cells:
- # use link info instead of redirect url
- link_cell = link_cells[0]
- if link_cell.link_page: # internal link
- menu_id = 'page-%s-%s' % (link_cell.link_page.slug, link_cell.link_page.id)
- else:
- menu_id = 'menu-%s-%s' % (menu_child.slug, menu_child.id)
- link_context = link_cell.get_cell_extra_context({})
- manifest['_pages'].append({
- 'title': link_context['title'],
- 'external': True,
- 'url': link_context['url'],
- 'id': menu_id,
- })
- else:
- menu_id = 'menu-%s-%s' % (menu_child.slug, menu_child.id)
- manifest['_pages'].append({
- 'title': menu_child.title,
- 'external': True,
- 'url': menu_child.redirect_url,
- 'id': menu_id,
- })
- manifest['menu'].append(menu_id)
-
- # last item, application refresh
- manifest['menu'].append({
- 'icon': 'fa-refresh',
- 'id': 'momo-update',
- 'title': _('Update Application')})
-
- options = MomoOptions.get_object()
- manifest['meta'] = {
- 'title': options.title or homepage.title,
- 'icon': 'icon.png',
- 'contact': options.contact_email or 'info@entrouvert.com',
- 'updateFreq': options.update_freq or 86400,
- 'manifestUrl': request.build_absolute_uri(default_storage.url('index.json')),
- 'assetsUrl': request.build_absolute_uri(default_storage.url('assets.zip')),
- 'stylesheets': ["assets/index.css"],
- }
-
- if options.extra_css:
- manifest['meta']['stylesheets'].append('assets/%s' % options.extra_css)
-
- if options.icons_on_homepage:
- manifest['display'] = 'icons'
-
- current_manifest = None
- if default_storage.exists('index.json'):
- with default_storage.open('index.json', mode='r') as fp:
- current_manifest = fp.read()
-
- new_manifest = json.dumps(manifest, indent=2)
- if new_manifest != current_manifest:
- with default_storage.open('index.json', mode='w') as fp:
- fp.write(new_manifest)
- else:
- messages.info(request, _('No changes were detected.'))
- return HttpResponseRedirect(reverse('momo-manager-homepage'))
-
- # assets.zip
- if default_storage.exists('assets.zip'):
- zf = zipfile.ZipFile(default_storage.open('assets.zip'))
- existing_files = set([x for x in zf.namelist() if x[0] != '/' and x[-1] != '/'])
- zf.close()
- assets_mtime = default_storage.modified_time('assets.zip')
- else:
- existing_files = set([])
- assets_mtime = datetime.datetime(2015, 1, 1)
-
- ckeditor_filenames = set(ckeditor.views.get_image_files())
- media_ckeditor_filenames = set(['media/' + x for x in ckeditor_filenames])
-
- if not media_ckeditor_filenames.issubset(existing_files) or default_storage.modified_time('assets-base.zip') > assets_mtime:
- # if there are new files, or if the base assets file changed, we
- # generate a new assets.zip
- shutil.copy(default_storage.path('assets-base.zip'),
- default_storage.path('assets.zip.tmp'))
- zf = zipfile.ZipFile(default_storage.path('assets.zip.tmp'), 'a')
- for filename in ckeditor_filenames:
- zf.write(default_storage.path(filename), 'media/' + filename)
- zf.close()
- if os.path.exists(default_storage.path('assets.zip')):
- os.unlink(default_storage.path('assets.zip'))
- os.rename(default_storage.path('assets.zip.tmp'), default_storage.path('assets.zip'))
-
- messages.info(request, _('A new update (including new assets) has been generated.'))
- else:
- messages.info(request, _('A new update has been generated.'))
-
+ try:
+ generate_manifest(request)
+ except GenerationError as e:
+ message.error(request, e.message)
+ except GenerationInfo as e:
+ messages.info(request, e.message)
return HttpResponseRedirect(reverse('momo-manager-homepage'))
diff --git a/debian/cron.hourly b/debian/cron.hourly
index 7a5784ba..d04e9c3a 100644
--- a/debian/cron.hourly
+++ b/debian/cron.hourly
@@ -1,2 +1,3 @@
#!/bin/sh
-su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command update_transactions --all-tenants"
\ No newline at end of file
+su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command update_transactions --all-tenants"
+su combo -s /bin/sh -c "/usr/bin/combo-manage tenant_command update_momo_manifest --all-tenants -v0"