data: add page parameters (#59798)
gitea-wip/combo/pipeline/head There was a failure building this commit Details
gitea/combo/pipeline/head Something is wrong with the build of this commit Details

This commit is contained in:
Lauréline Guérin 2022-01-03 16:02:21 +01:00
parent 941ae66e2a
commit 757cd93c76
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
12 changed files with 262 additions and 9 deletions

View File

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

View File

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

View File

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

View File

@ -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);
})
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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