Projet

Général

Profil

0003-cells-if-a-cell-is-invalid-display-it-38009.patch

Lauréline Guérin, 03 mars 2020 15:25

Télécharger (10,5 ko)

Voir les différences:

Subject: [PATCH 3/6] cells: if a cell is invalid, display it (#38009)

 combo/data/models.py                         | 54 +++++++++++++++++++-
 combo/manager/static/css/combo.manager.css   | 11 ++++
 combo/manager/static/js/combo.manager.js     |  8 +++
 combo/manager/templates/combo/page_view.html | 14 +++--
 combo/manager/views.py                       |  7 ++-
 tests/test_manager.py                        | 32 +++++++++++-
 6 files changed, 116 insertions(+), 10 deletions(-)
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: #df2240;
117
}
118

  
119
.invalid::before {
120
    font-family: FontAwesome;
121
    content: "\f071";  /* exclamation-triangle */
122
    padding-right: 0.5em;
123
}
124

  
114 125
div.cell-list div h3:after {
115 126
	font-family: FontAwesome;
116 127
	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
                      if (! $form.parents('div.cell').find('.invalid').length) {
217
                        $('<span class="invalid"></span>').insertAfter($form.parents('div.cell').find('.additional-label'));
218
                      }
219
                      $form.parents('div.cell').find('.invalid').text(data['invalid_reason']);
220
                    } else {
221
                      $form.parents('div.cell').find('.invalid').remove();
222
                    }
215 223
                }
216 224
              );
217 225
            }
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
                  {% with cell.get_invalid_reason as invalid_reason %}
154
                  {% if invalid_reason %}
155
                  <span class="invalid">{{ invalid_reason }}</span>
156
                  {% endif %}
157
                  {% endwith %}
154 158
                </span>
155 159
                {% if not cell.public %}
156 160
                  <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
-