general: add pre-configured json cells (#15723)

This commit is contained in:
Frédéric Péters 2017-04-02 13:31:16 +02:00
parent ddcbc0aef6
commit b7d62e0021
6 changed files with 222 additions and 14 deletions

View File

@ -14,9 +14,11 @@
# 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 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

View File

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

View File

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

View File

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

View File

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

View File

@ -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('<p><label')
resp = app.get('/manage/pages/%s/' % page.id)
assert resp.form['c%s-test' % cells[0].get_reference()].value == 'World Hello'
def test_logout(app, admin_user):
app = login(app)
app.get('/logout/')