data: add page parameters (#59798)
This commit is contained in:
parent
941ae66e2a
commit
757cd93c76
|
@ -0,0 +1,17 @@
|
|||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('data', '0052_auto_20211110_1521'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='page',
|
||||
name='extra_variables',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
||||
),
|
||||
]
|
|
@ -45,7 +45,7 @@ from django.db.models.signals import post_delete, post_save, pre_save
|
|||
from django.dispatch import receiver
|
||||
from django.forms import models as model_forms
|
||||
from django.forms.widgets import MediaDefiningClass
|
||||
from django.template import TemplateDoesNotExist, TemplateSyntaxError, engines
|
||||
from django.template import RequestContext, Template, TemplateDoesNotExist, TemplateSyntaxError, engines
|
||||
from django.test.client import RequestFactory
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import force_text, python_2_unicode_compatible, smart_bytes
|
||||
|
@ -194,6 +194,7 @@ class Page(models.Model):
|
|||
order = models.PositiveIntegerField()
|
||||
exclude_from_navigation = models.BooleanField(_('Exclude from navigation'), default=True)
|
||||
redirect_url = models.CharField(_('Redirect URL'), max_length=200, blank=True)
|
||||
extra_variables = JSONField(blank=True, default=dict)
|
||||
|
||||
public = models.BooleanField(_('Public'), default=True)
|
||||
groups = models.ManyToManyField(Group, verbose_name=_('Groups'), blank=True)
|
||||
|
@ -641,6 +642,21 @@ class Page(models.Model):
|
|||
def get_last_update_time(self):
|
||||
return self.last_update_timestamp
|
||||
|
||||
def get_extra_variables(self, request, original_context):
|
||||
result = {}
|
||||
context = RequestContext(request)
|
||||
context.push(original_context)
|
||||
for key, tplt in (self.extra_variables or {}).items():
|
||||
try:
|
||||
template = Template(tplt)
|
||||
except TemplateSyntaxError:
|
||||
continue
|
||||
result[key] = template.render(context)
|
||||
return result
|
||||
|
||||
def get_extra_variables_keys(self):
|
||||
return sorted((self.extra_variables or {}).keys())
|
||||
|
||||
def is_new(self):
|
||||
return self.creation_timestamp > timezone.now() - datetime.timedelta(days=7)
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ from django import forms
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms import formset_factory
|
||||
from django.template import Template, TemplateSyntaxError
|
||||
from django.template.loader import TemplateDoesNotExist, get_template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -168,6 +169,16 @@ class PageEditPictureForm(forms.ModelForm):
|
|||
field_classes = {'picture': ImageIncludingSvgField}
|
||||
|
||||
|
||||
class PageEditExtraVariableForm(forms.Form):
|
||||
key = forms.CharField(label=_('Variable name'), required=False)
|
||||
value = forms.CharField(
|
||||
label=_('Value template'), widget=forms.TextInput(attrs={'size': 60}), required=False
|
||||
)
|
||||
|
||||
|
||||
PageEditExtraVariablesFormSet = formset_factory(PageEditExtraVariableForm)
|
||||
|
||||
|
||||
class PageVisibilityForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Page
|
||||
|
|
|
@ -414,6 +414,20 @@ $(function() {
|
|||
}
|
||||
prepare_dynamic_fields();
|
||||
$(document).on('combo:dialog-loaded', prepare_dynamic_fields);
|
||||
|
||||
$(document).on('click', '#add-page-property-form', function() {
|
||||
if (typeof property_forms === "undefined") {var property_forms = $('.page-property-form');}
|
||||
if (typeof total_forms === "undefined") {var total_form = $('#id_form-TOTAL_FORMS');}
|
||||
if (typeof form_num === "undefined") {var form_num = property_forms.length - 1;}
|
||||
var new_form = $(property_forms[0]).clone();
|
||||
var form_regex = RegExp(`form-(\\d){1}-`,'g');
|
||||
form_num++;
|
||||
new_form.html(new_form.html().replace(form_regex, `form-${form_num}-`));
|
||||
new_form.appendTo('#page-property-forms tbody');
|
||||
$('#id_form-' + form_num + '-key').val('');
|
||||
$('#id_form-' + form_num + '-value').val('');
|
||||
total_form.val(form_num + 1);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{% extends "combo/page_add.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.management_form }}
|
||||
<table id="page-property-forms">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for field in form.0 %}
|
||||
<th class="column-{{ field.name }}{% if field.required %} required{% endif %}">{{ field.label }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for sub_form in form %}
|
||||
<tr class='page-property-form'>
|
||||
{% for field in sub_form %}
|
||||
<td class="field-{{ field.name }}">
|
||||
{{ field.errors.as_ul }}
|
||||
{{ field }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<button id="add-page-property-form" type="button">{% trans "Add another" %}</button>
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'combo-manager-page-view' pk=object.pk %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -11,6 +11,7 @@
|
|||
<ul class="extra-actions-menu">
|
||||
<li><a class="action-history" href="{% url 'combo-manager-page-history' pk=object.id %}">{% trans 'History' %}</a></li>
|
||||
<li><a {% if page_has_subpages %}rel="popup" data-autoclose-dialog="true" {% endif %}class="action-export" href="{% url 'combo-manager-page-export' pk=object.id %}">{% trans 'Export' %}</a></li>
|
||||
<li><a class="action-edit-page-variables" rel="popup" href="{% url 'combo-manager-page-edit-extra-variables' pk=object.id %}">{% trans "Edit extra page variables" %}</a></li>
|
||||
{% if request.user.is_superuser %}
|
||||
<li><a class="action-add-child" rel="popup" href="{% url 'combo-manager-page-add-child' pk=object.id %}">{% trans 'Add a child page' %}</a></li>
|
||||
<li><a class="action-edit-roles" rel="popup" href="{% url 'combo-manager-page-edit-roles' pk=object.id %}">{% trans 'Manage edit roles' %}</a></li>
|
||||
|
@ -90,6 +91,14 @@
|
|||
(<a rel="popup" href="{% url 'combo-manager-page-edit-picture' pk=object.id %}">{% trans 'change' %}</a>)
|
||||
</p>
|
||||
|
||||
{% if object.extra_variables %}
|
||||
<p>
|
||||
<label>{% trans 'Extra variables:' %}</label>
|
||||
{% for key in object.get_extra_variables_keys %}<i>{{ key }}</i>{% if not forloop.last %}, {% endif %}{% endfor %}
|
||||
(<a rel="popup" href="{% url 'combo-manager-page-edit-extra-variables' pk=object.id %}">{% trans 'change' %}</a>)
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% if object.parent_id or previous_page or next_page %}
|
||||
|
|
|
@ -64,6 +64,11 @@ urlpatterns = [
|
|||
views.page_remove_picture,
|
||||
name='combo-manager-page-remove-picture',
|
||||
),
|
||||
url(
|
||||
r'^pages/(?P<pk>\d+)/extra-variables/$',
|
||||
views.page_edit_extra_variables,
|
||||
name='combo-manager-page-edit-extra-variables',
|
||||
),
|
||||
url(r'^pages/(?P<pk>\d+)/delete$', staff_required(views.page_delete), name='combo-manager-page-delete'),
|
||||
url(r'^pages/(?P<pk>\d+)/export$', views.page_export, name='combo-manager-page-export'),
|
||||
url(
|
||||
|
|
|
@ -18,7 +18,7 @@ import datetime
|
|||
import hashlib
|
||||
import json
|
||||
import tarfile
|
||||
from operator import attrgetter
|
||||
from operator import attrgetter, itemgetter
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
|
@ -63,6 +63,7 @@ from .forms import (
|
|||
PageAddForm,
|
||||
PageDuplicateForm,
|
||||
PageEditDescriptionForm,
|
||||
PageEditExtraVariablesFormSet,
|
||||
PageEditIncludeInNavigationForm,
|
||||
PageEditPictureForm,
|
||||
PageEditRedirectionForm,
|
||||
|
@ -366,6 +367,38 @@ class PageRemovePictureView(ManagedPageMixin, DetailView):
|
|||
page_remove_picture = PageRemovePictureView.as_view()
|
||||
|
||||
|
||||
class PageEditExtraVariablesView(PageEditView):
|
||||
form_class = PageEditExtraVariablesFormSet
|
||||
comment = _('changed extra variables')
|
||||
template_name = 'combo/page_extra_variables.html'
|
||||
page_title = _('Extra page variables')
|
||||
|
||||
def get_initial(self):
|
||||
return sorted(
|
||||
({'key': k, 'value': v} for k, v in self.get_object().extra_variables.items()),
|
||||
key=itemgetter('key'),
|
||||
)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs.pop('instance')
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = self.get_object()
|
||||
self.object.extra_variables = {}
|
||||
for sub_data in form.cleaned_data:
|
||||
if not sub_data.get('key'):
|
||||
continue
|
||||
self.object.extra_variables[sub_data['key']] = sub_data['value']
|
||||
self.object.save()
|
||||
PageSnapshot.take(self.object, request=self.request, comment=self.comment)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
|
||||
page_edit_extra_variables = PageEditExtraVariablesView.as_view()
|
||||
|
||||
|
||||
class PageView(ManagedPageMixin, DetailView):
|
||||
model = Page
|
||||
template_name = 'combo/page_view.html'
|
||||
|
|
|
@ -102,6 +102,9 @@ def modify_global_context(request, ctx):
|
|||
pass
|
||||
if 'name_id' in ctx:
|
||||
ctx['selected_user'] = get_user_from_name_id(ctx['name_id'])
|
||||
if 'page' in ctx:
|
||||
page = ctx['page']
|
||||
ctx.update(page.get_extra_variables(request, ctx))
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
|
|
|
@ -571,6 +571,57 @@ def test_page_edit_picture(app, admin_user):
|
|||
assert Page.objects.get(id=page.id).picture.url in resp.text
|
||||
|
||||
|
||||
def test_page_edit_extra_variables(app, admin_user):
|
||||
app = login(app)
|
||||
page = Page.objects.create(title='One', slug='one', template_name='standard')
|
||||
assert page.extra_variables == {}
|
||||
resp = app.get('/manage/pages/%s/' % page.id)
|
||||
assert '<label>Extra variables:</label>' not in resp.text
|
||||
resp = resp.click(href='.*/extra-variables/')
|
||||
resp.form['form-0-key'] = 'foo'
|
||||
resp.form['form-0-value'] = 'bar'
|
||||
resp = resp.form.submit().follow()
|
||||
page.refresh_from_db()
|
||||
assert page.extra_variables == {'foo': 'bar'}
|
||||
assert '<label>Extra variables:</label>' in resp.text
|
||||
assert '<i>foo</i>' in resp
|
||||
|
||||
resp = resp.click(href='.*/extra-variables/', index=0)
|
||||
assert resp.form['form-TOTAL_FORMS'].value == '2'
|
||||
assert resp.form['form-0-key'].value == 'foo'
|
||||
assert resp.form['form-0-value'].value == 'bar'
|
||||
assert resp.form['form-1-key'].value == ''
|
||||
assert resp.form['form-1-value'].value == ''
|
||||
resp.form['form-0-value'] = 'bar-bis'
|
||||
resp.form['form-1-key'] = 'blah'
|
||||
resp.form['form-1-value'] = 'baz'
|
||||
resp = resp.form.submit().follow()
|
||||
page.refresh_from_db()
|
||||
assert page.extra_variables == {
|
||||
'foo': 'bar-bis',
|
||||
'blah': 'baz',
|
||||
}
|
||||
assert '<i>blah</i>, <i>foo</i>' in resp
|
||||
|
||||
resp = resp.click(href='.*/extra-variables/', index=0)
|
||||
assert resp.form['form-TOTAL_FORMS'].value == '3'
|
||||
assert resp.form['form-0-key'].value == 'blah'
|
||||
assert resp.form['form-0-value'].value == 'baz'
|
||||
assert resp.form['form-1-key'].value == 'foo'
|
||||
assert resp.form['form-1-value'].value == 'bar-bis'
|
||||
assert resp.form['form-2-key'].value == ''
|
||||
assert resp.form['form-2-value'].value == ''
|
||||
resp.form['form-1-key'] = 'foo'
|
||||
resp.form['form-1-value'] = 'bar'
|
||||
resp.form['form-0-key'] = ''
|
||||
resp = resp.form.submit().follow()
|
||||
page.refresh_from_db()
|
||||
assert page.extra_variables == {
|
||||
'foo': 'bar',
|
||||
}
|
||||
assert '<i>foo</i>' in resp
|
||||
|
||||
|
||||
def test_page_placeholder_restricted_visibility(app, admin_user):
|
||||
app = login(app)
|
||||
|
||||
|
|
|
@ -577,7 +577,7 @@ def test_style_demo(app, admin_user):
|
|||
assert Page.objects.count() == 0
|
||||
|
||||
|
||||
def test_page_async_cell(app):
|
||||
def test_page_async_cell(app, nocache):
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='Home', slug='index', template_name='standard')
|
||||
page.save()
|
||||
|
@ -1093,6 +1093,54 @@ def test_sub_slug(app, john_doe, jane_doe):
|
|||
assert resp.context['card_foo_bar_id'] == '42'
|
||||
|
||||
|
||||
def test_page_extra_variables(app):
|
||||
page = Page.objects.create(
|
||||
title='Home',
|
||||
slug='page',
|
||||
template_name='standard',
|
||||
extra_variables={'foo': 'bar', 'bar_id': '{{ 40|add:2 }}'},
|
||||
)
|
||||
cell = JsonCell.objects.create(
|
||||
page=page,
|
||||
url='http://example.net',
|
||||
order=0,
|
||||
placeholder='content',
|
||||
template_string='XX{{ foo }}YY{{ bar_id }}ZZ',
|
||||
)
|
||||
|
||||
with mock.patch('combo.utils.requests.get') as requests_get:
|
||||
requests_get.return_value = mock.Mock(content='{}', status_code=200)
|
||||
resp = app.get('/page/')
|
||||
assert '<div>XXbarYY42ZZ</div>' in resp
|
||||
|
||||
with mock.patch('combo.utils.requests.get') as requests_get:
|
||||
requests_get.return_value = mock.Mock(content='{}', status_code=200)
|
||||
resp = app.get(
|
||||
reverse(
|
||||
'combo-public-ajax-page-cell',
|
||||
kwargs={'page_pk': page.pk, 'cell_reference': cell.get_reference()},
|
||||
)
|
||||
)
|
||||
assert resp.text.strip() == 'XXbarYY42ZZ'
|
||||
|
||||
# check sub_slug/extra_variables override
|
||||
page.sub_slug = '(?P<fooo>[a-z]+)'
|
||||
page.save()
|
||||
cell.template_string = 'XX{{ foo }}YY{{ bar_id }}ZZ{{ fooo }}AA'
|
||||
cell.save()
|
||||
with mock.patch('combo.utils.requests.get') as requests_get:
|
||||
requests_get.return_value = mock.Mock(content='{}', status_code=200)
|
||||
resp = app.get('/page/baz/')
|
||||
assert '<div>XXbarYY42ZZbazAA</div>' in resp
|
||||
|
||||
page.sub_slug = '(?P<foo>[a-z]+)'
|
||||
page.save()
|
||||
with mock.patch('combo.utils.requests.get') as requests_get:
|
||||
requests_get.return_value = mock.Mock(content='{}', status_code=200)
|
||||
resp = app.get('/page/baz/')
|
||||
assert '<div>XXbarYY42ZZAA</div>' in resp
|
||||
|
||||
|
||||
def test_cell_slugs(app):
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='Home', slug='index', template_name='standard')
|
||||
|
|
|
@ -3036,12 +3036,7 @@ def test_card_cell_render_identifier(mock_send, nocache, app):
|
|||
assert '/api/cards/card_model_1/list' in mock_send.call_args_list[1][0][0].url
|
||||
assert '/api/cards/card_model_1/13/' in mock_send.call_args_list[2][0][0].url
|
||||
|
||||
for card_ids in [
|
||||
'{% for card in cards|objects:"card_model_1" %}{{ card.id }},{% endfor %}',
|
||||
'{{ cards|objects:"card_model_1"|getlist:"id"|join:"," }}',
|
||||
]:
|
||||
cell.card_ids = card_ids
|
||||
cell.save()
|
||||
def test_card_ids():
|
||||
mock_send.reset_mock()
|
||||
resp = app.get(page.get_online_url())
|
||||
assert len(resp.context['cells']) == 3
|
||||
|
@ -3062,6 +3057,22 @@ def test_card_cell_render_identifier(mock_send, nocache, app):
|
|||
in mock_send.call_args_list[i * 2 + 2][0][0].url
|
||||
)
|
||||
|
||||
for card_ids in [
|
||||
'{% for card in cards|objects:"card_model_1" %}{{ card.id }},{% endfor %}',
|
||||
'{{ cards|objects:"card_model_1"|getlist:"id"|join:"," }}',
|
||||
]:
|
||||
cell.card_ids = card_ids
|
||||
cell.save()
|
||||
test_card_ids()
|
||||
|
||||
cell.card_ids = '{{ var1 }}'
|
||||
cell.save()
|
||||
page.extra_variables = {'var1': card_ids}
|
||||
page.save()
|
||||
test_card_ids()
|
||||
page.extra_variables = {}
|
||||
page.save()
|
||||
|
||||
# with a card_ids template, but result is empty
|
||||
cell.card_ids = '{{ foo }}'
|
||||
cell.save()
|
||||
|
|
Loading…
Reference in New Issue