general: add possibily to layout placeholder cells in flex grid (#62072)

This commit is contained in:
Frédéric Péters 2022-02-22 14:20:35 +01:00
parent 9dba7d1245
commit 5b19cf1113
12 changed files with 198 additions and 5 deletions

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.21 on 2022-02-22 14:21
import django.contrib.postgres.fields.jsonb
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('data', '0054_link_cell_bypass_validity_check'),
]
operations = [
migrations.AddField(
model_name='page',
name='placeholder_options',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
),
]

View File

@ -194,6 +194,7 @@ class Page(models.Model):
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)
placeholder_options = JSONField(blank=True, default=dict)
public = models.BooleanField(_('Public'), default=True)
groups = models.ManyToManyField(Group, verbose_name=_('Groups'), blank=True)
@ -879,6 +880,10 @@ class CellBase(models.Model, metaclass=CellMeta):
]
)
@property
def cleaned_extra_css_class(self):
return ' '.join([x for x in self.extra_css_class.split() if not x.startswith('size--')])
@property
def asset_css_classes(self):
from combo.apps.assets.models import Asset
@ -1154,7 +1159,49 @@ class CellBase(models.Model, metaclass=CellMeta):
(k, v['label']) for k, v in extra_templates.items()
]
widgets = {'template_name': forms.Select(choices=template_names)}
return model_forms.modelform_factory(self.__class__, fields=fields, widgets=widgets)
page = self.page
cell = self
class OptionsForm(model_forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if page.placeholder_options.get(cell.placeholder, {}).get('fx_grid_layout'):
# add a size field that takes/stores its value in the extra_css_class
# char field.
self.fields['fx_size'] = forms.ChoiceField(
label=_('Size'),
choices=[
('', ''),
('size--1-1', '1/1'),
('size--t1-2', '1/2'),
('size--t1-3', '1/3'),
],
required=False,
)
# move extra_css_class field to be last
field_order = list(self.fields.keys())
field_order.remove('extra_css_class')
field_order.append('extra_css_class')
self.order_fields(field_order)
extra_css_class = self.initial['extra_css_class'].split()
for css_class, _dummy in self.fields['fx_size'].choices:
if css_class in extra_css_class:
extra_css_class.remove(css_class)
self.initial['extra_css_class'] = ' '.join(extra_css_class)
self.initial['fx_size'] = css_class
break
def save(self, *args, **kwargs):
if self.cleaned_data.get('fx_size'):
self.instance.extra_css_class = (
'%s %s'
% (self.instance.extra_css_class or '', self.cleaned_data.get('fx_size') or '')
).strip()
return super().save(*args, **kwargs)
return model_forms.modelform_factory(self.__class__, form=OptionsForm, fields=fields, widgets=widgets)
def get_extra_manager_context(self):
return {}

View File

@ -360,3 +360,16 @@ class SiteSettingsForm(forms.ModelForm):
self.fields['initial_login_page'].widget.template_name = 'combo/widgets/select_with_other_option.html'
self.fields['initial_login_page'].widget.attrs['class'] = 'page-selector'
self.fields['welcome_page'].queryset = self.fields['welcome_page'].queryset.filter(public=True)
class PlaceholderOptionsForm(forms.Form):
fx_grid_layout = forms.ChoiceField(
label=_('Grid Layout'),
required=False,
choices=[
('', _('Manual')),
('fx-grid', _('1 column')),
('fx-grid--t2', _('2 columns')),
('fx-grid--t3', _('3 columns')),
],
)

View File

@ -30,9 +30,16 @@ div.page-options span.subslug {
}
div.placeholder {
position: relative;
margin-bottom: 2em;
}
.placeholder-options-link {
position: absolute;
top: 1em;
right: 1em;
}
div#pages-list div.page {
display: flex;
align-items: center;

View File

@ -156,6 +156,7 @@
{% for placeholder in placeholders %}
<div class="placeholder" data-placeholder-key="{{ placeholder.key }}">
<h2>{{ placeholder.name }}</h2>
<a class="placeholder-options-link" data-popup href="{% url 'combo-manage-placeholder-options' page_pk=object.id placeholder=placeholder.key %}">{% trans "Options" %}</a>
<div class="cell-list">
{% for cell in placeholder.cells %}
<div id="cell-{{cell.get_reference}}" class="cell {{cell.class_name}}" data-cell-reference="{{ cell.get_reference }}">
@ -165,7 +166,7 @@
<span class="cell-template-label"
>{% if cell.template_name %}({{cell.get_template_label}}){% endif %}</span>
<span class="cell-slug">{% if cell.slug %}[{{cell.slug}}]{% endif %}</span>
<span class="extra-css-class">{% if cell.extra_css_class %}[{{ cell.extra_css_class }}]{% endif %}</span>
<span class="extra-css-class">{% if cell.cleaned_extra_css_class %}[{{ cell.cleaned_extra_css_class }}]{% endif %}</span>
<span class="additional-label"><i>{{cell.get_additional_label|default_if_none:""}}</i></span>
{% if cell.get_invalid_reason %}
<span class="invalid">{{ cell.get_invalid_reason }}{% if cell.class_name != 'link-list-cell' %} -

View File

@ -0,0 +1,18 @@
{% extends "combo/manager_base.html" %}
{% load i18n %}
{% block appbar %}
<h2>{% trans "Options" %}</h2>
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="buttons">
<button class="submit-button">{% trans "Submit" %}</button>
<a class="cancel" href="{% url 'combo-manager-page-view' pk=page.pk %}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -137,6 +137,11 @@ urlpatterns = [
name='combo-manager-link-list-order',
),
url(r'^pages/(?P<page_pk>\d+)/order$', views.cell_order, name='combo-manager-cell-order'),
url(
r'^pages/(?P<page_pk>\d+)/placeholder/(?P<placeholder>[\w_-]+)/options$',
views.placeholder_options,
name='combo-manage-placeholder-options',
),
url(r'^pages/order$', views.page_order, name='combo-manager-page-order'),
url(
r'^pages/(?P<page_path>[\w/_-]+)/$',

View File

@ -77,6 +77,7 @@ from .forms import (
PageRestrictedAddForm,
PageSelectTemplateForm,
PageVisibilityForm,
PlaceholderOptionsForm,
SiteExportForm,
SiteImportForm,
SiteSettingsForm,
@ -738,7 +739,7 @@ class PageEditCellView(ManagedPageMixin, UpdateView):
response['visibility_css_class'] = self.object.get_manager_visibility_css_class()
response['visibility_content'] = self.object.get_manager_visibility_content()
response['extra_css_class'] = self.object.extra_css_class
response['extra_css_class'] = self.object.cleaned_extra_css_class
response['slug'] = self.object.slug
response['template_label'] = self.object.get_template_label()
response['additional_label'] = self.object.get_additional_label()
@ -1110,3 +1111,30 @@ class SiteSettingsView(UpdateView):
site_settings = SiteSettingsView.as_view()
class PlaceholderOptionsView(ManagedPageMixin, FormView):
form_class = PlaceholderOptionsForm
template_name = 'combo/placeholder_options.html'
def get_object(self):
return get_object_or_404(Page, pk=self.kwargs['page_pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page'] = self.get_object()
return context
def get_initial(self):
return self.page.placeholder_options.get(self.kwargs['placeholder'])
def form_valid(self, form):
self.page.placeholder_options[self.kwargs['placeholder']] = form.cleaned_data
self.page.save()
return super().form_valid(form)
def get_success_url(self):
return reverse('combo-manager-page-view', kwargs={'pk': self.page.id})
placeholder_options = PlaceholderOptionsView.as_view()

View File

@ -1,5 +1,6 @@
{% load i18n %}
{% if render %}
{% if placeholder_options.fx_grid_layout %}<div class="combo-placeholder {{ placeholder_options.fx_grid_layout }}">{% endif %}
{% if render_skeleton %}
{{ skeleton }}
{% endif %}
@ -15,4 +16,5 @@
{% endwith %}
><div>{% render_cell cell %}</div></div>
{% endfor %}
{% if placeholder_options.fx_grid_layout %}</div>{% endif %}
{% endif %}

View File

@ -67,6 +67,8 @@ def skeleton_text(context, placeholder_name, content=''):
@register.inclusion_tag('combo/placeholder.html', takes_context=True)
def placeholder(context, placeholder_name, **options):
placeholder = Placeholder(key=placeholder_name, cell=context.get('cell'), **options)
if 'page' in context:
context['placeholder_options'] = context['page'].placeholder_options.get(placeholder_name) or {}
# make sure render_skeleton is available in context
context['render_skeleton'] = context.get('render_skeleton')
if context.get('placeholder_search_mode'):

View File

@ -701,7 +701,7 @@ def test_edit_page_num_queries(settings, app, admin_user):
app.get('/manage/pages/%s/' % page.pk) # load once to populate caches
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/pages/%s/' % page.pk)
assert len(ctx.captured_queries) == 41
assert len(ctx.captured_queries) == 44
def test_delete_page(app, admin_user):
@ -2456,7 +2456,7 @@ def test_page_versionning(app, admin_user):
resp = resp.click('restore', index=6)
with CaptureQueriesContext(connection) as ctx:
resp = resp.form.submit().follow()
assert len(ctx.captured_queries) == 152
assert len(ctx.captured_queries) == 155
resp2 = resp.click('See online')
assert resp2.text.index('Foobar1') < resp2.text.index('Foobar2') < resp2.text.index('Foobar3')
@ -2858,3 +2858,35 @@ def test_site_settings(app, admin_user):
site_settings.refresh_from_db()
assert site_settings.welcome_page == public_page
assert site_settings.initial_login_page == private_page
def test_manager_placeholder_grid(app, admin_user):
page = Page.objects.create(title='Page', slug='page')
cell = TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1)
app = login(app)
resp = app.get('/manage/pages/%s/' % page.id)
assert 'fx_size' not in resp.forms[0].fields
resp = app.get('/manage/pages/%s/' % page.id)
resp = resp.click('Options', href=r'placeholder.*options')
resp.form['fx_grid_layout'].select(text='2 columns')
resp = resp.form.submit().follow()
page.refresh_from_db()
assert page.placeholder_options['content']['fx_grid_layout'] == 'fx-grid--t2'
resp = resp.click('Options', href=r'placeholder.*options')
assert resp.form['fx_grid_layout'].value == 'fx-grid--t2'
resp = app.get('/manage/pages/%s/' % page.id)
assert 'fx_size' not in resp.forms[0].fields
resp.forms[0]['cdata_textcell-%s-fx_size' % cell.id].select('size--t1-2')
manager_submit_cell(resp.forms[0])
cell.refresh_from_db()
assert cell.extra_css_class == 'size--t1-2'
assert resp.forms[0]['cdata_textcell-%s-fx_size' % cell.id].value == 'size--t1-2'
assert not resp.forms[0]['cdata_textcell-%s-extra_css_class' % cell.id].value
resp.forms[0]['cdata_textcell-%s-extra_css_class' % cell.id] = 'plop'
resp_sub = manager_submit_cell(resp.forms[0])
assert resp_sub.json['extra_css_class'] == 'plop'
cell.refresh_from_db()
assert set(cell.extra_css_class.split()) == {'plop', 'size--t1-2'}

View File

@ -1225,3 +1225,22 @@ def test_cell_asset_css_classes(settings, app, admin_user):
assert cell.asset_css_classes == ''
resp = app.get('/', status=200)
assert 'class="cell text-cell textcell foo"' in re.sub(r' +', ' ', resp.text)
def test_placeholder_grid(app):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1)
resp = app.get('/', status=200)
# no placeholder div if there's no grid layout
assert resp.pyquery('#content > .text-cell')
page.placeholder_options = {'content': {'fx_grid_layout': 'fx-grid'}}
page.save()
resp = app.get('/', status=200)
# placeholder div in between
assert resp.pyquery('#content > .fx-grid > .text-cell')