0006-cells-data-cells-invalid-report-38009.patch
combo/data/apps.py | ||
---|---|---|
20 | 20 |
class DataConfig(AppConfig): |
21 | 21 |
name = 'combo.data' |
22 | 22 |
verbose_name = 'data' |
23 | ||
24 |
def hourly(self): |
|
25 |
from combo.data.library import get_cell_classes |
|
26 |
from combo.data.models import CellBase |
|
27 | ||
28 |
cell_classes = [c for c in self.get_models() if c in get_cell_classes()] |
|
29 |
for cell in CellBase.get_cells(cell_filter=lambda x: x in cell_classes, page__snapshot__isnull=True): |
|
30 |
if hasattr(cell, 'check_validity'): |
|
31 |
cell.check_validity() |
combo/data/models.py | ||
---|---|---|
768 | 768 |
return validity_info |
769 | 769 | |
770 | 770 |
def mark_as_valid(self, save=True): |
771 |
validity_info = self.validity_info.all().first()
|
|
771 |
validity_info = self.get_validity_info()
|
|
772 | 772 |
if validity_info is None: |
773 | 773 |
return |
774 | 774 |
if save: |
... | ... | |
782 | 782 |
return self.prefetched_validity_info[0] |
783 | 783 |
return self.validity_info.all().first() |
784 | 784 | |
785 |
def set_validity_from_url( |
|
786 |
self, resp, |
|
787 |
not_found_code='url_not_found', invalid_code='url_invalid', |
|
788 |
save=True): |
|
789 |
if resp is None: |
|
790 |
# can not retrieve data, don't report cell as invalid |
|
791 |
self.mark_as_valid(save=save) |
|
792 |
elif resp.status_code == 404: |
|
793 |
self.mark_as_invalid(not_found_code, save=save) |
|
794 |
elif 400 <= resp.status_code < 500: |
|
795 |
# 4xx error, cell is invalid |
|
796 |
self.mark_as_invalid(invalid_code, save=save) |
|
797 |
else: |
|
798 |
# 2xx or 3xx: cell is valid |
|
799 |
# 5xx error: can not retrieve data, don't report cell as invalid |
|
800 |
self.mark_as_valid(save=save) |
|
801 | ||
785 | 802 |
def get_invalid_reason(self): |
786 | 803 |
validity_info = self.get_validity_info() |
787 | 804 |
if validity_info is None: |
... | ... | |
1015 | 1032 |
edit_link_label = _('Edit link') |
1016 | 1033 |
add_as_link_code = 'link' |
1017 | 1034 | |
1035 |
invalid_reason_codes = { |
|
1036 |
'data_url_not_defined': _('No link set'), |
|
1037 |
'data_url_not_found': _('URL seems to unexist'), |
|
1038 |
'data_url_invalid': _('URL seems to be invalid'), |
|
1039 |
} |
|
1040 | ||
1018 | 1041 |
class Meta: |
1019 | 1042 |
verbose_name = _('Link') |
1020 | 1043 | |
1044 |
def save(self, *args, **kwargs): |
|
1045 |
if 'update_fields' in kwargs: |
|
1046 |
# don't check validity |
|
1047 |
return super(LinkCell, self).save(*args, **kwargs) |
|
1048 | ||
1049 |
result = super(LinkCell, self).save(*args, **kwargs) |
|
1050 |
# check validity |
|
1051 |
self.check_validity() |
|
1052 |
return result |
|
1053 | ||
1021 | 1054 |
def get_additional_label(self): |
1022 | 1055 |
title = self.title |
1023 | 1056 |
if not title and self.link_page: |
... | ... | |
1026 | 1059 |
return None |
1027 | 1060 |
return utils.ellipsize(title) |
1028 | 1061 | |
1062 |
def get_url(self, context=None): |
|
1063 |
context = context or {} |
|
1064 |
if self.link_page: |
|
1065 |
url = self.link_page.get_online_url() |
|
1066 |
else: |
|
1067 |
url = utils.get_templated_url(self.url, context=context) |
|
1068 |
if self.anchor: |
|
1069 |
url += '#' + self.anchor |
|
1070 |
return url |
|
1071 | ||
1029 | 1072 |
def get_cell_extra_context(self, context): |
1030 | 1073 |
render_skeleton = context.get('render_skeleton') |
1031 | 1074 |
request = context.get('request') |
1032 | 1075 |
extra_context = super(LinkCell, self).get_cell_extra_context(context) |
1033 | 1076 |
if self.link_page: |
1034 |
extra_context['url'] = self.link_page.get_online_url() |
|
1035 | 1077 |
extra_context['title'] = self.title or self.link_page.title |
1036 | 1078 |
else: |
1037 |
extra_context['url'] = utils.get_templated_url(self.url, context=context) |
|
1038 | 1079 |
extra_context['title'] = self.title or self.url |
1039 |
if self.anchor: |
|
1040 |
extra_context['url'] += '#' + self.anchor |
|
1041 |
if render_skeleton and not urlparse.urlparse(extra_context['url']).netloc: |
|
1080 |
url = self.get_url(context) |
|
1081 |
if render_skeleton and not urlparse.urlparse(url).netloc: |
|
1042 | 1082 |
# create full URL when used in a skeleton |
1043 |
extra_context['url'] = request.build_absolute_uri(extra_context['url']) |
|
1083 |
url = request.build_absolute_uri(url) |
|
1084 |
extra_context['url'] = url |
|
1044 | 1085 |
return extra_context |
1045 | 1086 | |
1046 | 1087 |
def get_default_form_class(self): |
... | ... | |
1058 | 1099 |
return [link_data] |
1059 | 1100 |
return [] |
1060 | 1101 | |
1102 |
def check_validity(self): |
|
1103 |
if self.link_page: |
|
1104 |
self.mark_as_valid() |
|
1105 |
return |
|
1106 |
if not self.url: |
|
1107 |
self.mark_as_invalid('data_url_not_defined') |
|
1108 |
return |
|
1109 | ||
1110 |
resp = None |
|
1111 |
try: |
|
1112 |
resp = requests.get(self.get_url(), timeout=settings.REQUESTS_TIMEOUT) |
|
1113 |
resp.raise_for_status() |
|
1114 |
except (requests.exceptions.RequestException): |
|
1115 |
pass |
|
1116 | ||
1117 |
self.set_validity_from_url(resp, not_found_code='data_url_not_found', invalid_code='data_url_invalid') |
|
1118 | ||
1061 | 1119 | |
1062 | 1120 |
@register_cell_class |
1063 | 1121 |
class LinkListCell(CellBase): |
... | ... | |
1066 | 1124 |
template_name = 'combo/link-list-cell.html' |
1067 | 1125 |
manager_form_template = 'combo/manager/link-list-cell-form.html' |
1068 | 1126 | |
1127 |
invalid_reason_codes = { |
|
1128 |
'data_link_invalid': _('Invalid link'), |
|
1129 |
} |
|
1130 | ||
1069 | 1131 |
class Meta: |
1070 | 1132 |
verbose_name = _('List of links') |
1071 | 1133 | |
... | ... | |
1073 | 1135 |
def link_placeholder(self): |
1074 | 1136 |
return '_linkslist:{}'.format(self.pk) |
1075 | 1137 | |
1076 |
def get_items(self): |
|
1138 |
def get_items(self, prefetch_validity_info=False):
|
|
1077 | 1139 |
return CellBase.get_cells( |
1078 | 1140 |
page=self.page, |
1079 | 1141 |
placeholder=self.link_placeholder, |
1080 |
cell_filter=lambda x: hasattr(x, 'add_as_link_label')) |
|
1142 |
cell_filter=lambda x: hasattr(x, 'add_as_link_label'), |
|
1143 |
prefetch_validity_info=prefetch_validity_info) |
|
1144 | ||
1145 |
def get_items_with_prefetch(self): |
|
1146 |
return self.get_items(prefetch_validity_info=True) |
|
1081 | 1147 | |
1082 | 1148 |
def get_additional_label(self): |
1083 | 1149 |
title = self.title |
... | ... | |
1129 | 1195 |
for link in self.get_items(): |
1130 | 1196 |
link.duplicate(page_target=new_cell.page, placeholder=new_cell.link_placeholder) |
1131 | 1197 | |
1198 |
def check_validity(self): |
|
1199 |
for link in self.get_items(prefetch_validity_info=True): |
|
1200 |
validity_info = link.get_validity_info() |
|
1201 |
if validity_info is not None: |
|
1202 |
self.mark_as_invalid('data_link_invalid') |
|
1203 |
return |
|
1204 |
self.mark_as_valid() |
|
1205 | ||
1132 | 1206 | |
1133 | 1207 |
@register_cell_class |
1134 | 1208 |
class FeedCell(CellBase): |
1135 | 1209 |
title = models.CharField(_('Title'), max_length=150, blank=True) |
1136 | 1210 |
url = models.CharField(_('URL'), blank=True, max_length=200) |
1137 |
limit = models.PositiveSmallIntegerField(_('Maximum number of entries'), |
|
1138 |
null=True, blank=True) |
|
1211 |
limit = models.PositiveSmallIntegerField( |
|
1212 |
_('Maximum number of entries'), |
|
1213 |
null=True, blank=True) |
|
1139 | 1214 | |
1140 | 1215 |
manager_form_factory_kwargs = {'field_classes': {'url': TemplatableURLField}} |
1141 | 1216 |
template_name = 'combo/feed-cell.html' |
1142 | 1217 | |
1218 |
invalid_reason_codes = { |
|
1219 |
'data_url_not_defined': _('No URL set'), |
|
1220 |
'data_url_not_found': _('URL seems to unexist'), |
|
1221 |
'data_url_invalid': _('URL seems to be invalid'), |
|
1222 |
} |
|
1223 | ||
1143 | 1224 |
class Meta: |
1144 | 1225 |
verbose_name = _('RSS/Atom Feed') |
1145 | 1226 | |
... | ... | |
1148 | 1229 | |
1149 | 1230 |
def get_cell_extra_context(self, context): |
1150 | 1231 |
extra_context = super(FeedCell, self).get_cell_extra_context(context) |
1232 | ||
1233 |
if not self.url: |
|
1234 |
self.mark_as_invalid('data_url_not_defined') |
|
1235 |
return extra_context |
|
1236 | ||
1151 | 1237 |
if context.get('placeholder_search_mode'): |
1152 | 1238 |
# don't call webservices when we're just looking for placeholders |
1153 | 1239 |
return extra_context |
1240 | ||
1154 | 1241 |
cache_key = hashlib.md5(smart_bytes(self.url)).hexdigest() |
1155 | 1242 |
feed_content = cache.get(cache_key) |
1156 | 1243 |
if not feed_content: |
1244 |
feed_response = None |
|
1157 | 1245 |
try: |
1158 | 1246 |
feed_response = requests.get(utils.get_templated_url(self.url)) |
1159 | 1247 |
feed_response.raise_for_status() |
... | ... | |
1163 | 1251 |
if feed_response.status_code == 200: |
1164 | 1252 |
feed_content = feed_response.content |
1165 | 1253 |
cache.set(cache_key, feed_content, 600) |
1254 |
self.set_validity_from_url(feed_response, not_found_code='data_url_not_found', invalid_code='data_url_invalid') |
|
1166 | 1255 |
if feed_content: |
1167 | 1256 |
extra_context['feed'] = feedparser.parse(feed_content) |
1168 | 1257 |
if self.limit: |
... | ... | |
1240 | 1329 | |
1241 | 1330 |
_json_content = None |
1242 | 1331 | |
1332 |
invalid_reason_codes = { |
|
1333 |
'data_url_not_found': _('URL seems to unexist'), |
|
1334 |
'data_url_invalid': _('URL seems to be invalid'), |
|
1335 |
} |
|
1336 | ||
1243 | 1337 |
class Meta: |
1244 | 1338 |
abstract = True |
1245 | 1339 | |
... | ... | |
1291 | 1385 |
if not url: |
1292 | 1386 |
continue |
1293 | 1387 |
try: |
1294 |
json_response = utils.requests.get(url, |
|
1295 |
headers={'Accept': 'application/json'}, |
|
1296 |
remote_service='auto', |
|
1297 |
cache_duration=data_url_dict.get('cache_duration', self.cache_duration), |
|
1298 |
without_user=True, |
|
1299 |
raise_if_not_cached=not(context.get('synchronous')), |
|
1300 |
invalidate_cache=invalidate_cache, |
|
1301 |
log_errors=log_errors, |
|
1302 |
timeout=data_url_dict.get('timeout', self.timeout), |
|
1303 |
django_request=context.get('request'), |
|
1304 |
) |
|
1388 |
json_response = utils.requests.get( |
|
1389 |
url, |
|
1390 |
headers={'Accept': 'application/json'}, |
|
1391 |
remote_service='auto', |
|
1392 |
cache_duration=data_url_dict.get('cache_duration', self.cache_duration), |
|
1393 |
without_user=True, |
|
1394 |
raise_if_not_cached=not(context.get('synchronous')), |
|
1395 |
invalidate_cache=invalidate_cache, |
|
1396 |
log_errors=log_errors, |
|
1397 |
timeout=data_url_dict.get('timeout', self.timeout), |
|
1398 |
django_request=context.get('request'), |
|
1399 |
) |
|
1305 | 1400 |
except requests.RequestException as e: |
1306 | 1401 |
extra_context[data_key + '_status'] = -1 |
1307 | 1402 |
extra_context[data_key + '_error'] = force_text(e) |
... | ... | |
1335 | 1430 |
# templated URLs |
1336 | 1431 |
context[data_key] = extra_context[data_key] |
1337 | 1432 | |
1433 |
if not self._meta.abstract: |
|
1434 |
returns = [extra_context.get(d['key'] + '_status') for d in data_urls] |
|
1435 |
returns = set([s for s in returns if s is not None]) |
|
1436 |
if returns and 200 not in returns: # not a single valid answer |
|
1437 |
if 404 in returns: |
|
1438 |
self.mark_as_invalid('data_url_not_found') |
|
1439 |
elif any([400 <= r < 500 for r in returns]): |
|
1440 |
# at least 4xx errors, report the cell as invalid |
|
1441 |
self.mark_as_invalid('data_url_invalid') |
|
1442 |
else: |
|
1443 |
# 2xx or 3xx: cell is valid |
|
1444 |
# 5xx error: can not retrieve data, don't report cell as invalid |
|
1445 |
self.mark_as_valid() |
|
1446 |
else: |
|
1447 |
self.mark_as_valid() |
|
1448 | ||
1338 | 1449 |
# keep cache of first response as it may be used to find the |
1339 | 1450 |
# appropriate template. |
1340 | 1451 |
self._json_content = extra_context[self.first_data_key] |
combo/data/templates/combo/manager/link-list-cell-form.html | ||
---|---|---|
3 | 3 | |
4 | 4 |
{% block cell-form %} |
5 | 5 |
{{ form.as_p }} |
6 |
{% with cell.get_items as links %} |
|
6 |
{% with cell.get_items_with_prefetch as links %}
|
|
7 | 7 |
{% if links %} |
8 | 8 |
<p><label>{% trans "Links:" %}</label></p> |
9 | 9 |
<div> |
... | ... | |
11 | 11 |
data-link-list-order-url="{% url 'combo-manager-link-list-order' page_pk=page.pk cell_reference=cell.get_reference %}"> |
12 | 12 |
{% for link in links %} |
13 | 13 |
<li data-link-item-id="{{ link.pk }}"><span class="handle">⣿</span> |
14 |
<span title="{{ link }}">{{ link|truncatechars:100 }}</span> |
|
14 |
<span title="{{ link }}"> |
|
15 |
{{ link|truncatechars:100 }} |
|
16 |
{% with link.get_invalid_reason as invalid_reason %} |
|
17 |
{% if invalid_reason %} |
|
18 |
<span class="invalid">{{ invalid_reason }}</span> |
|
19 |
{% endif %} |
|
20 |
{% endwith %} |
|
21 |
</span> |
|
15 | 22 |
<a rel="popup" title="{% trans "Edit" %}" class="link-action-icon edit" href="{% url 'combo-manager-page-list-cell-edit-link' page_pk=page.id cell_reference=cell.get_reference link_cell_reference=link.get_reference %}">{% trans "Edit" %}</a> |
16 | 23 |
<a rel="popup" title="{% trans "Delete" %}" class="link-action-icon delete" href="{% url 'combo-manager-page-list-cell-delete-link' page_pk=page.id cell_reference=cell.get_reference link_cell_reference=link.get_reference %}">{% trans "Delete" %}</a> |
17 | 24 |
</li> |
combo/manager/static/css/combo.manager.css | ||
---|---|---|
81 | 81 | |
82 | 82 |
div.cell h3 span.additional-label, |
83 | 83 |
div.cell h3 span.invalid, |
84 |
ul.list-of-links span.invalid, |
|
84 | 85 |
div.cell h3 span.visibility-summary, |
85 | 86 |
div.page span.visibility-summary { |
86 | 87 |
font-size: 80%; |
... | ... | |
112 | 113 |
max-width: 30%; |
113 | 114 |
} |
114 | 115 | |
115 |
div.cell h3 span.invalid { |
|
116 |
div.cell h3 span.invalid, |
|
117 |
ul.list-of-links span.invalid { |
|
116 | 118 |
color: #df2240; |
117 | 119 |
} |
118 | 120 |
combo/manager/views.py | ||
---|---|---|
97 | 97 |
select_related=['page'], |
98 | 98 |
page__snapshot__isnull=True, |
99 | 99 |
validity_info__invalid_since__isnull=False) |
100 |
invalid_cells = [c for c in invalid_cells if c.placeholder and not c.placeholder.startswith('_')] |
|
100 | 101 |
context = { |
101 | 102 |
'object_list': invalid_cells, |
102 | 103 |
} |
... | ... | |
647 | 648 |
else: |
648 | 649 |
form.instance.order = 1 |
649 | 650 |
PageSnapshot.take(self.cell.page, request=self.request, comment=_('changed cell "%s"') % self.cell) |
650 |
return super(PageListCellAddLinkView, self).form_valid(form) |
|
651 |
response = super(PageListCellAddLinkView, self).form_valid(form) |
|
652 |
self.cell.check_validity() |
|
653 |
return response |
|
651 | 654 | |
652 | 655 |
def get_success_url(self): |
653 | 656 |
return '%s#cell-%s' % ( |
... | ... | |
688 | 691 |
else: |
689 | 692 |
response = super(PageListCellEditLinkView, self).form_valid(form) |
690 | 693 |
PageSnapshot.take(self.cell.page, request=self.request, comment=_('changed cell "%s"') % self.cell) |
694 |
self.cell.check_validity() |
|
691 | 695 |
return response |
692 | 696 | |
693 | 697 |
def get_success_url(self): |
... | ... | |
722 | 726 |
def delete(self, request, *args, **kwargs): |
723 | 727 |
response = super(PageListCellDeleteLinkView, self).delete(request, *args, **kwargs) |
724 | 728 |
PageSnapshot.take(self.cell.page, request=self.request, comment=_('changed cell "%s"') % self.cell) |
729 |
self.cell.check_validity() |
|
725 | 730 |
return response |
726 | 731 | |
727 | 732 |
def get_success_url(self): |
combo/public/views.py | ||
---|---|---|
518 | 518 |
if redirect_url: |
519 | 519 |
return HttpResponseRedirect(redirect_url) |
520 | 520 | |
521 |
cells = CellBase.get_cells(page=page) |
|
521 |
cells = CellBase.get_cells(page=page, prefetch_validity_info=True)
|
|
522 | 522 |
extend_with_parent_cells(cells, hierarchy=pages) |
523 | 523 |
cells = [x for x in cells if x.is_visible(user=request.user)] |
524 | 524 |
mark_duplicated_slugs(cells) |
tests/test_cells.py | ||
---|---|---|
7 | 7 | |
8 | 8 |
from combo.data.models import ( |
9 | 9 |
Page, CellBase, TextCell, LinkCell, MenuCell, JsonCellBase, |
10 |
JsonCell, ConfigJsonCell, LinkListCell, ValidityInfo |
|
10 |
JsonCell, ConfigJsonCell, LinkListCell, FeedCell, ValidityInfo
|
|
11 | 11 |
) |
12 |
from django.apps import apps |
|
12 | 13 |
from django.conf import settings |
13 | 14 |
from django.db import connection |
14 | 15 |
from django.forms.widgets import Media |
... | ... | |
28 | 29 | |
29 | 30 |
pytestmark = pytest.mark.django_db |
30 | 31 | |
32 | ||
33 |
@pytest.fixture |
|
34 |
def context(): |
|
35 |
ctx = {'request': RequestFactory().get('/')} |
|
36 |
ctx['request'].user = None |
|
37 |
ctx['request'].session = {} |
|
38 |
return ctx |
|
39 | ||
40 | ||
31 | 41 |
def mock_json_response(content, **kwargs): |
32 | 42 |
content = force_bytes(content) |
33 | 43 |
text = force_text(content) |
... | ... | |
136 | 146 |
assert cell.render(ctx).strip() == '<a href="http://example.net/#anchor">altertitle</a>' |
137 | 147 | |
138 | 148 | |
149 |
def test_link_cell_validity(): |
|
150 |
page = Page.objects.create(title='example page', slug='example-page') |
|
151 |
cell = LinkCell.objects.create( |
|
152 |
page=page, |
|
153 |
title='Example Site', |
|
154 |
order=0, |
|
155 |
) |
|
156 | ||
157 |
# no link defined |
|
158 |
validity_info = ValidityInfo.objects.latest('pk') |
|
159 |
assert validity_info.invalid_reason_code == 'data_url_not_defined' |
|
160 |
assert validity_info.invalid_since is not None |
|
161 | ||
162 |
# internal link - no check |
|
163 |
cell.link_page = page |
|
164 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
165 |
mock_json = mock.Mock(status_code=404) |
|
166 |
requests_get.return_value = mock_json |
|
167 |
cell.save() |
|
168 |
assert requests_get.call_args_list == [] |
|
169 |
assert ValidityInfo.objects.exists() is False |
|
170 | ||
171 |
# external link |
|
172 |
cell.link_page = None |
|
173 |
cell.url = 'http://example.net/' |
|
174 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
175 |
mock_json = mock.Mock(status_code=404) |
|
176 |
requests_get.return_value = mock_json |
|
177 |
cell.save() |
|
178 |
validity_info = ValidityInfo.objects.latest('pk') |
|
179 |
assert validity_info.invalid_reason_code == 'data_url_not_found' |
|
180 |
assert validity_info.invalid_since is not None |
|
181 | ||
182 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
183 |
mock_json = mock.Mock(status_code=200) |
|
184 |
requests_get.return_value = mock_json |
|
185 |
cell.save() |
|
186 |
assert ValidityInfo.objects.exists() is False |
|
187 | ||
188 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
189 |
mock_json = mock.Mock(status_code=500) |
|
190 |
requests_get.return_value = mock_json |
|
191 |
cell.save() |
|
192 |
assert ValidityInfo.objects.exists() is False |
|
193 | ||
194 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
195 |
mock_json = mock.Mock(status_code=400) |
|
196 |
requests_get.return_value = mock_json |
|
197 |
cell.save() |
|
198 |
validity_info = ValidityInfo.objects.latest('pk') |
|
199 |
assert validity_info.invalid_reason_code == 'data_url_invalid' |
|
200 |
assert validity_info.invalid_since is not None |
|
201 | ||
202 | ||
139 | 203 |
def test_link_list_cell(): |
140 | 204 |
page = Page.objects.create(title='example page', slug='example-page') |
141 | 205 | |
... | ... | |
167 | 231 |
assert '<ul><li><a href="http://example.net/#anchor">altertitle</a></li></ul>' in cell.render(ctx) |
168 | 232 | |
169 | 233 | |
234 |
def test_link_list_cell_validity(): |
|
235 |
page = Page.objects.create(title='example page', slug='example-page') |
|
236 | ||
237 |
cell = LinkListCell.objects.create(order=0, page=page) |
|
238 |
item = LinkCell.objects.create(page=page, placeholder=cell.link_placeholder, order=0) |
|
239 | ||
240 |
item.mark_as_valid() |
|
241 |
cell.check_validity() |
|
242 |
assert ValidityInfo.objects.exists() is False |
|
243 | ||
244 |
item.mark_as_invalid('foo_bar_reason') |
|
245 |
cell.check_validity() |
|
246 |
validity_info = ValidityInfo.objects.latest('pk') |
|
247 |
assert validity_info.invalid_reason_code == 'data_link_invalid' |
|
248 |
assert validity_info.invalid_since is not None |
|
249 | ||
250 | ||
251 |
def test_feed_cell_validity(context): |
|
252 |
page = Page.objects.create(title='example page', slug='example-page') |
|
253 |
cell = FeedCell.objects.create(page=page, placeholder='content', order=1) |
|
254 | ||
255 |
cell.get_cell_extra_context(context) |
|
256 |
validity_info = ValidityInfo.objects.latest('pk') |
|
257 |
assert validity_info.invalid_reason_code == 'data_url_not_defined' |
|
258 |
assert validity_info.invalid_since is not None |
|
259 | ||
260 |
cell.url = 'http://example.net/' |
|
261 |
cell.save() |
|
262 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
263 |
mock_json = mock.Mock(status_code=404) |
|
264 |
requests_get.return_value = mock_json |
|
265 |
cell.get_cell_extra_context(context) |
|
266 |
validity_info = ValidityInfo.objects.latest('pk') |
|
267 |
assert validity_info.invalid_reason_code == 'data_url_not_found' |
|
268 |
assert validity_info.invalid_since is not None |
|
269 | ||
270 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
271 |
mock_json = mock.Mock(status_code=200, content='') |
|
272 |
requests_get.return_value = mock_json |
|
273 |
cell.get_cell_extra_context(context) |
|
274 |
assert ValidityInfo.objects.exists() is False |
|
275 | ||
276 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
277 |
mock_json = mock.Mock(status_code=500) |
|
278 |
requests_get.return_value = mock_json |
|
279 |
cell.get_cell_extra_context(context) |
|
280 |
assert ValidityInfo.objects.exists() is False |
|
281 | ||
282 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
283 |
mock_json = mock.Mock(status_code=400) |
|
284 |
requests_get.return_value = mock_json |
|
285 |
cell.get_cell_extra_context(context) |
|
286 |
validity_info = ValidityInfo.objects.latest('pk') |
|
287 |
assert validity_info.invalid_reason_code == 'data_url_invalid' |
|
288 |
assert validity_info.invalid_since is not None |
|
289 | ||
290 | ||
170 | 291 |
def test_menu_cell(): |
171 | 292 |
Page.objects.all().delete() |
172 | 293 |
parent = Page.objects.create( |
... | ... | |
388 | 509 |
assert '/var1=foo/' in resp.text |
389 | 510 |
assert '/var2=bar/' in resp.text |
390 | 511 | |
512 | ||
513 |
def test_json_cell_validity(context): |
|
514 |
page = Page.objects.create(title='example page', slug='example-page') |
|
515 |
cell = JsonCell.objects.create( |
|
516 |
page=page, placeholder='content', order=1, |
|
517 |
varnames_str='var1, var2, ', |
|
518 |
url='http://foo?varone=[var1]&vartwo=[var2]', |
|
519 |
template_string='/var1={{var1}}/var2={{var2}}/') |
|
520 | ||
521 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
522 |
cell.get_cell_extra_context(context) |
|
523 |
assert requests_get.call_args_list == [] # invalid context |
|
524 |
assert ValidityInfo.objects.exists() is False |
|
525 | ||
526 |
context['var1'] = 'foo' |
|
527 |
context['var2'] = 'bar' |
|
528 |
context['synchronous'] = True # to get fresh content |
|
529 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
530 |
mock_json = mock.Mock(status_code=404) |
|
531 |
requests_get.side_effect = [mock_json] |
|
532 |
cell.get_cell_extra_context(context) |
|
533 |
validity_info = ValidityInfo.objects.latest('pk') |
|
534 |
assert validity_info.invalid_reason_code == 'data_url_not_found' |
|
535 |
assert validity_info.invalid_since is not None |
|
536 | ||
537 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
538 |
data = {'data': []} |
|
539 |
requests_get.return_value = mock_json_response(content=json.dumps(data), status_code=200) |
|
540 |
cell.get_cell_extra_context(context) |
|
541 |
assert ValidityInfo.objects.exists() is False |
|
542 | ||
543 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
544 |
mock_json = mock.Mock(status_code=500) |
|
545 |
requests_get.return_value = mock_json |
|
546 |
cell.get_cell_extra_context(context) |
|
547 |
assert ValidityInfo.objects.exists() is False |
|
548 | ||
549 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
550 |
mock_json = mock.Mock(status_code=400) |
|
551 |
requests_get.return_value = mock_json |
|
552 |
cell.get_cell_extra_context(context) |
|
553 |
validity_info = ValidityInfo.objects.latest('pk') |
|
554 |
assert validity_info.invalid_reason_code == 'data_url_invalid' |
|
555 |
assert validity_info.invalid_since is not None |
|
556 | ||
557 | ||
391 | 558 |
def test_config_json_cell(): |
392 | 559 |
page = Page(title='example page', slug='example-page') |
393 | 560 |
page.save() |
... | ... | |
508 | 675 |
assert requests_get.call_args[-1]['log_errors'] == False |
509 | 676 |
assert requests_get.call_args[-1]['timeout'] == 42 |
510 | 677 | |
678 | ||
679 |
def test_config_json_cell_validity(settings, context): |
|
680 |
settings.JSON_CELL_TYPES = { |
|
681 |
'test-config-json-cell': { |
|
682 |
'name': 'Foobar', |
|
683 |
'url': 'http://foo?varone=[var1]&vartwo=[var2]', |
|
684 |
'varnames': ['var1', 'var2'] |
|
685 |
}, |
|
686 |
} |
|
687 |
templates_settings = [settings.TEMPLATES[0].copy()] |
|
688 |
templates_settings[0]['DIRS'] = ['%s/templates-1' % os.path.abspath(os.path.dirname(__file__))] |
|
689 |
settings.TEMPLATES = templates_settings |
|
690 | ||
691 |
page = Page.objects.create(title='example page', slug='example-page') |
|
692 |
cell = ConfigJsonCell.objects.create( |
|
693 |
page=page, placeholder='content', order=1, |
|
694 |
key='test-config-json-cell', |
|
695 |
parameters={'identifier': 'plop'}) |
|
696 |
assert cell.varnames == ['var1', 'var2'] |
|
697 | ||
698 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
699 |
cell.get_cell_extra_context(context) |
|
700 |
assert requests_get.call_args_list == [] # invalid context |
|
701 |
assert ValidityInfo.objects.exists() is False |
|
702 | ||
703 |
context['var1'] = 'foo' |
|
704 |
context['var2'] = 'bar' |
|
705 |
context['synchronous'] = True # to get fresh content |
|
706 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
707 |
mock_json = mock.Mock(status_code=404) |
|
708 |
requests_get.side_effect = [mock_json] |
|
709 |
cell.get_cell_extra_context(context) |
|
710 |
validity_info = ValidityInfo.objects.latest('pk') |
|
711 |
assert validity_info.invalid_reason_code == 'data_url_not_found' |
|
712 |
assert validity_info.invalid_since is not None |
|
713 | ||
714 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
715 |
data = {'data': []} |
|
716 |
requests_get.return_value = mock_json_response(content=json.dumps(data), status_code=200) |
|
717 |
cell.get_cell_extra_context(context) |
|
718 |
assert ValidityInfo.objects.exists() is False |
|
719 | ||
720 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
721 |
mock_json = mock.Mock(status_code=500) |
|
722 |
requests_get.return_value = mock_json |
|
723 |
cell.get_cell_extra_context(context) |
|
724 |
assert ValidityInfo.objects.exists() is False |
|
725 | ||
726 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
727 |
mock_json = mock.Mock(status_code=400) |
|
728 |
requests_get.return_value = mock_json |
|
729 |
cell.get_cell_extra_context(context) |
|
730 |
validity_info = ValidityInfo.objects.latest('pk') |
|
731 |
assert validity_info.invalid_reason_code == 'data_url_invalid' |
|
732 |
assert validity_info.invalid_since is not None |
|
733 | ||
734 | ||
511 | 735 |
def test_json_force_async(): |
512 | 736 |
cell = JsonCellBase() |
513 | 737 |
cell.url = 'http://example.net/test-force-async' |
... | ... | |
807 | 1031 |
validity_info.invalid_since = now() - datetime.timedelta(days=2) |
808 | 1032 |
validity_info.save() |
809 | 1033 |
assert cell.is_visible() is False |
1034 | ||
1035 | ||
1036 |
def test_hourly(): |
|
1037 |
appconfig = apps.get_app_config('data') |
|
1038 |
page = Page.objects.create(title='xxx', slug='test_current_forms_cell_render', template_name='standard') |
|
1039 |
cell_classes = [c for c in appconfig.get_models() if c in get_cell_classes()] |
|
1040 |
for klass in cell_classes: |
|
1041 |
klass.objects.create(page=page, placeholder='content', order=0) |
|
1042 |
for klass in cell_classes: |
|
1043 |
if klass in [LinkCell, LinkListCell]: |
|
1044 |
with mock.patch('combo.data.models.%s.check_validity' % klass.__name__) as check_validity: |
|
1045 |
appconfig.hourly() |
|
1046 |
assert check_validity.call_args_list == [mock.call()] |
|
1047 |
else: |
|
1048 |
assert hasattr(klass, 'check_validity') is False |
tests/test_manager.py | ||
---|---|---|
182 | 182 |
resp = app.get('/manage/pages/%s/' % page.pk) |
183 | 183 |
assert '<span class="invalid">foo_bar_reason</span>' not in resp.text |
184 | 184 | |
185 |
cell2 = LinkListCell.objects.create(order=0, placeholder='content', page=page) |
|
186 |
item = LinkCell.objects.create(page=page, placeholder=cell2.link_placeholder, order=0) |
|
187 |
item.mark_as_invalid('foo_bar_reason') |
|
188 |
cell2.check_validity() |
|
189 |
resp = app.get('/manage/pages/%s/' % page.pk) |
|
190 |
assert '<span class="invalid">Invalid link</span>' in resp.text |
|
191 |
assert '<span class="invalid">foo_bar_reason</span>' in resp.text |
|
192 | ||
185 | 193 | |
186 | 194 |
def test_edit_page_optional_placeholder(app, admin_user): |
187 | 195 |
Page.objects.all().delete() |
... | ... | |
367 | 375 |
app.get('/manage/pages/%s/' % page.pk) # load once to populate caches |
368 | 376 |
with CaptureQueriesContext(connection) as ctx: |
369 | 377 |
app.get('/manage/pages/%s/' % page.pk) |
370 |
assert len(ctx.captured_queries) == 29
|
|
378 |
assert len(ctx.captured_queries) == 31
|
|
371 | 379 | |
372 | 380 | |
373 | 381 |
def test_delete_page(app, admin_user): |
... | ... | |
602 | 610 |
assert '<a href="/manage/pages/{}/">{}</a>'.format(page.pk, page.title) in resp.text |
603 | 611 |
assert '<a href="/manage/pages/{}/#cell-{}">{}</a>'.format(page.pk, cell.get_reference(), cell.get_label()) in resp.text |
604 | 612 | |
613 |
# cells from snapshot are not reported |
|
605 | 614 |
snapshot = PageSnapshot.objects.create(page=page) |
606 | 615 |
page.snapshot = snapshot |
607 | 616 |
page.save() |
608 | 617 |
resp = app.get('/manage/cells/invalid-report/') |
609 | 618 |
assert resp.context['object_list'] == [] |
610 | 619 | |
620 |
# cells used in LinkListCell are not reported |
|
621 |
page.snapshot = None |
|
622 |
page.save() |
|
623 |
cell2 = LinkListCell.objects.create(order=0, placeholder='content', page=page) |
|
624 |
item = LinkCell.objects.create(page=page, placeholder=cell2.link_placeholder, order=0) |
|
625 |
item.mark_as_invalid('foo_bar_reason') |
|
626 |
resp = app.get('/manage/cells/invalid-report/') |
|
627 |
assert resp.context['object_list'] == [cell] |
|
628 | ||
611 | 629 | |
612 | 630 |
def test_duplicate_page(app, admin_user): |
613 | 631 |
page = Page.objects.create(title='One', slug='one', template_name='standard', exclude_from_navigation=False) |
tests/test_public.py | ||
---|---|---|
180 | 180 |
assert resp.text.count('BARFOO') == 1 |
181 | 181 |
assert resp.text.count('BAR2FOO') == 1 |
182 | 182 |
queries_count_third = len(ctx.captured_queries) |
183 |
assert queries_count_third == queries_count_second |
|
183 |
# +2 for validity info of parent page |
|
184 |
assert queries_count_third == queries_count_second + 2 |
|
184 | 185 | |
185 | 186 |
with CaptureQueriesContext(connection) as ctx: |
186 | 187 |
resp = app.get('/second/third/fourth/', status=200) |
... | ... | |
188 | 189 |
assert resp.text.count('BAR2FOO') == 1 |
189 | 190 |
queries_count_fourth = len(ctx.captured_queries) |
190 | 191 |
# +1 for get_parents_and_self() |
191 |
assert queries_count_fourth == queries_count_second + 1 |
|
192 |
assert queries_count_fourth == queries_count_second + 2 + 1
|
|
192 | 193 | |
193 | 194 |
# check footer doesn't get duplicated in real index children |
194 | 195 |
page6 = Page(title='Sixth', slug='sixth', template_name='standard', parent=page_index) |
195 |
- |