general: remove combo.apps.momo (#32913)
This commit is contained in:
parent
8832ba028c
commit
b7763d995f
|
@ -25,7 +25,6 @@ recursive-include combo/apps/fargo/templates *.html
|
||||||
recursive-include combo/apps/gallery/templates *.html
|
recursive-include combo/apps/gallery/templates *.html
|
||||||
recursive-include combo/apps/lingo/templates *.html
|
recursive-include combo/apps/lingo/templates *.html
|
||||||
recursive-include combo/apps/maps/templates *.html
|
recursive-include combo/apps/maps/templates *.html
|
||||||
recursive-include combo/apps/momo/templates *.html
|
|
||||||
recursive-include combo/apps/newsletters/templates *.html
|
recursive-include combo/apps/newsletters/templates *.html
|
||||||
recursive-include combo/apps/notifications/templates *.html
|
recursive-include combo/apps/notifications/templates *.html
|
||||||
recursive-include combo/apps/pwa/templates *.html *.js *.json
|
recursive-include combo/apps/pwa/templates *.html *.js *.json
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
Combo/momo integration
|
|
||||||
======================
|
|
||||||
|
|
||||||
Support is off by default, set ENABLE_MOMO = True to activate it.
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
Link cells are rendered as "see also" links.
|
|
|
@ -1,64 +0,0 @@
|
||||||
# combo - content management system
|
|
||||||
# Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import django.apps
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db import connection
|
|
||||||
from django.test.client import RequestFactory
|
|
||||||
from django.utils import translation
|
|
||||||
from django.utils.six.moves.urllib.parse import urlparse
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
class AppConfig(django.apps.AppConfig):
|
|
||||||
name = 'combo.apps.momo'
|
|
||||||
verbose_name = _('Mobile Application')
|
|
||||||
|
|
||||||
def is_enabled(self):
|
|
||||||
return getattr(settings, 'ENABLE_MOMO', False)
|
|
||||||
|
|
||||||
def get_before_urls(self):
|
|
||||||
from . import urls
|
|
||||||
return urls.urlpatterns
|
|
||||||
|
|
||||||
def get_extra_manager_actions(self):
|
|
||||||
return [{'href': reverse('momo-manager-homepage'),
|
|
||||||
'text': _('Mobile Application')}]
|
|
||||||
|
|
||||||
def hourly(self):
|
|
||||||
from .utils import GenerationInfo
|
|
||||||
try:
|
|
||||||
self.update_momo_manifest()
|
|
||||||
except GenerationInfo:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update_momo_manifest(self):
|
|
||||||
from .utils import generate_manifest
|
|
||||||
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
|
|
||||||
|
|
||||||
with translation.override(settings.LANGUAGE_CODE):
|
|
||||||
generate_manifest(request)
|
|
||||||
|
|
||||||
default_app_config = 'combo.apps.momo.AppConfig'
|
|
|
@ -1,32 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('auth', '0001_initial'),
|
|
||||||
('data', '0010_feedcell'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='MomoIconCell',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('placeholder', models.CharField(max_length=20)),
|
|
||||||
('order', models.PositiveIntegerField()),
|
|
||||||
('slug', models.SlugField(verbose_name='Slug', blank=True)),
|
|
||||||
('public', models.BooleanField(default=True, verbose_name='Public')),
|
|
||||||
('icon', models.CharField(default=b'', max_length=50, verbose_name='Icon', blank=True, choices=[(b'fa-home', 'Home'), (b'fa-globe', 'Globe'), (b'fa-mobile', 'Mobile'), (b'fa-comments', 'Comments'), (b'fa-map', 'Map'), (b'fa-users', 'Users'), (b'fa-institution', 'Institution'), (b'fa-bullhorn', 'Bull Horn'), (b'fa-calendar', 'Calendar'), (b'fa-map-marker', 'Map Marker'), (b'fa-book', 'Book')])),
|
|
||||||
('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
|
|
||||||
('page', models.ForeignKey(to='data.Page')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Icon for mobile',
|
|
||||||
},
|
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,27 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('momo', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='MomoOptions',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('title', models.CharField(max_length=100, null=True, verbose_name='Application Title')),
|
|
||||||
('contact_email', models.EmailField(max_length=75, null=True, verbose_name='Contact Email')),
|
|
||||||
('update_freq', models.PositiveIntegerField(default=86400, null=True, verbose_name='Update Frequency (in seconds)')),
|
|
||||||
('icons_on_homepage', models.BooleanField(default=False, verbose_name='Use icons on the homepage')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
},
|
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('momo', '0002_momooptions'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='momoiconcell',
|
|
||||||
name='icon',
|
|
||||||
field=models.CharField(default=b'', max_length=50, verbose_name='Icon', blank=True, choices=[(b'fa-home', 'Home'), (b'fa-globe', 'Globe'), (b'fa-mobile', 'Mobile'), (b'fa-comments', 'Comments'), (b'fa-map', 'Map'), (b'fa-users', 'Users'), (b'fa-institution', 'Institution'), (b'fa-bullhorn', 'Bull Horn'), (b'fa-calendar', 'Calendar'), (b'fa-map-marker', 'Map Marker'), (b'fa-book', 'Book'), (b'fa-envelope', 'Envelope'), (b'fa-car', 'Car'), (b'fa-road', 'Road')]),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import ckeditor.fields
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('momo', '0003_auto_20151021_1616'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='momoiconcell',
|
|
||||||
options={'verbose_name': 'Meta for mobile'},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='momoiconcell',
|
|
||||||
name='description',
|
|
||||||
field=ckeditor.fields.RichTextField(null=True, verbose_name='Description', blank=True),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('momo', '0004_momoiconcell_description'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='momoiconcell',
|
|
||||||
name='embed_page',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Embed redirection URL'),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('momo', '0005_momoiconcell_embed_page'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='momooptions',
|
|
||||||
name='extra_css',
|
|
||||||
field=models.CharField(max_length=100, null=True, verbose_name='Extra CSS', blank=True),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('momo', '0006_momooptions_extra_css'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='momoiconcell',
|
|
||||||
name='restricted_to_unlogged',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Restrict to unlogged users'),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('momo', '0007_momoiconcell_restricted_to_unlogged'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='momoiconcell',
|
|
||||||
name='style',
|
|
||||||
field=models.CharField(default=b'', max_length=128, verbose_name='Style', blank=True),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,19 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('momo', '0008_momoiconcell_style'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='momoiconcell',
|
|
||||||
name='icon',
|
|
||||||
field=models.CharField(default=b'', max_length=50, verbose_name='Icon', blank=True, choices=[(b'fa-home', 'Home'), (b'fa-globe', 'Globe'), (b'fa-mobile', 'Mobile'), (b'fa-comments', 'Comments'), (b'fa-map', 'Map'), (b'fa-users', 'Users'), (b'fa-institution', 'Institution'), (b'fa-bullhorn', 'Bull Horn'), (b'fa-calendar', 'Calendar'), (b'fa-map-marker', 'Map Marker'), (b'fa-book', 'Book'), (b'fa-envelope', 'Envelope'), (b'fa-car', 'Car'), (b'fa-road', 'Road'), (b'fa-heart', 'Heart')]),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,24 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('momo', '0009_auto_20160504_1036'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='momoiconcell',
|
|
||||||
name='extra_css_class',
|
|
||||||
field=models.CharField(max_length=100, verbose_name='Extra classes for CSS styling', blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='momooptions',
|
|
||||||
name='contact_email',
|
|
||||||
field=models.EmailField(max_length=254, null=True, verbose_name='Contact Email'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,28 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import datetime
|
|
||||||
from django.utils.timezone import utc
|
|
||||||
import combo.data.fields
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('momo', '0010_auto_20160928_1152'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='momoiconcell',
|
|
||||||
name='last_update_timestamp',
|
|
||||||
field=models.DateTimeField(default=datetime.datetime.now(utc), auto_now=True),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='momoiconcell',
|
|
||||||
name='description',
|
|
||||||
field=combo.data.fields.RichTextField(null=True, verbose_name='Description', blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,90 +0,0 @@
|
||||||
# combo - content management system
|
|
||||||
# Copyright (C) 2014-2015 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from django.apps import apps
|
|
||||||
from django.db import models
|
|
||||||
from django.forms import models as model_forms
|
|
||||||
from django.forms import Select
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from combo.data.fields import RichTextField
|
|
||||||
from combo.data.models import CellBase
|
|
||||||
from combo.data.library import register_cell_class
|
|
||||||
|
|
||||||
|
|
||||||
class MomoOptions(models.Model):
|
|
||||||
title = models.CharField(_('Application Title'), max_length=100, null=True)
|
|
||||||
contact_email = models.EmailField(_('Contact Email'), null=True)
|
|
||||||
update_freq = models.PositiveIntegerField(_('Update Frequency (in seconds)'),
|
|
||||||
default=86400, null=True)
|
|
||||||
icons_on_homepage = models.BooleanField(
|
|
||||||
_('Use icons on the homepage'), default=False)
|
|
||||||
extra_css = models.CharField(_('Extra CSS'), max_length=100, blank=True, null=True)
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.id = 1
|
|
||||||
return super(MomoOptions, self).save(*args, **kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_object(cls, *args, **kwargs):
|
|
||||||
obj, created = cls.objects.get_or_create(pk=1)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
@register_cell_class
|
|
||||||
class MomoIconCell(CellBase):
|
|
||||||
# initially for icons, now it holds additional page metadata such as
|
|
||||||
# description.
|
|
||||||
|
|
||||||
icon = models.CharField(_('Icon'), max_length=50,
|
|
||||||
default='', blank=True,
|
|
||||||
choices=[
|
|
||||||
('fa-home', _('Home')),
|
|
||||||
('fa-globe', _('Globe')),
|
|
||||||
('fa-mobile', _('Mobile')),
|
|
||||||
('fa-comments', _('Comments')),
|
|
||||||
('fa-map', _('Map')),
|
|
||||||
('fa-users', _('Users')),
|
|
||||||
('fa-institution', _('Institution')),
|
|
||||||
('fa-bullhorn', _('Bull Horn')),
|
|
||||||
('fa-calendar', _('Calendar')),
|
|
||||||
('fa-map-marker', _('Map Marker')),
|
|
||||||
('fa-book', _('Book')),
|
|
||||||
('fa-envelope', _('Envelope')),
|
|
||||||
('fa-car', _('Car')),
|
|
||||||
('fa-road', _('Road')),
|
|
||||||
('fa-heart', _('Heart')),
|
|
||||||
])
|
|
||||||
description = RichTextField(_('Description'), blank=True, null=True)
|
|
||||||
style = models.CharField(_('Style'), max_length=128, default='', blank=True)
|
|
||||||
embed_page = models.BooleanField(_('Embed redirection URL'), default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Meta for mobile')
|
|
||||||
|
|
||||||
def render(self, context):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_enabled(cls):
|
|
||||||
return apps.get_app_config('momo').is_enabled()
|
|
||||||
|
|
||||||
def get_default_form_class(self):
|
|
||||||
sorted_icons = self._meta.get_field('icon').choices
|
|
||||||
sorted_icons.sort(key=lambda x: x[1])
|
|
||||||
return model_forms.modelform_factory(self.__class__,
|
|
||||||
fields=['icon', 'style', 'description', 'embed_page'],
|
|
||||||
widgets={'icon': Select(choices=sorted_icons)})
|
|
|
@ -1,11 +0,0 @@
|
||||||
{% extends "combo/manager_base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block appbar %}
|
|
||||||
<h2>{% trans 'Mobile Application' %}</h2>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block breadcrumb %}
|
|
||||||
{{ block.super }}
|
|
||||||
<a href="{% url 'momo-manager-homepage' %}">{% trans 'Mobile Application' %}</a>
|
|
||||||
{% endblock %}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{% extends "momo/manager_base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block appbar %}
|
|
||||||
<h2>{% trans 'Mobile Application' %}</h2>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a rel="popup" href="{% url 'momo-manager-options' %}">{% trans 'Options' %}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="{% url 'momo-manager-generate' %}">{% trans 'Generate Content Update' %}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{% extends "momo/manager_base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block appbar %}
|
|
||||||
<h2>{% trans "Options" %}</h2>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<form method="post" enctype="multipart/form-data">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.as_p }}
|
|
||||||
<div class="buttons">
|
|
||||||
<button class="submit-button">{% trans "Save" %}</button>
|
|
||||||
<a class="cancel" href="{% url 'momo-manager-homepage' %}">{% trans 'Cancel' %}</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
# combo - content management system
|
|
||||||
# Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from django.conf.urls import url, include
|
|
||||||
|
|
||||||
from combo.urls_utils import decorated_includes, manager_required
|
|
||||||
|
|
||||||
from .views import MomoManagerView, OptionsUpdateView, generate
|
|
||||||
|
|
||||||
momo_manager_urls = [
|
|
||||||
url('^$', MomoManagerView.as_view(), name='momo-manager-homepage'),
|
|
||||||
url('^options/$', OptionsUpdateView.as_view(), name='momo-manager-options'),
|
|
||||||
url('^generate/$', generate, name='momo-manager-generate'),
|
|
||||||
]
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^manage/momo/', decorated_includes(manager_required,
|
|
||||||
include(momo_manager_urls))),
|
|
||||||
]
|
|
|
@ -1,260 +0,0 @@
|
||||||
# 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
from django.core.files.storage import default_storage
|
|
||||||
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_names]
|
|
||||||
if cell.slug:
|
|
||||||
classnames.append(cell.slug)
|
|
||||||
return '<div class="%s">%s</div>' % (' '.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 = {
|
|
||||||
'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.get_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 page.slug == 'index' and page.parent_id is None: # home
|
|
||||||
children = page.get_siblings()[1:]
|
|
||||||
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
|
|
||||||
try:
|
|
||||||
homepage = Page.objects.get(slug='index', parent_id=None)
|
|
||||||
except Page.DoesNotExist:
|
|
||||||
raise GenerationError(_('The homepage needs to be created first.'))
|
|
||||||
|
|
||||||
manifest.update(get_page_dict(request, homepage, manifest))
|
|
||||||
|
|
||||||
# footer
|
|
||||||
footer_cells = CellBase.get_cells(page_id=homepage.id, placeholder='footer')
|
|
||||||
if footer_cells:
|
|
||||||
context = {
|
|
||||||
'synchronous': True,
|
|
||||||
'page': homepage,
|
|
||||||
'page_cells': footer_cells,
|
|
||||||
'request': request,
|
|
||||||
'site_base': request.build_absolute_uri('/')[:-1],
|
|
||||||
}
|
|
||||||
manifest['footer'] = '\n'.join([
|
|
||||||
'<div id="footer-%s">%s</div>' % (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.'))
|
|
|
@ -1,48 +0,0 @@
|
||||||
# combo - content management system
|
|
||||||
# Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.utils.encoding import force_text
|
|
||||||
from django.views.generic import TemplateView, UpdateView
|
|
||||||
|
|
||||||
from .models import MomoOptions
|
|
||||||
from .utils import generate_manifest, GenerationError, GenerationInfo
|
|
||||||
|
|
||||||
|
|
||||||
class MomoManagerView(TemplateView):
|
|
||||||
template_name = 'momo/manager_home.html'
|
|
||||||
|
|
||||||
def generate(request, **kwargs):
|
|
||||||
try:
|
|
||||||
generate_manifest(request)
|
|
||||||
except GenerationError as e:
|
|
||||||
messages.error(request, force_text(e))
|
|
||||||
except GenerationInfo as e:
|
|
||||||
messages.info(request, force_text(e))
|
|
||||||
return HttpResponseRedirect(reverse('momo-manager-homepage'))
|
|
||||||
|
|
||||||
|
|
||||||
class OptionsUpdateView(UpdateView):
|
|
||||||
model = MomoOptions
|
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
def get_object(self, *args, **kwargs):
|
|
||||||
return MomoOptions.get_object()
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return reverse('momo-manager-homepage')
|
|
|
@ -68,7 +68,6 @@ INSTALLED_APPS = (
|
||||||
'combo.apps.family',
|
'combo.apps.family',
|
||||||
'combo.apps.dataviz',
|
'combo.apps.dataviz',
|
||||||
'combo.apps.lingo',
|
'combo.apps.lingo',
|
||||||
'combo.apps.momo',
|
|
||||||
'combo.apps.newsletters',
|
'combo.apps.newsletters',
|
||||||
'combo.apps.fargo',
|
'combo.apps.fargo',
|
||||||
'combo.apps.notifications',
|
'combo.apps.notifications',
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from webtest import TestApp
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from combo.data.models import Page, CellBase, TextCell, LinkCell, FeedCell
|
|
||||||
|
|
||||||
from .test_manager import login
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
|
||||||
|
|
||||||
|
|
||||||
class MomoEnabled(object):
|
|
||||||
def __enter__(self):
|
|
||||||
settings.ENABLE_MOMO = True
|
|
||||||
|
|
||||||
def __exit__(self, *args, **kwargs):
|
|
||||||
settings.ENABLE_MOMO = False
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def assets_base():
|
|
||||||
assets_base_path = os.path.join(settings.MEDIA_ROOT, 'assets-base.zip')
|
|
||||||
if not os.path.exists(assets_base_path):
|
|
||||||
fd = open(assets_base_path, 'wb')
|
|
||||||
z = zipfile.ZipFile(fd, 'w')
|
|
||||||
z.close()
|
|
||||||
|
|
||||||
|
|
||||||
def test_no_menu_if_not_enabled(app, admin_user):
|
|
||||||
app = login(app)
|
|
||||||
resp = app.get('/manage/', status=200)
|
|
||||||
assert not 'Mobile Application' in resp.text
|
|
||||||
|
|
||||||
|
|
||||||
def test_menu_if_enabled(app, admin_user):
|
|
||||||
with MomoEnabled():
|
|
||||||
app = login(app)
|
|
||||||
resp = app.get('/manage/', status=200)
|
|
||||||
assert 'Mobile Application' in resp.text
|
|
||||||
|
|
||||||
def test_options(app, admin_user):
|
|
||||||
with MomoEnabled():
|
|
||||||
app = login(app)
|
|
||||||
resp = app.get('/manage/', status=200)
|
|
||||||
resp = resp.click('Mobile Application')
|
|
||||||
resp = resp.click('Options')
|
|
||||||
resp.form['title'] = 'Momo Test'
|
|
||||||
resp.form['contact_email'] = 'foobar@localhost'
|
|
||||||
resp = resp.form.submit()
|
|
||||||
resp = resp.follow()
|
|
||||||
resp = resp.click('Options')
|
|
||||||
assert resp.form['title'].value == 'Momo Test'
|
|
||||||
|
|
||||||
def test_generate_no_assets_base(app, admin_user):
|
|
||||||
with MomoEnabled():
|
|
||||||
app = login(app)
|
|
||||||
resp = app.get('/manage/', status=200)
|
|
||||||
resp = resp.click('Mobile Application')
|
|
||||||
resp = resp.click('Generate Content Update')
|
|
||||||
assert not os.path.exists(os.path.join(settings.MEDIA_ROOT, 'index.json'))
|
|
||||||
|
|
||||||
def test_generate_no_homepage(app, admin_user, assets_base):
|
|
||||||
with MomoEnabled():
|
|
||||||
app = login(app)
|
|
||||||
resp = app.get('/manage/', status=200)
|
|
||||||
resp = resp.click('Mobile Application')
|
|
||||||
resp = resp.click('Generate Content Update')
|
|
||||||
assert not os.path.exists(os.path.join(settings.MEDIA_ROOT, 'index.json'))
|
|
||||||
|
|
||||||
def test_generate_simple(app, admin_user, assets_base):
|
|
||||||
Page.objects.all().delete()
|
|
||||||
page1 = Page(title='My Mobile App', slug='index', template_name='standard')
|
|
||||||
page1.save()
|
|
||||||
page2 = Page(title='Two', slug='two', template_name='standard')
|
|
||||||
page2.save()
|
|
||||||
page3 = Page(title='Three', slug='three', parent=page1, template_name='standard')
|
|
||||||
page3.save()
|
|
||||||
|
|
||||||
cell = TextCell(page=page2, placeholder='content', text='Lorem ipsum', order=0)
|
|
||||||
cell.save()
|
|
||||||
|
|
||||||
cell = TextCell(page=page1, placeholder='footer', text='This is the footer', order=0)
|
|
||||||
cell.save()
|
|
||||||
|
|
||||||
with MomoEnabled():
|
|
||||||
app = login(app)
|
|
||||||
resp = app.get('/manage/', status=200)
|
|
||||||
resp = resp.click('Mobile Application')
|
|
||||||
resp = resp.click('Generate Content Update')
|
|
||||||
assert os.path.exists(os.path.join(settings.MEDIA_ROOT, 'index.json'))
|
|
||||||
assert os.path.exists(os.path.join(settings.MEDIA_ROOT, 'assets.zip'))
|
|
||||||
content = json.load(open(os.path.join(settings.MEDIA_ROOT, 'index.json')))
|
|
||||||
assert content['meta']['title'] == 'My Mobile App'
|
|
||||||
assert 'This is the footer' in content['footer']
|
|
||||||
assert len(content['pages']) == 1
|
|
||||||
assert content['pages'][0]['title'] == 'Two'
|
|
||||||
assert 'Lorem ipsum' in content['pages'][0]['content']
|
|
||||||
assert 'menu-three-%s' % page3.id in content['menu']
|
|
Loading…
Reference in New Issue