0001-general-add-possibily-to-layout-placeholder-cells-in.patch
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 | ||
---|---|---|
360 | 360 |
self.fields['initial_login_page'].widget.template_name = 'combo/widgets/select_with_other_option.html' |
361 | 361 |
self.fields['initial_login_page'].widget.attrs['class'] = 'page-selector' |
362 | 362 |
self.fields['welcome_page'].queryset = self.fields['welcome_page'].queryset.filter(public=True) |
363 | ||
364 | ||
365 |
class PlaceholderOptionsForm(forms.Form): |
|
366 |
fx_grid_layout = forms.ChoiceField( |
|
367 |
label=_('Grid Layout'), |
|
368 |
required=False, |
|
369 |
choices=[ |
|
370 |
('', _('None')), |
|
371 |
('fx-grid', _('1 column')), |
|
372 |
('fx-grid--t2', _('2 columns')), |
|
373 |
('fx-grid--t3', _('3 columns')), |
|
374 |
], |
|
375 |
) |
combo/manager/static/css/combo.manager.scss | ||
---|---|---|
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 | ||
---|---|---|
152 | 152 |
name='combo-manager-link-list-order', |
153 | 153 |
), |
154 | 154 |
url(r'^pages/(?P<page_pk>\d+)/order$', views.cell_order, name='combo-manager-cell-order'), |
155 |
url( |
|
156 |
r'^pages/(?P<page_pk>\d+)/placeholder/(?P<placeholder>[\w_-]+)/options$', |
|
157 |
views.placeholder_options, |
|
158 |
name='combo-manage-placeholder-options', |
|
159 |
), |
|
155 | 160 |
url(r'^pages/order$', views.page_order, name='combo-manager-page-order'), |
156 | 161 |
url( |
157 | 162 |
r'^pages/(?P<page_path>[\w/_-]+)/$', |
combo/manager/views.py | ||
---|---|---|
76 | 76 |
PageRestrictedAddForm, |
77 | 77 |
PageSelectTemplateForm, |
78 | 78 |
PageVisibilityForm, |
79 |
PlaceholderOptionsForm, |
|
79 | 80 |
SiteExportForm, |
80 | 81 |
SiteImportForm, |
81 | 82 |
SiteSettingsForm, |
... | ... | |
1083 | 1084 | |
1084 | 1085 | |
1085 | 1086 |
site_settings = SiteSettingsView.as_view() |
1087 | ||
1088 | ||
1089 |
class PlaceholderOptionsView(ManagedPageMixin, FormView): |
|
1090 |
form_class = PlaceholderOptionsForm |
|
1091 |
template_name = 'combo/placeholder_options.html' |
|
1092 | ||
1093 |
def get_object(self): |
|
1094 |
return get_object_or_404(Page, pk=self.kwargs['page_pk']) |
|
1095 | ||
1096 |
def get_context_data(self, **kwargs): |
|
1097 |
context = super().get_context_data(**kwargs) |
|
1098 |
context['page'] = self.get_object() |
|
1099 |
return context |
|
1100 | ||
1101 |
def get_initial(self): |
|
1102 |
return self.page.placeholder_options.get(self.kwargs['placeholder']) |
|
1103 | ||
1104 |
def form_valid(self, form): |
|
1105 |
self.page.placeholder_options[self.kwargs['placeholder']] = form.cleaned_data |
|
1106 |
self.page.save() |
|
1107 |
return super().form_valid(form) |
|
1108 | ||
1109 |
def get_success_url(self): |
|
1110 |
return reverse('combo-manager-page-view', kwargs={'pk': self.page.id}) |
|
1111 | ||
1112 | ||
1113 |
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="combo-placeholder {{ 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 | ||
---|---|---|
2822 | 2822 |
site_settings.refresh_from_db() |
2823 | 2823 |
assert site_settings.welcome_page == public_page |
2824 | 2824 |
assert site_settings.initial_login_page == private_page |
2825 | ||
2826 | ||
2827 |
def test_manager_placeholder_grid(app, admin_user): |
|
2828 |
page = Page.objects.create(title='Page', slug='page') |
|
2829 |
cell = TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1) |
|
2830 |
app = login(app) |
|
2831 |
resp = app.get('/manage/pages/%s/' % page.id) |
|
2832 |
resp = resp.click('Options', href=r'cell.*options') |
|
2833 |
assert 'fx_size' not in resp.form.fields |
|
2834 |
resp = app.get('/manage/pages/%s/' % page.id) |
|
2835 |
resp = resp.click('Options', href=r'placeholder.*options') |
|
2836 |
resp.form['fx_grid_layout'].select(text='2 columns') |
|
2837 |
resp = resp.form.submit().follow() |
|
2838 | ||
2839 |
page.refresh_from_db() |
|
2840 |
assert page.placeholder_options['content']['fx_grid_layout'] == 'fx-grid--t2' |
|
2841 |
resp = resp.click('Options', href=r'placeholder.*options') |
|
2842 |
assert resp.form['fx_grid_layout'].value == 'fx-grid--t2' |
|
2843 | ||
2844 |
resp = app.get('/manage/pages/%s/' % page.id) |
|
2845 |
resp = resp.click('Options', href=r'cell.*options') |
|
2846 |
resp.form['cdata_textcell-%s-fx_size' % cell.id].select(text='1/2') |
|
2847 |
resp = resp.form.submit().follow() |
|
2848 |
cell.refresh_from_db() |
|
2849 |
assert cell.extra_css_class == 'size--t1-2' |
|
2850 | ||
2851 |
resp = resp.click('Options', href=r'cell.*options') |
|
2852 |
assert resp.form['cdata_textcell-%s-fx_size' % cell.id].value == 'size--t1-2' |
|
2853 |
assert not resp.form['cdata_textcell-%s-extra_css_class' % cell.id].value |
|
2854 |
resp.form['cdata_textcell-%s-extra_css_class' % cell.id] = 'plop' |
|
2855 |
resp = resp.form.submit().follow() |
|
2856 |
cell.refresh_from_db() |
|
2857 |
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'}} |
|
1230 |
page.save() |
|
1231 | ||
1232 |
resp = app.get('/', status=200) |
|
1233 |
# placeholder div in between |
|
1234 |
assert resp.pyquery('#content > .fx-grid > .text-cell') |
|
1216 |
- |