diff --git a/combo/data/forms.py b/combo/data/forms.py
index fdf84ab0..c7f486a9 100644
--- a/combo/data/forms.py
+++ b/combo/data/forms.py
@@ -14,9 +14,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+import copy
+
from django import forms
-from .models import Page, ParametersCell, MenuCell
+from .models import Page, ParametersCell, MenuCell, ConfigJsonCell
from jsonfield.widgets import JSONWidget
class ParametersForm(forms.Form):
@@ -61,3 +63,29 @@ class MenuCellForm(forms.ModelForm):
pages = Page.get_as_reordered_flat_hierarchy(Page.objects.all())
choices = [(x.id, '%s %s' % (u'\u00a0' * x.level * 2, x.title)) for x in pages]
self.fields['root_page'].widget = forms.Select(choices=choices)
+
+
+class ConfigJsonForm(forms.ModelForm):
+ formdef = []
+
+ class Meta:
+ model = ConfigJsonCell
+ fields = ('parameters',)
+ widgets = {'parameters': forms.HiddenInput()}
+
+ def __init__(self, *args, **kwargs):
+ parameters = copy.copy(kwargs['instance'].parameters or {})
+ # reset parameters in instance as the value is actually created from
+ # additional fields.
+ kwargs['instance'].parameters = None
+ super(ConfigJsonForm, self).__init__(*args, **kwargs)
+ for field in self.formdef:
+ self.fields[field['varname']] = forms.CharField(
+ label=field['label'],
+ initial=parameters.get(field['varname']))
+
+ def clean(self):
+ self.cleaned_data['parameters'] = {}
+ for field in self.formdef:
+ self.cleaned_data['parameters'][field['varname']] = self.cleaned_data[field['varname']]
+ return self.cleaned_data
diff --git a/combo/data/migrations/0024_configjsoncell.py b/combo/data/migrations/0024_configjsoncell.py
new file mode 100644
index 00000000..707602dd
--- /dev/null
+++ b/combo/data/migrations/0024_configjsoncell.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import jsonfield.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('auth', '0006_require_contenttypes_0002'),
+ ('data', '0023_auto_20170313_1541'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ConfigJsonCell',
+ 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)),
+ ('extra_css_class', models.CharField(max_length=100, verbose_name='Extra classes for CSS styling', blank=True)),
+ ('public', models.BooleanField(default=True, verbose_name='Public')),
+ ('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
+ ('last_update_timestamp', models.DateTimeField(auto_now=True)),
+ ('key', models.CharField(max_length=50)),
+ ('parameters', jsonfield.fields.JSONField(default=dict, blank=True)),
+ ('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
+ ('page', models.ForeignKey(to='data.Page')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ ]
diff --git a/combo/data/models.py b/combo/data/models.py
index 86071a39..801fa35c 100644
--- a/combo/data/models.py
+++ b/combo/data/models.py
@@ -804,24 +804,22 @@ class ParentContentCell(CellBase):
def render(self, context):
return ''
-@register_cell_class
-class JsonCell(CellBase):
- title = models.CharField(_('Title'), max_length=150, blank=True)
- url = models.URLField(_('URL'), blank=True)
- template_string = models.TextField(_('Template'), blank=True, null=True)
- cache_duration = models.PositiveIntegerField(
- _('Cache duration'), default=60)
+
+class JsonCellBase(CellBase):
+ url = None
+ cache_duration = 60
+ template_string = None
_json_content = None
class Meta:
- verbose_name = _('JSON Feed')
+ abstract = True
def is_visible(self, user=None):
- return bool(self.url) and super(JsonCell, self).is_visible(user=user)
+ return bool(self.url) and super(JsonCellBase, self).is_visible(user=user)
def get_cell_extra_context(self, context):
- extra_context = super(JsonCell, self).get_cell_extra_context(context)
+ extra_context = super(JsonCellBase, self).get_cell_extra_context(context)
self._json_content = None
try:
url = utils.get_templated_url(self.url, context)
@@ -862,4 +860,65 @@ class JsonCell(CellBase):
if self.template_string:
tmpl = Template(self.template_string)
return tmpl.render(context)
- return super(JsonCell, self).render(context)
+ return super(JsonCellBase, self).render(context)
+
+
+@register_cell_class
+class JsonCell(JsonCellBase):
+ title = models.CharField(_('Title'), max_length=150, blank=True)
+ url = models.URLField(_('URL'), blank=True)
+ template_string = models.TextField(_('Template'), blank=True, null=True)
+ cache_duration = models.PositiveIntegerField(
+ _('Cache duration'), default=60)
+
+ class Meta:
+ verbose_name = _('JSON Feed')
+
+
+@register_cell_class
+class ConfigJsonCell(JsonCellBase):
+ key = models.CharField(max_length=50)
+ parameters = JSONField(blank=True)
+
+ @classmethod
+ def get_cell_types(cls):
+ l = []
+ for key, definition in settings.JSON_CELL_TYPES.items():
+ l.append({
+ 'name': definition['name'],
+ 'variant': key,
+ 'group': _('Extra'),
+ 'cell_type_str': cls.get_cell_type_str(),
+ })
+ l.sort(lambda x, y: cmp(x.get('name'), y.get('name')))
+ return l
+
+ def get_label(self):
+ return settings.JSON_CELL_TYPES[self.key]['name']
+
+ def set_variant(self, variant):
+ self.key = variant
+
+ @property
+ def url(self):
+ return settings.JSON_CELL_TYPES[self.key]['url']
+
+ @property
+ def template_name(self):
+ return 'combo/json/%s.html' % self.key
+
+ def get_default_form_class(self):
+ formdef = settings.JSON_CELL_TYPES[self.key].get('form')
+ if not formdef:
+ return None
+ from .forms import ConfigJsonForm
+ # create a subclass of ConfigJsonForm with 'formdef' (the list of
+ # fields) as an attribute.
+ config_form_class = type(str('%sConfigClass' % self.key),
+ (ConfigJsonForm,), {'formdef': formdef})
+ return config_form_class
+
+ def get_cell_extra_context(self, context):
+ ctx = super(ConfigJsonCell, self).get_cell_extra_context(context)
+ ctx['parameters'] = self.parameters
+ return ctx
diff --git a/combo/settings.py b/combo/settings.py
index 2c31fed3..7126bb9e 100644
--- a/combo/settings.py
+++ b/combo/settings.py
@@ -264,6 +264,8 @@ MELLON_IDENTITY_PROVIDERS = []
# mapping of payment modes
LINGO_NO_ONLINE_PAYMENT_REASONS = {}
+JSON_CELL_TYPES = {}
+
local_settings_file = os.environ.get('COMBO_SETTINGS_FILE',
os.path.join(os.path.dirname(__file__), 'local_settings.py'))
diff --git a/tests/test_cells.py b/tests/test_cells.py
index b5154a81..36b99874 100644
--- a/tests/test_cells.py
+++ b/tests/test_cells.py
@@ -4,7 +4,7 @@ import os
import pytest
import requests
-from combo.data.models import Page, CellBase, TextCell, LinkCell, JsonCell
+from combo.data.models import Page, CellBase, TextCell, LinkCell, JsonCell, ConfigJsonCell
from django.forms.widgets import Media
from django.template import Context
from django.test import override_settings
@@ -210,3 +210,23 @@ def test_json_cell():
assert requests_get.call_count == 1
assert requests_get.call_args[0][0] == 'http://testuser?email=foo%40example.net'
assert requests_get.call_args[1]['cache_duration'] == 10
+
+def test_config_json_cell():
+ page = Page(title='example page', slug='example-page')
+ page.save()
+
+ request = RequestFactory().get('/')
+
+ with override_settings(JSON_CELL_TYPES={'foobar': {'name': 'Foobar', 'url': 'http://test/'}}):
+ cell = ConfigJsonCell()
+ cell.key = 'foobar'
+ cell.parameters = {'blah': 'plop'}
+ assert cell.get_label() == 'Foobar'
+ assert cell.url == 'http://test/'
+ assert cell.template_name == 'combo/json/foobar.html'
+
+ with mock.patch('combo.utils.requests.get') as requests_get:
+ requests_get.return_value = mock.Mock(content=json.dumps({'hello': 'world'}), status_code=200)
+ context = cell.get_cell_extra_context(Context({'request': request}))
+ assert context['json'] == {'hello': 'world'}
+ assert context['parameters'] == {'blah': 'plop'}
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 82d450c7..885aa05f 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -6,13 +6,14 @@ import urllib
from django.core.files.storage import default_storage
from django.conf import settings
from django.contrib.auth.models import User
+from django.test import override_settings
import pytest
from webtest import TestApp
from webtest import Upload
from combo.wsgi import application
-from combo.data.models import Page, CellBase, TextCell, LinkCell
+from combo.data.models import Page, CellBase, TextCell, LinkCell, ConfigJsonCell
from combo.apps.search.models import SearchCell
pytestmark = pytest.mark.django_db
@@ -423,6 +424,68 @@ def test_edit_cell_order(app, admin_user):
for i, cell in enumerate(cells):
assert TextCell.objects.get(id=cell.id).order == new_order[i]
+
+def test_edit_config_json_cell(app, admin_user):
+ Page.objects.all().delete()
+ page = Page(title='One', slug='one', template_name='standard')
+ page.save()
+
+ app = login(app)
+ resp = app.get('/manage/pages/%s/' % page.id)
+ options = [x.text for x in resp.html.find_all('option')]
+ assert not 'Foobar' in options
+
+ with override_settings(JSON_CELL_TYPES={'foobar': {'name': 'Foobar', 'url': 'http://test/'}}):
+ resp = app.get('/manage/pages/%s/' % page.id)
+ options = [x.text for x in resp.html.find_all('option')]
+ assert 'Foobar' in options
+ data_add_url = [x for x in resp.html.find_all('option') if x.text == 'Foobar'][0].get('data-add-url')
+ resp = app.get(data_add_url)
+ assert resp.location == 'http://testserver/manage/pages/%s/' % page.id
+
+ cells = CellBase.get_cells(page_id=page.id)
+ assert len(cells) == 1
+ assert isinstance(cells[0], ConfigJsonCell)
+ assert cells[0].key == 'foobar'
+
+ resp = app.get('/manage/pages/%s/' % page.id)
+ assert ('data-cell-reference="%s"' % cells[0].get_reference()) in resp.body
+ assert 'There are no options for this cell.' in resp.form.text
+
+ # make it configurable
+ with override_settings(JSON_CELL_TYPES=
+ {'foobar': {
+ 'name': 'Foobar',
+ 'url': 'http://test/',
+ 'form': [
+ {
+ 'label': 'Test',
+ 'type': 'string',
+ 'varname': 'test',
+ }
+ ]}}):
+ resp = app.get('/manage/pages/%s/' % page.id)
+ assert not 'There are no options for this cell.' in resp.form.text
+
+ resp.form['c%s-test' % cells[0].get_reference()].value = 'Hello world'
+ resp = resp.form.submit()
+ assert resp.status_int == 302
+ assert resp.location == 'http://testserver/manage/pages/%s/' % page.id
+
+ resp = app.get('/manage/pages/%s/' % page.id)
+ assert resp.form['c%s-test' % cells[0].get_reference()].value == 'Hello world'
+
+ resp = app.get('/manage/pages/%s/' % page.id)
+ assert ('data-cell-reference="%s"' % cells[0].get_reference()) in resp.body
+ resp.forms[0]['c%s-test' % cells[0].get_reference()].value = 'World Hello'
+ resp = resp.form.submit(xhr=True)
+ assert resp.status_int == 200
+ assert resp.body.startswith('