cells: if a cell is invalid, display it (#38009)

This commit is contained in:
Lauréline Guérin 2020-02-17 14:09:56 +01:00
parent 39234986fe
commit 6fa8bf883a
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
6 changed files with 116 additions and 10 deletions

View File

@ -564,6 +564,7 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)):
last_update_timestamp = models.DateTimeField(auto_now=True)
validity_info = GenericRelation(ValidityInfo)
invalid_reason_codes = {}
default_form_class = None
manager_form_factory_kwargs = {}
@ -641,7 +642,7 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)):
return cell_types
@classmethod
def get_cells(cls, cell_filter=None, skip_cell_cache=False, **kwargs):
def get_cells(cls, cell_filter=None, skip_cell_cache=False, prefetch_validity_info=False, **kwargs):
"""Returns the list of cells of various classes matching **kwargs"""
cells = []
pages = []
@ -670,6 +671,12 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)):
if cell_filter and not cell_filter(klass):
continue
cells.extend(klass.objects.filter(**kwargs))
if prefetch_validity_info:
validity_info_list = list(ValidityInfo.objects.select_related('content_type'))
for cell in cells:
cell.prefetched_validity_info = [
v for v in validity_info_list
if v.object_id == cell.pk and v.content_type.model_class() == cell.__class__]
cells.sort(key=lambda x: x.order)
return cells
@ -737,8 +744,51 @@ class CellBase(six.with_metaclass(CellMeta, models.Model)):
def get_extra_manager_context(self):
return {}
def is_visible(self, user=None):
def mark_as_invalid(self, reason_code, force=True, save=True):
validity_info = self.validity_info.all().first()
if validity_info is None:
validity_info = ValidityInfo(content_object=self)
if not force and validity_info.invalid_since is not None:
# don't overwrite invalid reason already set
return
if validity_info.invalid_reason_code == reason_code:
# don't overwrite invalid_since if same reason already set
return
validity_info.invalid_reason_code = reason_code
validity_info.invalid_since = now()
if save:
validity_info.save()
return validity_info
def mark_as_valid(self, save=True):
validity_info = self.validity_info.all().first()
if validity_info is None:
return
if save:
validity_info.delete()
return validity_info
def get_validity_info(self):
if hasattr(self, 'prefetched_validity_info'):
if not self.prefetched_validity_info:
return
return self.prefetched_validity_info[0]
return self.validity_info.all().first()
def get_invalid_reason(self):
validity_info = self.get_validity_info()
if validity_info is None:
return
if not validity_info.invalid_since:
return
return self.invalid_reason_codes.get(
validity_info.invalid_reason_code, validity_info.invalid_reason_code)
def is_visible(self, user=None):
validity_info = self.get_validity_info()
if validity_info is not None and validity_info.invalid_since and validity_info.invalid_since < now() - datetime.timedelta(days=2):
return False
return element_is_visible(self, user=user)

View File

@ -80,6 +80,7 @@ div.cell h3 span.extra-css-class {
}
div.cell h3 span.additional-label,
div.cell h3 span.invalid,
div.cell h3 span.visibility-summary,
div.page span.visibility-summary {
font-size: 80%;
@ -111,6 +112,16 @@ div.cell h3 span.visibility-summary {
max-width: 30%;
}
div.cell h3 span.invalid {
color: #df2240;
}
.invalid::before {
font-family: FontAwesome;
content: "\f071"; /* exclamation-triangle */
padding-right: 0.5em;
}
div.cell-list div h3:after {
font-family: FontAwesome;
content: "\f107"; /* angle-down */

View File

@ -212,6 +212,14 @@ $(function() {
$.getJSON($form.data('label-url'),
function(data) {
$form.parents('div.cell').find('.additional-label i').text(data['label']);
if (data['invalid_reason']) {
if (! $form.parents('div.cell').find('.invalid').length) {
$('<span class="invalid"></span>').insertAfter($form.parents('div.cell').find('.additional-label'));
}
$form.parents('div.cell').find('.invalid').text(data['invalid_reason']);
} else {
$form.parents('div.cell').find('.invalid').remove();
}
}
);
}

View File

@ -146,11 +146,15 @@
<span class="group1">
{{ cell.get_label }}
{% if cell.slug %} [{{cell.slug}}] {% endif %}
{% if cell.extra_css_class %}
<span class="extra-css-class">[{{ cell.extra_css_class }}]</span>
{% endif %}
<span class="additional-label">
<i>{{cell.get_additional_label|default_if_none:""}}</i></span>
{% if cell.extra_css_class %}
<span class="extra-css-class">[{{ cell.extra_css_class }}]</span>
{% endif %}
<span class="additional-label"><i>{{cell.get_additional_label|default_if_none:""}}</i></span>
{% with cell.get_invalid_reason as invalid_reason %}
{% if invalid_reason %}
<span class="invalid">{{ invalid_reason }}</span>
{% endif %}
{% endwith %}
</span>
{% if not cell.public %}
<span class="visibility-summary

View File

@ -261,7 +261,7 @@ class PageView(DetailView):
context['cell_type_groups'] = list(cell_type_groups.items())
context['cell_type_groups'].sort(key=lambda x: x[0])
cells = CellBase.get_cells(page=self.object)
cells = CellBase.get_cells(page=self.object, prefetch_validity_info=True)
self.object.prefetched_cells = cells
template = self.object.template_name
placeholders = []
@ -568,7 +568,10 @@ def page_order(request):
def page_get_additional_label(request, page_pk, cell_reference):
cell = CellBase.get_cell(cell_reference, page_id=page_pk)
response = HttpResponse(content_type='application/json')
json.dump({'label': force_text(cell.get_additional_label()) or ''}, response)
json.dump({
'label': force_text(cell.get_additional_label()) or '',
'invalid_reason': force_text(cell.get_invalid_reason() or '')
}, response)
return response

View File

@ -23,7 +23,9 @@ from webtest import TestApp
from webtest import Upload
from combo.wsgi import application
from combo.data.models import Page, CellBase, TextCell, LinkCell, ConfigJsonCell, JsonCell, PageSnapshot, LinkListCell, ParentContentCell, MenuCell
from combo.data.models import (
Page, CellBase, TextCell, LinkCell, ConfigJsonCell, JsonCell, PageSnapshot,
LinkListCell, ParentContentCell, MenuCell, ValidityInfo)
from combo.apps.assets.models import Asset
from combo.apps.family.models import FamilyInfosCell
from combo.apps.search.models import SearchCell
@ -153,6 +155,34 @@ def test_edit_page(app, admin_user):
assert Page.objects.all()[0].exclude_from_navigation is False
def test_edit_page_cell_invalid_placeholder(app, admin_user):
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1)
cell.mark_as_invalid('foo_bar_reason')
validity_info = ValidityInfo.objects.latest('pk')
old_reason = validity_info.invalid_reason_code
old_date = validity_info.invalid_since
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
assert '<span class="invalid">foo_bar_reason</span>' in resp.text
cell.mark_as_invalid('another_foo_bar_reason', force=False)
validity_info.refresh_from_db()
assert old_reason == validity_info.invalid_reason_code
assert old_date == validity_info.invalid_since
cell.mark_as_invalid('another_foo_bar_reason')
validity_info.refresh_from_db()
assert validity_info.invalid_reason_code == 'another_foo_bar_reason'
assert old_date < validity_info.invalid_since
cell.mark_as_valid()
assert ValidityInfo.objects.exists() is False
resp = app.get('/manage/pages/%s/' % page.pk)
assert '<span class="invalid">foo_bar_reason</span>' not in resp.text
def test_edit_page_optional_placeholder(app, admin_user):
Page.objects.all().delete()
page = Page.objects.create(title='One', slug='one', template_name='standard')