Projet

Général

Profil

0001-data-add-page-parameters-59798.patch

Lauréline Guérin, 04 janvier 2022 10:53

Télécharger (17,1 ko)

Voir les différences:

Subject: [PATCH] data: add page parameters (#59798)

 combo/data/migrations/0052_page_parameters.py | 17 ++++++
 combo/data/models.py                          | 18 ++++++-
 combo/manager/forms.py                        | 11 ++++
 combo/manager/static/js/combo.manager.js      | 14 +++++
 .../templates/combo/page_parameters.html      | 36 +++++++++++++
 combo/manager/templates/combo/page_view.html  |  6 +++
 combo/manager/urls.py                         |  5 ++
 combo/manager/views.py                        | 33 +++++++++++-
 combo/public/views.py                         |  3 ++
 tests/test_manager.py                         | 53 +++++++++++++++++++
 tests/test_public.py                          | 50 ++++++++++++++++-
 tests/test_wcs.py                             | 23 +++++---
 12 files changed, 260 insertions(+), 9 deletions(-)
 create mode 100644 combo/data/migrations/0052_page_parameters.py
 create mode 100644 combo/manager/templates/combo/page_parameters.html
combo/data/migrations/0052_page_parameters.py
1
import django.contrib.postgres.fields.jsonb
2
from django.db import migrations
3

  
4

  
5
class Migration(migrations.Migration):
6

  
7
    dependencies = [
8
        ('data', '0051_link_cell_max_length'),
9
    ]
10

  
11
    operations = [
12
        migrations.AddField(
13
            model_name='page',
14
            name='parameters',
15
            field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
16
        ),
17
    ]
combo/data/models.py
45 45
from django.dispatch import receiver
46 46
from django.forms import models as model_forms
47 47
from django.forms.widgets import MediaDefiningClass
48
from django.template import TemplateDoesNotExist, TemplateSyntaxError, engines
48
from django.template import RequestContext, Template, TemplateDoesNotExist, TemplateSyntaxError, engines
49 49
from django.test.client import RequestFactory
50 50
from django.utils import timezone
51 51
from django.utils.encoding import force_text, python_2_unicode_compatible, smart_bytes
......
194 194
    order = models.PositiveIntegerField()
195 195
    exclude_from_navigation = models.BooleanField(_('Exclude from navigation'), default=True)
196 196
    redirect_url = models.CharField(_('Redirect URL'), max_length=200, blank=True)
197
    parameters = JSONField(blank=True, default=dict)
197 198

  
198 199
    public = models.BooleanField(_('Public'), default=True)
199 200
    groups = models.ManyToManyField(Group, verbose_name=_('Groups'), blank=True)
......
641 642
    def get_last_update_time(self):
642 643
        return self.last_update_timestamp
643 644

  
645
    def get_parameters(self, request, original_context):
646
        result = {}
647
        context = RequestContext(request)
648
        context.push(original_context)
649
        for key, tplt in (self.parameters or {}).items():
650
            try:
651
                template = Template(tplt)
652
            except TemplateSyntaxError:
653
                continue
654
            result[key] = template.render(context)
655
        return result
656

  
657
    def get_parameters_keys(self):
658
        return sorted((self.parameters or {}).keys())
659

  
644 660
    def is_new(self):
645 661
        return self.creation_timestamp > timezone.now() - datetime.timedelta(days=7)
646 662

  
combo/manager/forms.py
20 20
from django.conf import settings
21 21
from django.contrib.auth.models import Group
22 22
from django.core.exceptions import ValidationError
23
from django.forms import formset_factory
23 24
from django.template import Template, TemplateSyntaxError
24 25
from django.template.loader import TemplateDoesNotExist, get_template
25 26
from django.utils.translation import ugettext_lazy as _
......
168 169
        field_classes = {'picture': ImageIncludingSvgField}
169 170

  
170 171

  
172
class PageEditParameterForm(forms.Form):
173
    key = forms.CharField(label=_('Property name'), required=False)
174
    value = forms.CharField(
175
        label=_('Value template'), widget=forms.TextInput(attrs={'size': 60}), required=False
176
    )
177

  
178

  
179
PageEditParameterFormSet = formset_factory(PageEditParameterForm)
180

  
181

  
171 182
class PageVisibilityForm(forms.ModelForm):
172 183
    class Meta:
173 184
        model = Page
combo/manager/static/js/combo.manager.js
397 397
    window.location = $(this).parent('div').find('option:selected').data('add-url');
398 398
    return false;
399 399
  });
400

  
401
  $(document).on('click', '#add-page-property-form', function() {
402
    if (typeof property_forms === "undefined") {var property_forms = $('.page-property-form');}
403
    if (typeof total_forms === "undefined") {var total_form = $('#id_form-TOTAL_FORMS');}
404
    if (typeof form_num === "undefined") {var form_num = property_forms.length - 1;}
405
    var new_form = $(property_forms[0]).clone();
406
    var form_regex = RegExp(`form-(\\d){1}-`,'g');
407
    form_num++;
408
    new_form.html(new_form.html().replace(form_regex, `form-${form_num}-`));
409
    new_form.appendTo('#page-property-forms tbody');
410
    $('#id_form-' + form_num + '-key').val('');
411
    $('#id_form-' + form_num + '-value').val('');
412
    total_form.val(form_num + 1);
413
  })
400 414
});
401 415

  
402 416

  
combo/manager/templates/combo/page_parameters.html
1
{% extends "combo/page_add.html" %}
2
{% load i18n %}
3

  
4
{% block content %}
5
<form method="post" enctype="multipart/form-data">
6
  {% csrf_token %}
7
  <h3>{% trans "Page properties" %}</h3>
8
  {{ form.management_form }}
9
  <table id="page-property-forms">
10
      <thead>
11
          <tr>
12
              {% for field in form.0 %}
13
              <th class="column-{{ field.name }}{% if field.required %} required{% endif %}">{{ field.label }}</th>
14
              {% endfor %}
15
          </tr>
16
      </thead>
17
      <tbody>
18
      {% for sub_form in form %}
19
      <tr class='page-property-form'>
20
          {% for field in sub_form %}
21
          <td class="field-{{ field.name }}">
22
              {{ field.errors.as_ul }}
23
              {{ field }}
24
          </td>
25
          {% endfor %}
26
      </tr>
27
      {% endfor %}
28
      </tbody>
29
  </table>
30
  <button id="add-page-property-form" type="button">{% trans "Add another property" %}</button>
31
  <div class="buttons">
32
    <button class="submit-button">{% trans "Save" %}</button>
33
    <a class="cancel" href="{% url 'combo-manager-page-view' pk=object.pk %}">{% trans 'Cancel' %}</a>
34
  </div>
35
</form>
36
{% endblock %}
combo/manager/templates/combo/page_view.html
90 90
(<a rel="popup" href="{% url 'combo-manager-page-edit-picture' pk=object.id %}">{% trans 'change' %}</a>)
91 91
</p>
92 92

  
93
<p>
94
<label>{% trans 'Parameters:' %}</label>
95
{% for key in object.get_parameters_keys %}<i>{{ key }}</i>{% if not forloop.last %}, {% endif %}{% empty %}<i>{% trans 'none' context 'parameters' %}</i>{% endfor %}
96
(<a rel="popup" href="{% url 'combo-manager-page-edit-parameters' pk=object.id %}">{% trans 'change' %}</a>)
97
</p>
98

  
93 99
</div>
94 100

  
95 101
{% if object.parent_id or previous_page or next_page %}
combo/manager/urls.py
64 64
        views.page_remove_picture,
65 65
        name='combo-manager-page-remove-picture',
66 66
    ),
67
    url(
68
        r'^pages/(?P<pk>\d+)/parameters/$',
69
        views.page_edit_parameters,
70
        name='combo-manager-page-edit-parameters',
71
    ),
67 72
    url(r'^pages/(?P<pk>\d+)/delete$', staff_required(views.page_delete), name='combo-manager-page-delete'),
68 73
    url(r'^pages/(?P<pk>\d+)/export$', views.page_export, name='combo-manager-page-export'),
69 74
    url(
combo/manager/views.py
18 18
import hashlib
19 19
import json
20 20
import tarfile
21
from operator import attrgetter
21
from operator import attrgetter, itemgetter
22 22

  
23 23
from django.conf import settings
24 24
from django.contrib import messages
......
64 64
    PageDuplicateForm,
65 65
    PageEditDescriptionForm,
66 66
    PageEditIncludeInNavigationForm,
67
    PageEditParameterFormSet,
67 68
    PageEditPictureForm,
68 69
    PageEditRedirectionForm,
69 70
    PageEditRolesForm,
......
366 367
page_remove_picture = PageRemovePictureView.as_view()
367 368

  
368 369

  
370
class PageEditParametersView(PageEditView):
371
    form_class = PageEditParameterFormSet
372
    comment = _('changed parameters')
373
    template_name = 'combo/page_parameters.html'
374

  
375
    def get_initial(self):
376
        return sorted(
377
            ({'key': k, 'value': v} for k, v in self.get_object().parameters.items()), key=itemgetter('key')
378
        )
379

  
380
    def get_form_kwargs(self):
381
        kwargs = super().get_form_kwargs()
382
        kwargs.pop('instance')
383
        return kwargs
384

  
385
    def form_valid(self, form):
386
        self.object = self.get_object()
387
        self.object.parameters = {}
388
        for sub_data in form.cleaned_data:
389
            if not sub_data.get('key'):
390
                continue
391
            self.object.parameters[sub_data['key']] = sub_data['value']
392
        self.object.save()
393
        PageSnapshot.take(self.object, request=self.request, comment=self.comment)
394
        return HttpResponseRedirect(self.get_success_url())
395

  
396

  
397
page_edit_parameters = PageEditParametersView.as_view()
398

  
399

  
369 400
class PageView(ManagedPageMixin, DetailView):
370 401
    model = Page
371 402
    template_name = 'combo/page_view.html'
combo/public/views.py
102 102
            pass
103 103
    if 'name_id' in ctx:
104 104
        ctx['selected_user'] = get_user_from_name_id(ctx['name_id'])
105
    if 'page' in ctx:
106
        page = ctx['page']
107
        ctx.update(page.get_parameters(request, ctx))
105 108

  
106 109

  
107 110
@csrf_exempt
tests/test_manager.py
571 571
    assert Page.objects.get(id=page.id).picture.url in resp.text
572 572

  
573 573

  
574
def test_page_edit_parameters(app, admin_user):
575
    app = login(app)
576
    page = Page.objects.create(title='One', slug='one', template_name='standard')
577
    assert page.parameters == {}
578
    resp = app.get('/manage/pages/%s/' % page.id)
579
    assert resp.text.count('<i>none</i>') == 4
580
    resp = resp.click(href='.*/parameters/')
581
    resp.form['form-0-key'] = 'foo'
582
    resp.form['form-0-value'] = 'bar'
583
    resp = resp.form.submit().follow()
584
    page.refresh_from_db()
585
    assert page.parameters == {'foo': 'bar'}
586
    assert resp.text.count('<i>none</i>') == 3
587
    assert '<i>foo</i>' in resp
588

  
589
    resp = resp.click(href='.*/parameters/')
590
    assert resp.form['form-TOTAL_FORMS'].value == '2'
591
    assert resp.form['form-0-key'].value == 'foo'
592
    assert resp.form['form-0-value'].value == 'bar'
593
    assert resp.form['form-1-key'].value == ''
594
    assert resp.form['form-1-value'].value == ''
595
    resp.form['form-0-value'] = 'bar-bis'
596
    resp.form['form-1-key'] = 'blah'
597
    resp.form['form-1-value'] = 'baz'
598
    resp = resp.form.submit().follow()
599
    page.refresh_from_db()
600
    assert page.parameters == {
601
        'foo': 'bar-bis',
602
        'blah': 'baz',
603
    }
604
    assert resp.text.count('<i>none</i>') == 3
605
    assert '<i>blah</i>, <i>foo</i>' in resp
606

  
607
    resp = resp.click(href='.*/parameters/')
608
    assert resp.form['form-TOTAL_FORMS'].value == '3'
609
    assert resp.form['form-0-key'].value == 'blah'
610
    assert resp.form['form-0-value'].value == 'baz'
611
    assert resp.form['form-1-key'].value == 'foo'
612
    assert resp.form['form-1-value'].value == 'bar-bis'
613
    assert resp.form['form-2-key'].value == ''
614
    assert resp.form['form-2-value'].value == ''
615
    resp.form['form-1-key'] = 'foo'
616
    resp.form['form-1-value'] = 'bar'
617
    resp.form['form-0-key'] = ''
618
    resp = resp.form.submit().follow()
619
    page.refresh_from_db()
620
    assert page.parameters == {
621
        'foo': 'bar',
622
    }
623
    assert resp.text.count('<i>none</i>') == 3
624
    assert '<i>foo</i>' in resp
625

  
626

  
574 627
def test_page_placeholder_restricted_visibility(app, admin_user):
575 628
    app = login(app)
576 629

  
tests/test_public.py
577 577
        assert Page.objects.count() == 0
578 578

  
579 579

  
580
def test_page_async_cell(app):
580
def test_page_async_cell(app, nocache):
581 581
    Page.objects.all().delete()
582 582
    page = Page(title='Home', slug='index', template_name='standard')
583 583
    page.save()
......
1070 1070
        assert resp.context['card_foo_bar_id'] == '42'
1071 1071

  
1072 1072

  
1073
def test_page_parameters(app):
1074
    page = Page.objects.create(
1075
        title='Home',
1076
        slug='page',
1077
        template_name='standard',
1078
        parameters={'foo': 'bar', 'bar_id': '{{ 40|add:2 }}'},
1079
    )
1080
    cell = JsonCell.objects.create(
1081
        page=page,
1082
        url='http://example.net',
1083
        order=0,
1084
        placeholder='content',
1085
        template_string='XX{{ foo }}YY{{ bar_id }}ZZ',
1086
    )
1087

  
1088
    with mock.patch('combo.utils.requests.get') as requests_get:
1089
        requests_get.return_value = mock.Mock(content='{}', status_code=200)
1090
        resp = app.get('/page/')
1091
    assert '<div>XXbarYY42ZZ</div>' in resp
1092

  
1093
    with mock.patch('combo.utils.requests.get') as requests_get:
1094
        requests_get.return_value = mock.Mock(content='{}', status_code=200)
1095
        resp = app.get(
1096
            reverse(
1097
                'combo-public-ajax-page-cell',
1098
                kwargs={'page_pk': page.pk, 'cell_reference': cell.get_reference()},
1099
            )
1100
        )
1101
    assert resp.text.strip() == 'XXbarYY42ZZ'
1102

  
1103
    # check sub_slug/parameters override
1104
    page.sub_slug = '(?P<fooo>[a-z]+)'
1105
    page.save()
1106
    cell.template_string = 'XX{{ foo }}YY{{ bar_id }}ZZ{{ fooo }}AA'
1107
    cell.save()
1108
    with mock.patch('combo.utils.requests.get') as requests_get:
1109
        requests_get.return_value = mock.Mock(content='{}', status_code=200)
1110
        resp = app.get('/page/baz/')
1111
    assert '<div>XXbarYY42ZZbazAA</div>' in resp
1112

  
1113
    page.sub_slug = '(?P<foo>[a-z]+)'
1114
    page.save()
1115
    with mock.patch('combo.utils.requests.get') as requests_get:
1116
        requests_get.return_value = mock.Mock(content='{}', status_code=200)
1117
        resp = app.get('/page/baz/')
1118
    assert '<div>XXbarYY42ZZAA</div>' in resp
1119

  
1120

  
1073 1121
def test_cell_slugs(app):
1074 1122
    Page.objects.all().delete()
1075 1123
    page = Page(title='Home', slug='index', template_name='standard')
tests/test_wcs.py
2183 2183
    assert '/api/cards/card_model_1/list' in mock_send.call_args_list[1][0][0].url
2184 2184
    assert '/api/cards/card_model_1/13/' in mock_send.call_args_list[2][0][0].url
2185 2185

  
2186
    for card_ids in [
2187
        '{% for card in cards|objects:"card_model_1" %}{{ card.id }},{% endfor %}',
2188
        '{{ cards|objects:"card_model_1"|getlist:"id"|join:"," }}',
2189
    ]:
2190
        cell.card_ids = card_ids
2191
        cell.save()
2186
    def test_card_ids():
2192 2187
        mock_send.reset_mock()
2193 2188
        resp = app.get(page.get_online_url())
2194 2189
        assert len(resp.context['cells']) == 3
......
2209 2204
                in mock_send.call_args_list[i * 2 + 2][0][0].url
2210 2205
            )
2211 2206

  
2207
    for card_ids in [
2208
        '{% for card in cards|objects:"card_model_1" %}{{ card.id }},{% endfor %}',
2209
        '{{ cards|objects:"card_model_1"|getlist:"id"|join:"," }}',
2210
    ]:
2211
        cell.card_ids = card_ids
2212
        cell.save()
2213
        test_card_ids()
2214

  
2215
        cell.card_ids = '{{ var1 }}'
2216
        cell.save()
2217
        page.parameters = {'var1': card_ids}
2218
        page.save()
2219
        test_card_ids()
2220
        page.parameters = {}
2221
        page.save()
2222

  
2212 2223
    # with a card_ids template, but result is empty
2213 2224
    cell.card_ids = '{{ foo }}'
2214 2225
    cell.save()
2215
-