diff --git a/combo/data/fields.py b/combo/data/fields.py index ebfa1533..620baaca 100644 --- a/combo/data/fields.py +++ b/combo/data/fields.py @@ -18,6 +18,9 @@ from django import forms from django.conf import settings +from django.core import validators +from django.forms.widgets import TextInput +from django.utils.encoding import force_text import ckeditor.fields @@ -48,3 +51,22 @@ class RichTextFormField(ckeditor.fields.RichTextFormField): value = value.replace(u' !', u'\u202f!') value = value.replace(u' ?', u'\u202f?') return value + + +def templatable_url_validator(value): + value = force_text(value) + if '{{' in value or '{%' in value: + # leave templates alone + return + validators.URLValidator()(value) + + +class TemplatableURLField(forms.URLField): + widget = TextInput + default_validators = [templatable_url_validator] + + def to_python(self, value): + value = super(forms.URLField, self).to_python(value) + if '{{' in value or '{%' in value: + return value + return super(TemplatableURLField, self).to_python(value) diff --git a/combo/data/migrations/0037_auto_20190701_2118.py b/combo/data/migrations/0037_auto_20190701_2118.py new file mode 100644 index 00000000..3892788e --- /dev/null +++ b/combo/data/migrations/0037_auto_20190701_2118.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.12 on 2019-07-01 19:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('data', '0036_page_sub_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='feedcell', + name='url', + field=models.CharField(blank=True, max_length=200, verbose_name='URL'), + ), + migrations.AlterField( + model_name='jsoncell', + name='url', + field=models.CharField(blank=True, max_length=200, verbose_name='URL'), + ), + ] diff --git a/combo/data/models.py b/combo/data/models.py index 27cdcda5..86f9502b 100644 --- a/combo/data/models.py +++ b/combo/data/models.py @@ -50,7 +50,7 @@ from django.forms.widgets import MediaDefiningClass from django.template import Context, engines, TemplateDoesNotExist from django.test.client import RequestFactory -from .fields import RichTextField +from .fields import RichTextField, TemplatableURLField from jsonfield import JSONField @@ -505,9 +505,11 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)): last_update_timestamp = models.DateTimeField(auto_now=True) default_form_class = None + manager_form_factory_kwargs = {} + manager_form_template = 'combo/cell_form.html' + visible = True user_dependant = False - manager_form_template = 'combo/cell_form.html' template_name = None # get_badge(self, context); set to None so cell types can be skipped easily @@ -658,7 +660,7 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)): if not fields: return None - return model_forms.modelform_factory(self.__class__, fields=fields) + return model_forms.modelform_factory(self.__class__, fields=fields, **self.manager_form_factory_kwargs) def get_options_form_class(self): return model_forms.modelform_factory(self.__class__, @@ -907,10 +909,11 @@ class LinkCell(CellBase): @register_cell_class class FeedCell(CellBase): title = models.CharField(_('Title'), max_length=150, blank=True) - url = models.URLField(_('URL'), blank=True) + url = models.CharField(_('URL'), blank=True, max_length=200) limit = models.PositiveSmallIntegerField(_('Maximum number of entries'), null=True, blank=True) + manager_form_factory_kwargs = {'field_classes': {'url': TemplatableURLField}} template_name = 'combo/feed-cell.html' class Meta: @@ -1275,7 +1278,7 @@ class JsonCellBase(CellBase): @register_cell_class class JsonCell(JsonCellBase): title = models.CharField(_('Title'), max_length=150, blank=True) - url = models.URLField(_('URL'), blank=True) + url = models.CharField(_('URL'), blank=True, max_length=200) template_string = models.TextField(_('Display Template'), blank=True, null=True) cache_duration = models.PositiveIntegerField( _('Cache duration'), default=60) @@ -1287,6 +1290,8 @@ class JsonCell(JsonCellBase): timeout = models.PositiveIntegerField(_('Request timeout'), default=0, help_text=_('In seconds. Use 0 for default system timeout')) + manager_form_factory_kwargs = {'field_classes': {'url': TemplatableURLField}} + class Meta: verbose_name = _('JSON Prototype') diff --git a/tests/test_manager.py b/tests/test_manager.py index c6ca5776..f96adb83 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -586,6 +586,38 @@ def test_edit_text_cell(app, admin_user): assert TextCell.objects.get(id=cells[0].id).text == u'Hello\u00a0: World' +def test_edit_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) + data_add_url = [x for x in resp.html.find_all('option') if x.text == 'JSON Prototype'][0].get('data-add-url') + resp = app.get(data_add_url) + + cells = CellBase.get_cells(page_id=page.id) + assert len(cells) == 1 + assert isinstance(cells[0], JsonCell) + assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.id, cells[0].get_reference())) + + resp = app.get('/manage/pages/%s/' % page.id) + resp.form['cdata_jsoncell-%s-url' % cells[0].id].value = 'xxx' + resp = resp.form.submit() + assert 'Enter a valid URL.' in resp.text + assert JsonCell.objects.get(id=cells[0].id).url == '' + + resp = app.get('/manage/pages/%s/' % page.id) + resp.form['cdata_jsoncell-%s-url' % cells[0].id].value = 'https://www.example.net/' + resp = resp.form.submit() + assert JsonCell.objects.get(id=cells[0].id).url == 'https://www.example.net/' + + resp = app.get('/manage/pages/%s/' % page.id) + resp.form['cdata_jsoncell-%s-url' % cells[0].id].value = '{{url}}' + resp = resp.form.submit() + assert JsonCell.objects.get(id=cells[0].id).url == '{{url}}' + + def test_edit_config_json_cell(app, admin_user): Page.objects.all().delete() page = Page(title='One', slug='one', template_name='standard')