Project

General

Profile

0001-general-add-possibily-to-layout-placeholder-cells-in.patch

Frédéric Péters, 25 February 2022 09:50 AM

Download (14.9 KB)

View differences:

Subject: [PATCH 1/3] general: add possibily to layout placeholder cells in
 flex grid (#62072)

 .../0054_page_placeholder_options.py          | 19 ++++++++
 combo/data/models.py                          | 45 ++++++++++++++++++-
 combo/manager/forms.py                        | 14 ++++++
 combo/manager/static/css/combo.manager.css    |  7 +++
 combo/manager/templates/combo/page_view.html  |  5 ++-
 .../templates/combo/placeholder_options.html  | 18 ++++++++
 combo/manager/urls.py                         |  5 +++
 combo/manager/views.py                        | 28 ++++++++++++
 combo/public/templates/combo/placeholder.html |  2 +
 combo/public/templatetags/combo.py            |  2 +
 tests/test_manager.py                         | 33 ++++++++++++++
 tests/test_public.py                          | 19 ++++++++
 12 files changed, 194 insertions(+), 3 deletions(-)
 create mode 100644 combo/data/migrations/0054_page_placeholder_options.py
 create mode 100644 combo/manager/templates/combo/placeholder_options.html
combo/data/migrations/0054_page_placeholder_options.py
1
# Generated by Django 2.2.21 on 2022-02-22 14:21
2

  
3
import django.contrib.postgres.fields.jsonb
4
from django.db import migrations
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('data', '0053_page_variables'),
11
    ]
12

  
13
    operations = [
14
        migrations.AddField(
15
            model_name='page',
16
            name='placeholder_options',
17
            field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
18
        ),
19
    ]
combo/data/models.py
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 197
    extra_variables = JSONField(blank=True, default=dict)
198
    placeholder_options = JSONField(blank=True, default=dict)
198 199

  
199 200
    public = models.BooleanField(_('Public'), default=True)
200 201
    groups = models.ManyToManyField(Group, verbose_name=_('Groups'), blank=True)
......
863 864
            ]
864 865
        )
865 866

  
867
    @property
868
    def cleaned_extra_css_class(self):
869
        return ' '.join([x for x in self.extra_css_class.split() if not x.startswith('size--')])
870

  
866 871
    @property
867 872
    def asset_css_classes(self):
868 873
        from combo.apps.assets.models import Asset
......
1105 1110
                (k, v['label']) for k, v in extra_templates.items()
1106 1111
            ]
1107 1112
            widgets = {'template_name': forms.Select(choices=template_names)}
1108
        return model_forms.modelform_factory(self.__class__, fields=fields, widgets=widgets)
1113

  
1114
        page = self.page
1115
        cell = self
1116

  
1117
        class OptionsForm(model_forms.ModelForm):
1118
            def __init__(self, *args, **kwargs):
1119
                super().__init__(*args, **kwargs)
1120
                if page.placeholder_options.get(cell.placeholder, {}).get('fx_grid_layout'):
1121
                    # add a size field that takes/stores its value in the extra_css_class
1122
                    # char field.
1123
                    self.fields['fx_size'] = forms.ChoiceField(
1124
                        label=_('Size'),
1125
                        choices=[
1126
                            ('', ''),
1127
                            ('size--1-1', '1/1'),
1128
                            ('size--t1-2', '1/2'),
1129
                            ('size--t1-3', '1/3'),
1130
                        ],
1131
                        required=False,
1132
                    )
1133
                    self.fields.move_to_end('extra_css_class')
1134
                    extra_css_class = self.initial['extra_css_class'].split()
1135
                    for css_class, _dummy in self.fields['fx_size'].choices:
1136
                        if css_class in extra_css_class:
1137
                            extra_css_class.remove(css_class)
1138
                            self.initial['extra_css_class'] = ' '.join(extra_css_class)
1139
                            self.initial['fx_size'] = css_class
1140
                            break
1141

  
1142
            def save(self, *args, **kwargs):
1143
                if self.cleaned_data.get('fx_size'):
1144
                    self.instance.extra_css_class = (
1145
                        '%s %s'
1146
                        % (self.instance.extra_css_class or '', self.cleaned_data.get('fx_size') or '')
1147
                    ).strip()
1148

  
1149
                return super().save(*args, **kwargs)
1150

  
1151
        return model_forms.modelform_factory(self.__class__, form=OptionsForm, fields=fields, widgets=widgets)
1109 1152

  
1110 1153
    def get_extra_manager_context(self):
1111 1154
        return {}
combo/manager/forms.py
345 345
        self.fields['initial_login_page'].widget.template_name = 'combo/widgets/select_with_other_option.html'
346 346
        self.fields['initial_login_page'].widget.attrs['class'] = 'page-selector'
347 347
        self.fields['welcome_page'].queryset = self.fields['welcome_page'].queryset.filter(public=True)
348

  
349

  
350
class PlaceholderOptionsForm(forms.Form):
351
    fx_grid_layout = forms.ChoiceField(
352
        label=_('Grid Layout'),
353
        required=False,
354
        choices=[
355
            ('', _('None')),
356
            ('fx-grid--auto', _('Automatic')),
357
            ('fx-grid', _('1 column')),
358
            ('fx-grid--t2', _('2 columns')),
359
            ('fx-grid--t3', _('3 columns')),
360
        ],
361
    )
combo/manager/static/css/combo.manager.css
17 17
}
18 18

  
19 19
div.placeholder {
20
	position: relative;
20 21
	margin-bottom: 2em;
21 22
}
22 23

  
24
.placeholder-options-link {
25
	position: absolute;
26
	top: 1em;
27
	right: 1em;
28
}
29

  
23 30
div#pages-list div.page {
24 31
	display: flex;
25 32
	align-items: center;
combo/manager/templates/combo/page_view.html
156 156
{% for placeholder in placeholders %}
157 157
  <div class="placeholder" data-placeholder-key="{{ placeholder.key }}">
158 158
    <h2>{{ placeholder.name }}</h2>
159
    <a class="placeholder-options-link" data-popup href="{% url 'combo-manage-placeholder-options' page_pk=object.id placeholder=placeholder.key %}">{% trans "Options" %}</a>
159 160
    <div class="cell-list">
160 161
      {% for cell in placeholder.cells %}
161 162
      <div id="cell-{{cell.get_reference}}" class="cell {{cell.class_name}}" data-cell-reference="{{ cell.get_reference }}">
......
164 165
                        {{ cell.get_label }}
165 166
                        {% if cell.template_name %} ({{cell.get_template_label}}){% endif %}
166 167
                        {% if cell.slug %} [{{cell.slug}}] {% endif %}
167
                  {% if cell.extra_css_class %}
168
                  <span class="extra-css-class">[{{ cell.extra_css_class }}]</span>
168
                  {% if cell.cleaned_extra_css_class %}
169
                  <span class="extra-css-class">[{{ cell.cleaned_extra_css_class }}]</span>
169 170
                  {% endif %}
170 171
                  <span class="additional-label"><i>{{cell.get_additional_label|default_if_none:""}}</i></span>
171 172
                  {% if cell.get_invalid_reason %}
combo/manager/templates/combo/placeholder_options.html
1
{% extends "combo/manager_base.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
<h2>{% trans "Options" %}</h2>
6
{% endblock %}
7

  
8
{% block content %}
9

  
10
<form method="post">
11
  {% csrf_token %}
12
  {{ form.as_p }}
13
  <div class="buttons">
14
    <button class="submit-button">{% trans "Submit" %}</button>
15
    <a class="cancel" href="{% url 'combo-manager-page-view' pk=page.pk %}">{% trans 'Cancel' %}</a>
16
  </div>
17
</form>
18
{% endblock %}
combo/manager/urls.py
146 146
        name='combo-manager-link-list-order',
147 147
    ),
148 148
    url(r'^pages/(?P<page_pk>\d+)/order$', views.cell_order, name='combo-manager-cell-order'),
149
    url(
150
        r'^pages/(?P<page_pk>\d+)/placeholder/(?P<placeholder>[\w_-]+)/options$',
151
        views.placeholder_options,
152
        name='combo-manage-placeholder-options',
153
    ),
149 154
    url(r'^pages/order$', views.page_order, name='combo-manager-page-order'),
150 155
    url(
151 156
        r'^pages/(?P<page_path>[\w/_-]+)/$',
combo/manager/views.py
74 74
    PageRestrictedAddForm,
75 75
    PageSelectTemplateForm,
76 76
    PageVisibilityForm,
77
    PlaceholderOptionsForm,
77 78
    SiteExportForm,
78 79
    SiteImportForm,
79 80
    SiteSettingsForm,
......
1055 1056

  
1056 1057

  
1057 1058
site_settings = SiteSettingsView.as_view()
1059

  
1060

  
1061
class PlaceholderOptionsView(ManagedPageMixin, FormView):
1062
    form_class = PlaceholderOptionsForm
1063
    template_name = 'combo/placeholder_options.html'
1064

  
1065
    def get_object(self):
1066
        return get_object_or_404(Page, pk=self.kwargs['page_pk'])
1067

  
1068
    def get_context_data(self, **kwargs):
1069
        context = super().get_context_data(**kwargs)
1070
        context['page'] = self.get_object()
1071
        return context
1072

  
1073
    def get_initial(self):
1074
        return self.page.placeholder_options.get(self.kwargs['placeholder'])
1075

  
1076
    def form_valid(self, form):
1077
        self.page.placeholder_options[self.kwargs['placeholder']] = form.cleaned_data
1078
        self.page.save()
1079
        return super().form_valid(form)
1080

  
1081
    def get_success_url(self):
1082
        return reverse('combo-manager-page-view', kwargs={'pk': self.page.id})
1083

  
1084

  
1085
placeholder_options = PlaceholderOptionsView.as_view()
combo/public/templates/combo/placeholder.html
1 1
{% load i18n %}
2 2
{% if render %}
3
{% if placeholder_options.fx_grid_layout %}<div class="{{ placeholder_options.fx_grid_layout }}">{% endif %}
3 4
{% if render_skeleton %}
4 5
{{ skeleton }}
5 6
{% endif %}
......
15 16
     {% endwith %}
16 17
     ><div>{% render_cell cell %}</div></div>
17 18
{% endfor %}
19
{% if placeholder_options.fx_grid_layout %}</div>{% endif %}
18 20
{% endif %}
combo/public/templatetags/combo.py
66 66
@register.inclusion_tag('combo/placeholder.html', takes_context=True)
67 67
def placeholder(context, placeholder_name, **options):
68 68
    placeholder = Placeholder(key=placeholder_name, cell=context.get('cell'), **options)
69
    if 'page' in context:
70
        context['placeholder_options'] = context['page'].placeholder_options.get(placeholder_name) or {}
69 71
    # make sure render_skeleton is available in context
70 72
    context['render_skeleton'] = context.get('render_skeleton')
71 73
    if context.get('placeholder_search_mode'):
tests/test_manager.py
2780 2780
    site_settings.refresh_from_db()
2781 2781
    assert site_settings.welcome_page == public_page
2782 2782
    assert site_settings.initial_login_page == private_page
2783

  
2784

  
2785
def test_manager_placeholder_grid(app, admin_user):
2786
    page = Page.objects.create(title='Page', slug='page')
2787
    cell = TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1)
2788
    app = login(app)
2789
    resp = app.get('/manage/pages/%s/' % page.id)
2790
    resp = resp.click('Options', href=r'cell.*options')
2791
    assert 'fx_size' not in resp.form.fields
2792
    resp = app.get('/manage/pages/%s/' % page.id)
2793
    resp = resp.click('Options', href=r'placeholder.*options')
2794
    resp.form['fx_grid_layout'].select(text='Automatic')
2795
    resp = resp.form.submit().follow()
2796

  
2797
    page.refresh_from_db()
2798
    assert page.placeholder_options['content']['fx_grid_layout'] == 'fx-grid--auto'
2799
    resp = resp.click('Options', href=r'placeholder.*options')
2800
    assert resp.form['fx_grid_layout'].value == 'fx-grid--auto'
2801

  
2802
    resp = app.get('/manage/pages/%s/' % page.id)
2803
    resp = resp.click('Options', href=r'cell.*options')
2804
    resp.form['cdata_textcell-%s-fx_size' % cell.id].select(text='1/2')
2805
    resp = resp.form.submit().follow()
2806
    cell.refresh_from_db()
2807
    assert cell.extra_css_class == 'size--t1-2'
2808

  
2809
    resp = resp.click('Options', href=r'cell.*options')
2810
    assert resp.form['cdata_textcell-%s-fx_size' % cell.id].value == 'size--t1-2'
2811
    assert not resp.form['cdata_textcell-%s-extra_css_class' % cell.id].value
2812
    resp.form['cdata_textcell-%s-extra_css_class' % cell.id] = 'plop'
2813
    resp = resp.form.submit().follow()
2814
    cell.refresh_from_db()
2815
    assert set(cell.extra_css_class.split()) == {'plop', 'size--t1-2'}
tests/test_public.py
1213 1213
    assert cell.asset_css_classes == ''
1214 1214
    resp = app.get('/', status=200)
1215 1215
    assert 'class="cell text-cell textcell foo"' in re.sub(r' +', ' ', resp.text)
1216

  
1217

  
1218
def test_placeholder_grid(app):
1219
    Page.objects.all().delete()
1220
    page = Page(title='Home', slug='index', template_name='standard')
1221
    page.save()
1222

  
1223
    TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1)
1224

  
1225
    resp = app.get('/', status=200)
1226
    # no placeholder div if there's no grid layout
1227
    assert resp.pyquery('#content > .text-cell')
1228

  
1229
    page.placeholder_options = {'content': {'fx_grid_layout': 'fx-grid--auto'}}
1230
    page.save()
1231

  
1232
    resp = app.get('/', status=200)
1233
    # placeholder div in between
1234
    assert resp.pyquery('#content > .fx-grid--auto > .text-cell')
1216
-