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 | ||
---|---|---|
754 | 754 |
if save: |
755 | 755 |
self.save(update_fields=['invalid_reason_code', 'invalid_since']) |
756 | 756 | |
757 |
def set_validity_from_url( |
|
758 |
self, resp, |
|
759 |
not_found_code='url_not_found', invalid_code='url_invalid', |
|
760 |
save=True): |
|
761 |
if resp is None: |
|
762 |
# can not retrieve data, don't report cell as invalid |
|
763 |
self.mark_as_valid(save=save) |
|
764 |
elif resp.status_code == 404: |
|
765 |
self.mark_as_invalid(not_found_code, save=save) |
|
766 |
elif 400 <= resp.status_code < 500: |
|
767 |
# 4xx error, cell is invalid |
|
768 |
self.mark_as_invalid(invalid_code, save=save) |
|
769 |
else: |
|
770 |
# 2xx or 3xx: cell is valid |
|
771 |
# 5xx error: can not retrieve data, don't report cell as invalid |
|
772 |
self.mark_as_valid(save=save) |
|
773 | ||
757 | 774 |
def get_invalid_reason(self): |
758 | 775 |
if not self.invalid_since: |
759 | 776 |
return None |
... | ... | |
983 | 1000 |
edit_link_label = _('Edit link') |
984 | 1001 |
add_as_link_code = 'link' |
985 | 1002 | |
1003 |
invalid_reason_codes = { |
|
1004 |
'data_url_not_defined': _('No link set'), |
|
1005 |
'data_url_not_found': _('URL seems to unexist'), |
|
1006 |
'data_url_invalid': _('URL seems to be invalid'), |
|
1007 |
} |
|
1008 | ||
986 | 1009 |
class Meta: |
987 | 1010 |
verbose_name = _('Link') |
988 | 1011 | |
1012 |
def save(self, *args, **kwargs): |
|
1013 |
if 'update_fields' in kwargs: |
|
1014 |
# don't check validity |
|
1015 |
return super(LinkCell, self).save(*args, **kwargs) |
|
1016 | ||
1017 |
# check validity |
|
1018 |
self.check_validity(save=False) |
|
1019 |
return super(LinkCell, self).save(*args, **kwargs) |
|
1020 | ||
989 | 1021 |
def get_additional_label(self): |
990 | 1022 |
title = self.title |
991 | 1023 |
if not title and self.link_page: |
... | ... | |
994 | 1026 |
return None |
995 | 1027 |
return utils.ellipsize(title) |
996 | 1028 | |
1029 |
def get_url(self, context=None): |
|
1030 |
context = context or {} |
|
1031 |
if self.link_page: |
|
1032 |
url = self.link_page.get_online_url() |
|
1033 |
else: |
|
1034 |
url = utils.get_templated_url(self.url, context=context) |
|
1035 |
if self.anchor: |
|
1036 |
url += '#' + self.anchor |
|
1037 |
return url |
|
1038 | ||
997 | 1039 |
def get_cell_extra_context(self, context): |
998 | 1040 |
render_skeleton = context.get('render_skeleton') |
999 | 1041 |
request = context.get('request') |
1000 | 1042 |
extra_context = super(LinkCell, self).get_cell_extra_context(context) |
1001 | 1043 |
if self.link_page: |
1002 |
extra_context['url'] = self.link_page.get_online_url() |
|
1003 | 1044 |
extra_context['title'] = self.title or self.link_page.title |
1004 | 1045 |
else: |
1005 |
extra_context['url'] = utils.get_templated_url(self.url, context=context) |
|
1006 | 1046 |
extra_context['title'] = self.title or self.url |
1007 |
if self.anchor: |
|
1008 |
extra_context['url'] += '#' + self.anchor |
|
1009 |
if render_skeleton and not urlparse.urlparse(extra_context['url']).netloc: |
|
1047 |
url = self.get_url(context) |
|
1048 |
if render_skeleton and not urlparse.urlparse(url).netloc: |
|
1010 | 1049 |
# create full URL when used in a skeleton |
1011 |
extra_context['url'] = request.build_absolute_uri(extra_context['url']) |
|
1050 |
url = request.build_absolute_uri(url) |
|
1051 |
extra_context['url'] = url |
|
1012 | 1052 |
return extra_context |
1013 | 1053 | |
1014 | 1054 |
def get_default_form_class(self): |
... | ... | |
1026 | 1066 |
return [link_data] |
1027 | 1067 |
return [] |
1028 | 1068 | |
1069 |
def check_validity(self, save=True): |
|
1070 |
if self.link_page: |
|
1071 |
self.mark_as_valid(save=save) |
|
1072 |
return |
|
1073 |
if not self.url: |
|
1074 |
self.mark_as_invalid('data_url_not_defined', save=save) |
|
1075 |
return |
|
1076 | ||
1077 |
resp = None |
|
1078 |
try: |
|
1079 |
resp = requests.get(self.get_url()) |
|
1080 |
resp.raise_for_status() |
|
1081 |
except (requests.exceptions.RequestException): |
|
1082 |
pass |
|
1083 | ||
1084 |
self.set_validity_from_url(resp, not_found_code='data_url_not_found', invalid_code='data_url_invalid') |
|
1085 | ||
1029 | 1086 | |
1030 | 1087 |
@register_cell_class |
1031 | 1088 |
class LinkListCell(CellBase): |
... | ... | |
1034 | 1091 |
template_name = 'combo/link-list-cell.html' |
1035 | 1092 |
manager_form_template = 'combo/manager/link-list-cell-form.html' |
1036 | 1093 | |
1094 |
invalid_reason_codes = { |
|
1095 |
'data_link_invalid': _('Invalid link'), |
|
1096 |
} |
|
1097 | ||
1037 | 1098 |
class Meta: |
1038 | 1099 |
verbose_name = _('List of links') |
1039 | 1100 | |
... | ... | |
1097 | 1158 |
for link in self.get_items(): |
1098 | 1159 |
link.duplicate(page_target=new_cell.page, placeholder=new_cell.link_placeholder) |
1099 | 1160 | |
1161 |
def check_validity(self): |
|
1162 |
for link in self.get_items(): |
|
1163 |
if link.invalid_since is not None: |
|
1164 |
self.mark_as_invalid('data_link_invalid') |
|
1165 |
return |
|
1166 |
self.mark_as_valid() |
|
1167 | ||
1100 | 1168 | |
1101 | 1169 |
@register_cell_class |
1102 | 1170 |
class FeedCell(CellBase): |
1103 | 1171 |
title = models.CharField(_('Title'), max_length=150, blank=True) |
1104 | 1172 |
url = models.CharField(_('URL'), blank=True, max_length=200) |
1105 |
limit = models.PositiveSmallIntegerField(_('Maximum number of entries'), |
|
1106 |
null=True, blank=True) |
|
1173 |
limit = models.PositiveSmallIntegerField( |
|
1174 |
_('Maximum number of entries'), |
|
1175 |
null=True, blank=True) |
|
1107 | 1176 | |
1108 | 1177 |
manager_form_factory_kwargs = {'field_classes': {'url': TemplatableURLField}} |
1109 | 1178 |
template_name = 'combo/feed-cell.html' |
1110 | 1179 | |
1180 |
invalid_reason_codes = { |
|
1181 |
'data_url_not_defined': _('No URL set'), |
|
1182 |
'data_url_not_found': _('URL seems to unexist'), |
|
1183 |
'data_url_invalid': _('URL seems to be invalid'), |
|
1184 |
} |
|
1185 | ||
1111 | 1186 |
class Meta: |
1112 | 1187 |
verbose_name = _('RSS/Atom Feed') |
1113 | 1188 | |
... | ... | |
1116 | 1191 | |
1117 | 1192 |
def get_cell_extra_context(self, context): |
1118 | 1193 |
extra_context = super(FeedCell, self).get_cell_extra_context(context) |
1194 | ||
1195 |
if not self.url: |
|
1196 |
self.mark_as_invalid('data_url_not_defined') |
|
1197 |
return extra_context |
|
1198 | ||
1119 | 1199 |
if context.get('placeholder_search_mode'): |
1120 | 1200 |
# don't call webservices when we're just looking for placeholders |
1121 | 1201 |
return extra_context |
1202 | ||
1122 | 1203 |
cache_key = hashlib.md5(smart_bytes(self.url)).hexdigest() |
1123 | 1204 |
feed_content = cache.get(cache_key) |
1124 | 1205 |
if not feed_content: |
1206 |
feed_response = None |
|
1125 | 1207 |
try: |
1126 | 1208 |
feed_response = requests.get(utils.get_templated_url(self.url)) |
1127 | 1209 |
feed_response.raise_for_status() |
... | ... | |
1131 | 1213 |
if feed_response.status_code == 200: |
1132 | 1214 |
feed_content = feed_response.content |
1133 | 1215 |
cache.set(cache_key, feed_content, 600) |
1216 |
self.set_validity_from_url(feed_response, not_found_code='data_url_not_found', invalid_code='data_url_invalid') |
|
1134 | 1217 |
if feed_content: |
1135 | 1218 |
extra_context['feed'] = feedparser.parse(feed_content) |
1136 | 1219 |
if self.limit: |
... | ... | |
1208 | 1291 | |
1209 | 1292 |
_json_content = None |
1210 | 1293 | |
1294 |
invalid_reason_codes = { |
|
1295 |
'data_url_not_found': _('URL seems to unexist'), |
|
1296 |
'data_url_invalid': _('URL seems to be invalid'), |
|
1297 |
} |
|
1298 | ||
1211 | 1299 |
class Meta: |
1212 | 1300 |
abstract = True |
1213 | 1301 | |
... | ... | |
1259 | 1347 |
if not url: |
1260 | 1348 |
continue |
1261 | 1349 |
try: |
1262 |
json_response = utils.requests.get(url, |
|
1263 |
headers={'Accept': 'application/json'}, |
|
1264 |
remote_service='auto', |
|
1265 |
cache_duration=data_url_dict.get('cache_duration', self.cache_duration), |
|
1266 |
without_user=True, |
|
1267 |
raise_if_not_cached=not(context.get('synchronous')), |
|
1268 |
invalidate_cache=invalidate_cache, |
|
1269 |
log_errors=log_errors, |
|
1270 |
timeout=data_url_dict.get('timeout', self.timeout), |
|
1271 |
django_request=context.get('request'), |
|
1272 |
) |
|
1350 |
json_response = utils.requests.get( |
|
1351 |
url, |
|
1352 |
headers={'Accept': 'application/json'}, |
|
1353 |
remote_service='auto', |
|
1354 |
cache_duration=data_url_dict.get('cache_duration', self.cache_duration), |
|
1355 |
without_user=True, |
|
1356 |
raise_if_not_cached=not(context.get('synchronous')), |
|
1357 |
invalidate_cache=invalidate_cache, |
|
1358 |
log_errors=log_errors, |
|
1359 |
timeout=data_url_dict.get('timeout', self.timeout), |
|
1360 |
django_request=context.get('request'), |
|
1361 |
) |
|
1273 | 1362 |
except requests.RequestException as e: |
1274 | 1363 |
extra_context[data_key + '_status'] = -1 |
1275 | 1364 |
extra_context[data_key + '_error'] = force_text(e) |
... | ... | |
1303 | 1392 |
# templated URLs |
1304 | 1393 |
context[data_key] = extra_context[data_key] |
1305 | 1394 | |
1395 |
returns = [extra_context.get(d['key'] + '_status') for d in data_urls] |
|
1396 |
returns = set([s for s in returns if s is not None]) |
|
1397 |
if returns and 200 not in returns: # not a single valid answer |
|
1398 |
if 404 in returns: |
|
1399 |
self.mark_as_invalid('data_url_not_found') |
|
1400 |
elif any([400 <= r < 500 for r in returns]): |
|
1401 |
# at least 4xx errors, report the cell as invalid |
|
1402 |
self.mark_as_invalid('data_url_invalid') |
|
1403 |
else: |
|
1404 |
# 2xx or 3xx: cell is valid |
|
1405 |
# 5xx error: can not retrieve data, don't report cell as invalid |
|
1406 |
self.mark_as_valid() |
|
1407 |
else: |
|
1408 |
self.mark_as_valid() |
|
1409 | ||
1306 | 1410 |
# keep cache of first response as it may be used to find the |
1307 | 1411 |
# appropriate template. |
1308 | 1412 |
self._json_content = extra_context[self.first_data_key] |
combo/data/templates/combo/manager/link-list-cell-form.html | ||
---|---|---|
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 |
{% if link.invalid_since %} |
|
17 |
<span class="invalid">/!\ {{ link.get_invalid_reason }}</span> |
|
18 |
{% endif %} |
|
19 |
</span> |
|
15 | 20 |
<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 | 21 |
<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 | 22 |
</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: red; |
117 | 119 |
} |
118 | 120 |
combo/manager/views.py | ||
---|---|---|
97 | 97 |
select_related=['page'], |
98 | 98 |
page__snapshot__isnull=True, |
99 | 99 |
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): |
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 |
|
10 |
JsonCell, ConfigJsonCell, LinkListCell, FeedCell
|
|
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 |
assert cell.invalid_reason_code == 'data_url_not_defined' |
|
159 |
assert cell.invalid_since is not None |
|
160 | ||
161 |
# internal link - no check |
|
162 |
cell.link_page = page |
|
163 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
164 |
mock_json = mock.Mock(status_code=404) |
|
165 |
requests_get.return_value = mock_json |
|
166 |
cell.save() |
|
167 |
assert requests_get.call_args_list == [] |
|
168 |
assert cell.invalid_reason_code is None |
|
169 |
assert cell.invalid_since is None |
|
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 |
assert cell.invalid_reason_code == 'data_url_not_found' |
|
179 |
assert cell.invalid_since is not None |
|
180 | ||
181 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
182 |
mock_json = mock.Mock(status_code=200) |
|
183 |
requests_get.return_value = mock_json |
|
184 |
cell.save() |
|
185 |
assert cell.invalid_reason_code is None |
|
186 |
assert cell.invalid_since is None |
|
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 cell.invalid_reason_code is None |
|
193 |
assert cell.invalid_since is None |
|
194 | ||
195 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
196 |
mock_json = mock.Mock(status_code=400) |
|
197 |
requests_get.return_value = mock_json |
|
198 |
cell.save() |
|
199 |
assert cell.invalid_reason_code == 'data_url_invalid' |
|
200 |
assert cell.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 cell.invalid_reason_code is None |
|
243 |
assert cell.invalid_since is None |
|
244 | ||
245 |
item.mark_as_invalid('foo_bar_reason') |
|
246 |
cell.check_validity() |
|
247 |
assert cell.invalid_reason_code == 'data_link_invalid' |
|
248 |
assert cell.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 |
assert cell.invalid_reason_code == 'data_url_not_defined' |
|
257 |
assert cell.invalid_since is not None |
|
258 | ||
259 |
cell.url = 'http://example.net/' |
|
260 |
cell.save() |
|
261 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
262 |
mock_json = mock.Mock(status_code=404) |
|
263 |
requests_get.return_value = mock_json |
|
264 |
cell.get_cell_extra_context(context) |
|
265 |
assert cell.invalid_reason_code == 'data_url_not_found' |
|
266 |
assert cell.invalid_since is not None |
|
267 | ||
268 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
269 |
mock_json = mock.Mock(status_code=200, content='') |
|
270 |
requests_get.return_value = mock_json |
|
271 |
cell.get_cell_extra_context(context) |
|
272 |
assert cell.invalid_reason_code is None |
|
273 |
assert cell.invalid_since is None |
|
274 | ||
275 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
276 |
mock_json = mock.Mock(status_code=500) |
|
277 |
requests_get.return_value = mock_json |
|
278 |
cell.get_cell_extra_context(context) |
|
279 |
assert cell.invalid_reason_code is None |
|
280 |
assert cell.invalid_since is None |
|
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 |
assert cell.invalid_reason_code == 'data_url_invalid' |
|
287 |
assert cell.invalid_since is not None |
|
288 | ||
289 | ||
170 | 290 |
def test_menu_cell(): |
171 | 291 |
Page.objects.all().delete() |
172 | 292 |
parent = Page.objects.create( |
... | ... | |
388 | 508 |
assert '/var1=foo/' in resp.text |
389 | 509 |
assert '/var2=bar/' in resp.text |
390 | 510 | |
511 | ||
512 |
def test_json_cell_validity(context): |
|
513 |
page = Page.objects.create(title='example page', slug='example-page') |
|
514 |
cell = JsonCell.objects.create( |
|
515 |
page=page, placeholder='content', order=1, |
|
516 |
varnames_str='var1, var2, ', |
|
517 |
url='http://foo?varone=[var1]&vartwo=[var2]', |
|
518 |
template_string='/var1={{var1}}/var2={{var2}}/') |
|
519 | ||
520 |
with mock.patch('combo.data.models.requests.get') as requests_get: |
|
521 |
cell.get_cell_extra_context(context) |
|
522 |
assert requests_get.call_args_list == [] # invalid context |
|
523 |
assert cell.invalid_reason_code is None |
|
524 |
assert cell.invalid_since is None |
|
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 |
assert cell.invalid_reason_code == 'data_url_not_found' |
|
534 |
assert cell.invalid_since is not None |
|
535 | ||
536 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
537 |
data = {'data': []} |
|
538 |
requests_get.return_value = mock_json_response(content=json.dumps(data), status_code=200) |
|
539 |
cell.get_cell_extra_context(context) |
|
540 |
assert cell.invalid_reason_code is None |
|
541 |
assert cell.invalid_since is None |
|
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 cell.invalid_reason_code is None |
|
548 |
assert cell.invalid_since is None |
|
549 | ||
550 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
551 |
mock_json = mock.Mock(status_code=400) |
|
552 |
requests_get.return_value = mock_json |
|
553 |
cell.get_cell_extra_context(context) |
|
554 |
assert cell.invalid_reason_code == 'data_url_invalid' |
|
555 |
assert cell.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 cell.invalid_reason_code is None |
|
702 |
assert cell.invalid_since is None |
|
703 | ||
704 |
context['var1'] = 'foo' |
|
705 |
context['var2'] = 'bar' |
|
706 |
context['synchronous'] = True # to get fresh content |
|
707 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
708 |
mock_json = mock.Mock(status_code=404) |
|
709 |
requests_get.side_effect = [mock_json] |
|
710 |
cell.get_cell_extra_context(context) |
|
711 |
assert cell.invalid_reason_code == 'data_url_not_found' |
|
712 |
assert cell.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 cell.invalid_reason_code is None |
|
719 |
assert cell.invalid_since is None |
|
720 | ||
721 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
722 |
mock_json = mock.Mock(status_code=500) |
|
723 |
requests_get.return_value = mock_json |
|
724 |
cell.get_cell_extra_context(context) |
|
725 |
assert cell.invalid_reason_code is None |
|
726 |
assert cell.invalid_since is None |
|
727 | ||
728 |
with mock.patch('combo.utils.requests.get') as requests_get: |
|
729 |
mock_json = mock.Mock(status_code=400) |
|
730 |
requests_get.return_value = mock_json |
|
731 |
cell.get_cell_extra_context(context) |
|
732 |
assert cell.invalid_reason_code == 'data_url_invalid' |
|
733 |
assert cell.invalid_since is not None |
|
734 | ||
735 | ||
511 | 736 |
def test_json_force_async(): |
512 | 737 |
cell = JsonCellBase() |
513 | 738 |
cell.url = 'http://example.net/test-force-async' |
... | ... | |
803 | 1028 |
# invalid cell since two days |
804 | 1029 |
cell.invalid_since = now() - datetime.timedelta(days=2) |
805 | 1030 |
assert cell.is_visible() is False |
1031 | ||
1032 | ||
1033 |
def test_hourly(): |
|
1034 |
appconfig = apps.get_app_config('data') |
|
1035 |
page = Page.objects.create(title='xxx', slug='test_current_forms_cell_render', template_name='standard') |
|
1036 |
cell_classes = [c for c in appconfig.get_models() if c in get_cell_classes()] |
|
1037 |
for klass in cell_classes: |
|
1038 |
klass.objects.create(page=page, placeholder='content', order=0) |
|
1039 |
for klass in cell_classes: |
|
1040 |
if klass in [LinkCell, LinkListCell]: |
|
1041 |
with mock.patch('combo.data.models.%s.check_validity' % klass.__name__) as check_validity: |
|
1042 |
appconfig.hourly() |
|
1043 |
assert check_validity.call_args_list == [mock.call()] |
|
1044 |
else: |
|
1045 |
assert hasattr(klass, 'check_validity') is False |
tests/test_manager.py | ||
---|---|---|
176 | 176 |
resp = app.get('/manage/pages/%s/' % page.pk) |
177 | 177 |
assert '<span class="invalid">/!\\ foo_bar_reason</span>' not in resp.text |
178 | 178 | |
179 |
cell2 = LinkListCell.objects.create(order=0, placeholder='content', page=page) |
|
180 |
item = LinkCell.objects.create(page=page, placeholder=cell2.link_placeholder, order=0) |
|
181 |
item.mark_as_invalid('foo_bar_reason') |
|
182 |
cell2.check_validity() |
|
183 |
resp = app.get('/manage/pages/%s/' % page.pk) |
|
184 |
assert '<span class="invalid">/!\\ Invalid link</span>' in resp.text |
|
185 |
assert '<span class="invalid">/!\\ foo_bar_reason</span>' in resp.text |
|
186 | ||
179 | 187 | |
180 | 188 |
def test_edit_page_optional_placeholder(app, admin_user): |
181 | 189 |
Page.objects.all().delete() |
... | ... | |
596 | 604 |
assert '<a href="/manage/pages/{}/">{}</a>'.format(page.pk, page.title) in resp.text |
597 | 605 |
assert '<a href="/manage/pages/{}/#cell-{}">{}</a>'.format(page.pk, cell.get_reference(), cell.get_label()) in resp.text |
598 | 606 | |
607 |
# cells from snapshot are not reported |
|
599 | 608 |
snapshot = PageSnapshot.objects.create(page=page) |
600 | 609 |
page.snapshot = snapshot |
601 | 610 |
page.save() |
602 | 611 |
resp = app.get('/manage/cells/invalid-report/') |
603 | 612 |
assert resp.context['object_list'] == [] |
604 | 613 | |
614 |
# cells used in LinkListCell are not reported |
|
615 |
page.snapshot = None |
|
616 |
page.save() |
|
617 |
cell2 = LinkListCell.objects.create(order=0, placeholder='content', page=page) |
|
618 |
item = LinkCell.objects.create(page=page, placeholder=cell2.link_placeholder, order=0) |
|
619 |
item.mark_as_invalid('foo_bar_reason') |
|
620 |
resp = app.get('/manage/cells/invalid-report/') |
|
621 |
assert resp.context['object_list'] == [cell] |
|
622 | ||
605 | 623 | |
606 | 624 |
def test_duplicate_page(app, admin_user): |
607 | 625 |
page = Page.objects.create(title='One', slug='one', template_name='standard', exclude_from_navigation=False) |
608 |
- |