general: add pre-configured json cells (#15723)
This commit is contained in:
parent
ddcbc0aef6
commit
b7d62e0021
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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/')
|
||||
|
|
Loading…
Reference in New Issue