0003-cells-if-a-cell-is-invalid-display-it-38009.patch
combo/data/models.py | ||
---|---|---|
564 | 564 |
last_update_timestamp = models.DateTimeField(auto_now=True) |
565 | 565 | |
566 | 566 |
validity_info = GenericRelation(ValidityInfo) |
567 |
invalid_reason_codes = {} |
|
567 | 568 | |
568 | 569 |
default_form_class = None |
569 | 570 |
manager_form_factory_kwargs = {} |
... | ... | |
641 | 642 |
return cell_types |
642 | 643 | |
643 | 644 |
@classmethod |
644 |
def get_cells(cls, cell_filter=None, skip_cell_cache=False, **kwargs): |
|
645 |
def get_cells(cls, cell_filter=None, skip_cell_cache=False, prefetch_validity_info=False, **kwargs):
|
|
645 | 646 |
"""Returns the list of cells of various classes matching **kwargs""" |
646 | 647 |
cells = [] |
647 | 648 |
pages = [] |
... | ... | |
670 | 671 |
if cell_filter and not cell_filter(klass): |
671 | 672 |
continue |
672 | 673 |
cells.extend(klass.objects.filter(**kwargs)) |
674 |
if prefetch_validity_info: |
|
675 |
validity_info_list = list(ValidityInfo.objects.select_related('content_type')) |
|
676 |
for cell in cells: |
|
677 |
cell.prefetched_validity_info = [ |
|
678 |
v for v in validity_info_list |
|
679 |
if v.object_id == cell.pk and v.content_type.model_class() == cell.__class__] |
|
673 | 680 |
cells.sort(key=lambda x: x.order) |
674 | 681 |
return cells |
675 | 682 | |
... | ... | |
737 | 744 |
def get_extra_manager_context(self): |
738 | 745 |
return {} |
739 | 746 | |
740 |
def is_visible(self, user=None): |
|
747 |
def mark_as_invalid(self, reason_code, force=True, save=True): |
|
748 |
validity_info = self.validity_info.all().first() |
|
749 |
if validity_info is None: |
|
750 |
validity_info = ValidityInfo(content_object=self) |
|
751 | ||
752 |
if not force and validity_info.invalid_since is not None: |
|
753 |
# don't overwrite invalid reason already set |
|
754 |
return |
|
755 | ||
756 |
if validity_info.invalid_reason_code == reason_code: |
|
757 |
# don't overwrite invalid_since if same reason already set |
|
758 |
return |
|
759 | ||
760 |
validity_info.invalid_reason_code = reason_code |
|
761 |
validity_info.invalid_since = now() |
|
762 |
if save: |
|
763 |
validity_info.save() |
|
764 |
return validity_info |
|
765 | ||
766 |
def mark_as_valid(self, save=True): |
|
741 | 767 |
validity_info = self.validity_info.all().first() |
768 |
if validity_info is None: |
|
769 |
return |
|
770 |
if save: |
|
771 |
validity_info.delete() |
|
772 |
return validity_info |
|
773 | ||
774 |
def get_validity_info(self): |
|
775 |
if hasattr(self, 'prefetched_validity_info'): |
|
776 |
if not self.prefetched_validity_info: |
|
777 |
return |
|
778 |
return self.prefetched_validity_info[0] |
|
779 |
return self.validity_info.all().first() |
|
780 | ||
781 |
def get_invalid_reason(self): |
|
782 |
validity_info = self.get_validity_info() |
|
783 |
if validity_info is None: |
|
784 |
return |
|
785 |
if not validity_info.invalid_since: |
|
786 |
return |
|
787 |
return self.invalid_reason_codes.get( |
|
788 |
validity_info.invalid_reason_code, validity_info.invalid_reason_code) |
|
789 | ||
790 |
def is_visible(self, user=None): |
|
791 |
validity_info = self.get_validity_info() |
|
742 | 792 |
if validity_info is not None and validity_info.invalid_since and validity_info.invalid_since < now() - datetime.timedelta(days=2): |
743 | 793 |
return False |
744 | 794 |
return element_is_visible(self, user=user) |
combo/manager/static/css/combo.manager.css | ||
---|---|---|
80 | 80 |
} |
81 | 81 | |
82 | 82 |
div.cell h3 span.additional-label, |
83 |
div.cell h3 span.invalid, |
|
83 | 84 |
div.cell h3 span.visibility-summary, |
84 | 85 |
div.page span.visibility-summary { |
85 | 86 |
font-size: 80%; |
... | ... | |
111 | 112 |
max-width: 30%; |
112 | 113 |
} |
113 | 114 | |
115 |
div.cell h3 span.invalid { |
|
116 |
color: red; |
|
117 |
} |
|
118 | ||
114 | 119 |
div.cell-list div h3:after { |
115 | 120 |
font-family: FontAwesome; |
116 | 121 |
content: "\f107"; /* angle-down */ |
combo/manager/static/js/combo.manager.js | ||
---|---|---|
212 | 212 |
$.getJSON($form.data('label-url'), |
213 | 213 |
function(data) { |
214 | 214 |
$form.parents('div.cell').find('.additional-label i').text(data['label']); |
215 |
if (data['invalid_reason']) { |
|
216 |
$form.parents('div.cell').find('.invalid').text('/!\\ ' + data['invalid_reason']); |
|
217 |
} else { |
|
218 |
$form.parents('div.cell').find('.invalid').text(''); |
|
219 |
} |
|
215 | 220 |
} |
216 | 221 |
); |
217 | 222 |
} |
combo/manager/templates/combo/page_view.html | ||
---|---|---|
146 | 146 |
<span class="group1"> |
147 | 147 |
{{ cell.get_label }} |
148 | 148 |
{% if cell.slug %} [{{cell.slug}}] {% endif %} |
149 |
{% if cell.extra_css_class %} |
|
150 |
<span class="extra-css-class">[{{ cell.extra_css_class }}]</span> |
|
151 |
{% endif %} |
|
152 |
<span class="additional-label">
|
|
153 |
<i>{{cell.get_additional_label|default_if_none:""}}</i></span>
|
|
149 |
{% if cell.extra_css_class %}
|
|
150 |
<span class="extra-css-class">[{{ cell.extra_css_class }}]</span>
|
|
151 |
{% endif %}
|
|
152 |
<span class="additional-label"><i>{{cell.get_additional_label|default_if_none:""}}</i></span>
|
|
153 |
<span class="invalid">{% with cell.get_invalid_reason as invalid_reason %}{% if invalid_reason %}/!\ {{ cell.get_invalid_reason }}{% endif %}{% endwith %}</span>
|
|
154 | 154 |
</span> |
155 | 155 |
{% if not cell.public %} |
156 | 156 |
<span class="visibility-summary |
combo/manager/views.py | ||
---|---|---|
261 | 261 |
context['cell_type_groups'] = list(cell_type_groups.items()) |
262 | 262 |
context['cell_type_groups'].sort(key=lambda x: x[0]) |
263 | 263 | |
264 |
cells = CellBase.get_cells(page=self.object) |
|
264 |
cells = CellBase.get_cells(page=self.object, prefetch_validity_info=True)
|
|
265 | 265 |
self.object.prefetched_cells = cells |
266 | 266 |
template = self.object.template_name |
267 | 267 |
placeholders = [] |
... | ... | |
568 | 568 |
def page_get_additional_label(request, page_pk, cell_reference): |
569 | 569 |
cell = CellBase.get_cell(cell_reference, page_id=page_pk) |
570 | 570 |
response = HttpResponse(content_type='application/json') |
571 |
json.dump({'label': force_text(cell.get_additional_label()) or ''}, response) |
|
571 |
json.dump({ |
|
572 |
'label': force_text(cell.get_additional_label()) or '', |
|
573 |
'invalid_reason': force_text(cell.get_invalid_reason() or '') |
|
574 |
}, response) |
|
572 | 575 |
return response |
573 | 576 | |
574 | 577 |
tests/test_manager.py | ||
---|---|---|
23 | 23 |
from webtest import Upload |
24 | 24 | |
25 | 25 |
from combo.wsgi import application |
26 |
from combo.data.models import Page, CellBase, TextCell, LinkCell, ConfigJsonCell, JsonCell, PageSnapshot, LinkListCell, ParentContentCell, MenuCell |
|
26 |
from combo.data.models import ( |
|
27 |
Page, CellBase, TextCell, LinkCell, ConfigJsonCell, JsonCell, PageSnapshot, |
|
28 |
LinkListCell, ParentContentCell, MenuCell, ValidityInfo) |
|
27 | 29 |
from combo.apps.assets.models import Asset |
28 | 30 |
from combo.apps.family.models import FamilyInfosCell |
29 | 31 |
from combo.apps.search.models import SearchCell |
... | ... | |
153 | 155 |
assert Page.objects.all()[0].exclude_from_navigation is False |
154 | 156 | |
155 | 157 | |
158 |
def test_edit_page_cell_invalid_placeholder(app, admin_user): |
|
159 |
page = Page.objects.create(title='One', slug='one', template_name='standard') |
|
160 |
cell = TextCell.objects.create(page=page, placeholder='content', text='Foobar', order=1) |
|
161 |
cell.mark_as_invalid('foo_bar_reason') |
|
162 |
validity_info = ValidityInfo.objects.latest('pk') |
|
163 |
old_reason = validity_info.invalid_reason_code |
|
164 |
old_date = validity_info.invalid_since |
|
165 | ||
166 |
app = login(app) |
|
167 |
resp = app.get('/manage/pages/%s/' % page.pk) |
|
168 |
assert '<span class="invalid">/!\\ foo_bar_reason</span>' in resp.text |
|
169 | ||
170 |
cell.mark_as_invalid('another_foo_bar_reason', force=False) |
|
171 |
validity_info.refresh_from_db() |
|
172 |
assert old_reason == validity_info.invalid_reason_code |
|
173 |
assert old_date == validity_info.invalid_since |
|
174 | ||
175 |
cell.mark_as_invalid('another_foo_bar_reason') |
|
176 |
validity_info.refresh_from_db() |
|
177 |
assert validity_info.invalid_reason_code == 'another_foo_bar_reason' |
|
178 |
assert old_date < validity_info.invalid_since |
|
179 | ||
180 |
cell.mark_as_valid() |
|
181 |
assert ValidityInfo.objects.exists() is False |
|
182 |
resp = app.get('/manage/pages/%s/' % page.pk) |
|
183 |
assert '<span class="invalid">/!\\ foo_bar_reason</span>' not in resp.text |
|
184 | ||
185 | ||
156 | 186 |
def test_edit_page_optional_placeholder(app, admin_user): |
157 | 187 |
Page.objects.all().delete() |
158 | 188 |
page = Page.objects.create(title='One', slug='one', template_name='standard') |
159 |
- |