Projet

Général

Profil

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

Frédéric Péters, 04 février 2022 08:43

Télécharger (17,1 ko)

Voir les différences:

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

 combo/data/migrations/0053_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/0053_page_parameters.py
 create mode 100644 combo/manager/templates/combo/page_parameters.html
combo/data/migrations/0053_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', '0052_auto_20211110_1521'),
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
414 414
  }
415 415
  prepare_dynamic_fields();
416 416
  $(document).on('combo:dialog-loaded', prepare_dynamic_fields);
417

  
418
  $(document).on('click', '#add-page-property-form', function() {
419
    if (typeof property_forms === "undefined") {var property_forms = $('.page-property-form');}
420
    if (typeof total_forms === "undefined") {var total_form = $('#id_form-TOTAL_FORMS');}
421
    if (typeof form_num === "undefined") {var form_num = property_forms.length - 1;}
422
    var new_form = $(property_forms[0]).clone();
423
    var form_regex = RegExp(`form-(\\d){1}-`,'g');
424
    form_num++;
425
    new_form.html(new_form.html().replace(form_regex, `form-${form_num}-`));
426
    new_form.appendTo('#page-property-forms tbody');
427
    $('#id_form-' + form_num + '-key').val('');
428
    $('#id_form-' + form_num + '-value').val('');
429
    total_form.val(form_num + 1);
430
  })
417 431
});
418 432

  
419 433

  
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()
......
1093 1093
        assert resp.context['card_foo_bar_id'] == '42'
1094 1094

  
1095 1095

  
1096
def test_page_parameters(app):
1097
    page = Page.objects.create(
1098
        title='Home',
1099
        slug='page',
1100
        template_name='standard',
1101
        parameters={'foo': 'bar', 'bar_id': '{{ 40|add:2 }}'},
1102
    )
1103
    cell = JsonCell.objects.create(
1104
        page=page,
1105
        url='http://example.net',
1106
        order=0,
1107
        placeholder='content',
1108
        template_string='XX{{ foo }}YY{{ bar_id }}ZZ',
1109
    )
1110

  
1111
    with mock.patch('combo.utils.requests.get') as requests_get:
1112
        requests_get.return_value = mock.Mock(content='{}', status_code=200)
1113
        resp = app.get('/page/')
1114
    assert '<div>XXbarYY42ZZ</div>' in resp
1115

  
1116
    with mock.patch('combo.utils.requests.get') as requests_get:
1117
        requests_get.return_value = mock.Mock(content='{}', status_code=200)
1118
        resp = app.get(
1119
            reverse(
1120
                'combo-public-ajax-page-cell',
1121
                kwargs={'page_pk': page.pk, 'cell_reference': cell.get_reference()},
1122
            )
1123
        )
1124
    assert resp.text.strip() == 'XXbarYY42ZZ'
1125

  
1126
    # check sub_slug/parameters override
1127
    page.sub_slug = '(?P<fooo>[a-z]+)'
1128
    page.save()
1129
    cell.template_string = 'XX{{ foo }}YY{{ bar_id }}ZZ{{ fooo }}AA'
1130
    cell.save()
1131
    with mock.patch('combo.utils.requests.get') as requests_get:
1132
        requests_get.return_value = mock.Mock(content='{}', status_code=200)
1133
        resp = app.get('/page/baz/')
1134
    assert '<div>XXbarYY42ZZbazAA</div>' in resp
1135

  
1136
    page.sub_slug = '(?P<foo>[a-z]+)'
1137
    page.save()
1138
    with mock.patch('combo.utils.requests.get') as requests_get:
1139
        requests_get.return_value = mock.Mock(content='{}', status_code=200)
1140
        resp = app.get('/page/baz/')
1141
    assert '<div>XXbarYY42ZZAA</div>' in resp
1142

  
1143

  
1096 1144
def test_cell_slugs(app):
1097 1145
    Page.objects.all().delete()
1098 1146
    page = Page(title='Home', slug='index', template_name='standard')
tests/test_wcs.py
3036 3036
    assert '/api/cards/card_model_1/list' in mock_send.call_args_list[1][0][0].url
3037 3037
    assert '/api/cards/card_model_1/13/' in mock_send.call_args_list[2][0][0].url
3038 3038

  
3039
    for card_ids in [
3040
        '{% for card in cards|objects:"card_model_1" %}{{ card.id }},{% endfor %}',
3041
        '{{ cards|objects:"card_model_1"|getlist:"id"|join:"," }}',
3042
    ]:
3043
        cell.card_ids = card_ids
3044
        cell.save()
3039
    def test_card_ids():
3045 3040
        mock_send.reset_mock()
3046 3041
        resp = app.get(page.get_online_url())
3047 3042
        assert len(resp.context['cells']) == 3
......
3062 3057
                in mock_send.call_args_list[i * 2 + 2][0][0].url
3063 3058
            )
3064 3059

  
3060
    for card_ids in [
3061
        '{% for card in cards|objects:"card_model_1" %}{{ card.id }},{% endfor %}',
3062
        '{{ cards|objects:"card_model_1"|getlist:"id"|join:"," }}',
3063
    ]:
3064
        cell.card_ids = card_ids
3065
        cell.save()
3066
        test_card_ids()
3067

  
3068
        cell.card_ids = '{{ var1 }}'
3069
        cell.save()
3070
        page.parameters = {'var1': card_ids}
3071
        page.save()
3072
        test_card_ids()
3073
        page.parameters = {}
3074
        page.save()
3075

  
3065 3076
    # with a card_ids template, but result is empty
3066 3077
    cell.card_ids = '{{ foo }}'
3067 3078
    cell.save()
3068
-