0001-datasource-collect-agendas-48282.patch
tests/conftest.py | ||
---|---|---|
29 | 29 |
return value |
30 | 30 | |
31 | 31 | |
32 |
@pytest.fixture |
|
33 |
def chrono_url(request, pub): |
|
34 |
return site_options(request, pub, 'options', 'chrono_url', 'http://chrono.example.net/') |
|
35 | ||
36 | ||
32 | 37 |
@pytest.fixture |
33 | 38 |
def fargo_url(request, pub): |
34 | 39 |
return site_options(request, pub, 'options', 'fargo_url', 'http://fargo.example.net/') |
tests/test_datasource_chrono.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 | ||
3 |
import pytest |
|
4 |
import json |
|
5 |
import shutil |
|
6 | ||
7 |
from django.utils.six import StringIO |
|
8 | ||
9 |
from quixote import cleanup |
|
10 |
from wcs import fields |
|
11 |
from wcs.data_sources import NamedDataSource, build_agenda_datasources, collect_agenda_data |
|
12 |
from wcs.formdef import FormDef |
|
13 |
from wcs.qommon.misc import ConnectionError |
|
14 |
from wcs.qommon.http_request import HTTPRequest |
|
15 | ||
16 |
import mock |
|
17 |
from utilities import create_temporary_pub |
|
18 | ||
19 | ||
20 |
def setup_module(module): |
|
21 |
cleanup() |
|
22 | ||
23 |
global pub |
|
24 | ||
25 |
pub = create_temporary_pub() |
|
26 |
pub.cfg['debug'] = {'logger': True} |
|
27 |
pub.write_cfg() |
|
28 |
pub.set_config() |
|
29 | ||
30 | ||
31 |
def teardown_module(module): |
|
32 |
shutil.rmtree(pub.APP_DIR) |
|
33 | ||
34 | ||
35 |
@pytest.fixture |
|
36 |
def pub(request): |
|
37 |
req = HTTPRequest(None, {'SERVER_NAME': 'example.net', 'SCRIPT_NAME': ''}) |
|
38 |
pub.set_app_dir(req) |
|
39 |
pub._set_request(req) |
|
40 |
return pub |
|
41 | ||
42 | ||
43 |
AGENDA_EVENTS_DATA = [ |
|
44 |
{ |
|
45 |
"api": { |
|
46 |
"datetimes_url": "http://chrono.example.net/api/agenda/events-A/datetimes/", |
|
47 |
}, |
|
48 |
"id": "events-A", |
|
49 |
"kind": "events", |
|
50 |
"text": "Events A", |
|
51 |
}, |
|
52 |
{ |
|
53 |
"api": { |
|
54 |
"datetimes_url": "http://chrono.example.net/api/agenda/events-B/datetimes/", |
|
55 |
}, |
|
56 |
"id": "events-B", |
|
57 |
"kind": "events", |
|
58 |
"text": "Events B", |
|
59 |
}, |
|
60 |
] |
|
61 | ||
62 | ||
63 |
AGENDA_MEETINGS_DATA = [ |
|
64 |
{ |
|
65 |
"api": {"meetings_url": "http://chrono.example.net/api/agenda/meetings-A/meetings/"}, |
|
66 |
"id": "meetings-A", |
|
67 |
"kind": "meetings", |
|
68 |
"text": "Meetings A", |
|
69 |
}, |
|
70 |
{ |
|
71 |
"api": { |
|
72 |
"meetings_url": "http://chrono.example.net/api/agenda/virtual-B/meetings/", |
|
73 |
}, |
|
74 |
"id": "virtual-B", |
|
75 |
"kind": "virtual", |
|
76 |
"text": "Virtual B", |
|
77 |
}, |
|
78 |
] |
|
79 | ||
80 | ||
81 |
AGENDA_MEETING_TYPES_DATA = { |
|
82 |
'meetings-A': [ |
|
83 |
{ |
|
84 |
"api": { |
|
85 |
"datetimes_url": "http://chrono.example.net/api/agenda/meetings-A/meetings/mt-1/datetimes/" |
|
86 |
}, |
|
87 |
"id": "mt-1", |
|
88 |
"text": "MT 1", |
|
89 |
}, |
|
90 |
{ |
|
91 |
"api": { |
|
92 |
"datetimes_url": "http://chrono.example.net/api/agenda/meetings-A/meetings/mt-2/datetimes/" |
|
93 |
}, |
|
94 |
"id": "mt-2", |
|
95 |
"text": "MT 2", |
|
96 |
}, |
|
97 |
], |
|
98 |
'virtual-B': [ |
|
99 |
{ |
|
100 |
"api": { |
|
101 |
"datetimes_url": "http://chrono.example.net/api/agenda/virtual-B/meetings/mt-3/datetimes/" |
|
102 |
}, |
|
103 |
"id": "mt-3", |
|
104 |
"text": "MT 3", |
|
105 |
}, |
|
106 |
], |
|
107 |
} |
|
108 | ||
109 | ||
110 |
@mock.patch('wcs.qommon.misc.urlopen') |
|
111 |
def test_collect_agenda_data(urlopen, pub, chrono_url): |
|
112 |
pub.load_site_options() |
|
113 |
NamedDataSource.wipe() |
|
114 | ||
115 |
urlopen.side_effect = lambda *args: StringIO('{"data": []}') |
|
116 |
assert collect_agenda_data(pub) == [] |
|
117 |
assert urlopen.call_args_list == [mock.call('http://chrono.example.net/api/agenda/')] |
|
118 | ||
119 |
urlopen.side_effect = ConnectionError |
|
120 |
urlopen.reset_mock() |
|
121 |
assert collect_agenda_data(pub) is None |
|
122 |
assert urlopen.call_args_list == [mock.call('http://chrono.example.net/api/agenda/')] |
|
123 | ||
124 |
# events agenda |
|
125 |
urlopen.side_effect = lambda *args: StringIO(json.dumps({"data": AGENDA_EVENTS_DATA})) |
|
126 |
urlopen.reset_mock() |
|
127 |
assert collect_agenda_data(pub) == [ |
|
128 |
{'text': 'Events A', 'url': 'http://chrono.example.net/api/agenda/events-A/datetimes/'}, |
|
129 |
{'text': 'Events B', 'url': 'http://chrono.example.net/api/agenda/events-B/datetimes/'}, |
|
130 |
] |
|
131 |
assert urlopen.call_args_list == [mock.call('http://chrono.example.net/api/agenda/')] |
|
132 | ||
133 |
# meetings agenda |
|
134 |
urlopen.side_effect = [ |
|
135 |
StringIO(json.dumps({"data": AGENDA_MEETINGS_DATA})), |
|
136 |
StringIO(json.dumps({"data": AGENDA_MEETING_TYPES_DATA['meetings-A']})), |
|
137 |
StringIO(json.dumps({"data": AGENDA_MEETING_TYPES_DATA['virtual-B']})), |
|
138 |
] |
|
139 |
urlopen.reset_mock() |
|
140 |
assert collect_agenda_data(pub) == [ |
|
141 |
{ |
|
142 |
'text': 'Meetings A - Slot types', |
|
143 |
'url': 'http://chrono.example.net/api/agenda/meetings-A/meetings/', |
|
144 |
}, |
|
145 |
{ |
|
146 |
'text': 'Meetings A - Slots of type MT 1', |
|
147 |
'url': 'http://chrono.example.net/api/agenda/meetings-A/meetings/mt-1/datetimes/', |
|
148 |
}, |
|
149 |
{ |
|
150 |
'text': 'Meetings A - Slots of type MT 2', |
|
151 |
'url': 'http://chrono.example.net/api/agenda/meetings-A/meetings/mt-2/datetimes/', |
|
152 |
}, |
|
153 |
{'text': 'Virtual B - Slot types', 'url': 'http://chrono.example.net/api/agenda/virtual-B/meetings/'}, |
|
154 |
{ |
|
155 |
'text': 'Virtual B - Slots of type MT 3', |
|
156 |
'url': 'http://chrono.example.net/api/agenda/virtual-B/meetings/mt-3/datetimes/', |
|
157 |
}, |
|
158 |
] |
|
159 |
assert urlopen.call_args_list == [ |
|
160 |
mock.call('http://chrono.example.net/api/agenda/'), |
|
161 |
mock.call('http://chrono.example.net/api/agenda/meetings-A/meetings/'), |
|
162 |
mock.call('http://chrono.example.net/api/agenda/virtual-B/meetings/'), |
|
163 |
] |
|
164 | ||
165 |
# if meeting types could not be collected |
|
166 |
urlopen.side_effect = [ |
|
167 |
StringIO(json.dumps({"data": AGENDA_MEETINGS_DATA})), |
|
168 |
StringIO(json.dumps({"data": AGENDA_MEETING_TYPES_DATA['meetings-A']})), |
|
169 |
ConnectionError, |
|
170 |
] |
|
171 |
urlopen.reset_mock() |
|
172 |
assert collect_agenda_data(pub) is None |
|
173 |
assert urlopen.call_args_list == [ |
|
174 |
mock.call('http://chrono.example.net/api/agenda/'), |
|
175 |
mock.call('http://chrono.example.net/api/agenda/meetings-A/meetings/'), |
|
176 |
mock.call('http://chrono.example.net/api/agenda/virtual-B/meetings/'), |
|
177 |
] |
|
178 | ||
179 |
urlopen.side_effect = [ |
|
180 |
StringIO(json.dumps({"data": AGENDA_MEETINGS_DATA})), |
|
181 |
ConnectionError, |
|
182 |
] |
|
183 |
urlopen.reset_mock() |
|
184 |
assert collect_agenda_data(pub) is None |
|
185 |
assert urlopen.call_args_list == [ |
|
186 |
mock.call('http://chrono.example.net/api/agenda/'), |
|
187 |
mock.call('http://chrono.example.net/api/agenda/meetings-A/meetings/'), |
|
188 |
] |
|
189 | ||
190 | ||
191 |
@mock.patch('wcs.data_sources.collect_agenda_data') |
|
192 |
def test_build_agenda_datasources_without_chrono(mock_collect, pub): |
|
193 |
NamedDataSource.wipe() |
|
194 |
build_agenda_datasources(pub) |
|
195 |
assert mock_collect.call_args_list == [] |
|
196 |
assert NamedDataSource.count() == 0 |
|
197 | ||
198 | ||
199 |
@mock.patch('wcs.data_sources.collect_agenda_data') |
|
200 |
def test_build_agenda_datasources(mock_collect, pub, chrono_url): |
|
201 |
pub.load_site_options() |
|
202 |
NamedDataSource.wipe() |
|
203 | ||
204 |
# create some datasource, with same urls, but not external |
|
205 |
ds = NamedDataSource(name='Foo A') |
|
206 |
ds.data_source = {'type': 'json', 'value': 'http://chrono.example.net/api/agenda/events-A/datetimes/'} |
|
207 |
ds.store() |
|
208 |
ds = NamedDataSource(name='Foo B') |
|
209 |
ds.data_source = {'type': 'json', 'value': 'http://chrono.example.net/api/agenda/events-B/datetimes/'} |
|
210 |
ds.store() |
|
211 | ||
212 |
# error during collect |
|
213 |
mock_collect.return_value = None |
|
214 |
build_agenda_datasources(pub) |
|
215 |
assert NamedDataSource.count() == 2 # no changes |
|
216 | ||
217 |
# no agenda datasource found in chrono |
|
218 |
mock_collect.return_value = [] |
|
219 |
build_agenda_datasources(pub) |
|
220 |
assert NamedDataSource.count() == 2 # no changes |
|
221 | ||
222 |
# 2 agenda datasources found |
|
223 |
mock_collect.return_value = [ |
|
224 |
{'text': 'Events A', 'url': 'http://chrono.example.net/api/agenda/events-A/datetimes/'}, |
|
225 |
{'text': 'Events B', 'url': 'http://chrono.example.net/api/agenda/events-B/datetimes/'}, |
|
226 |
] |
|
227 | ||
228 |
# agenda datasources does not exist, create them |
|
229 |
build_agenda_datasources(pub) |
|
230 |
assert NamedDataSource.count() == 2 + 2 |
|
231 |
datasource1 = NamedDataSource.get(2 + 1) |
|
232 |
datasource2 = NamedDataSource.get(2 + 2) |
|
233 |
assert datasource1.name == 'Events A' |
|
234 |
assert datasource1.external == 'agenda' |
|
235 |
assert datasource1.external_status is None |
|
236 |
assert datasource1.data_source == { |
|
237 |
'type': 'json', |
|
238 |
'value': 'http://chrono.example.net/api/agenda/events-A/datetimes/', |
|
239 |
} |
|
240 |
assert datasource2.name == 'Events B' |
|
241 |
assert datasource2.external == 'agenda' |
|
242 |
assert datasource2.external_status is None |
|
243 |
assert datasource2.data_source == { |
|
244 |
'type': 'json', |
|
245 |
'value': 'http://chrono.example.net/api/agenda/events-B/datetimes/', |
|
246 |
} |
|
247 | ||
248 |
# again, datasources already exist, but name is wrong => change it |
|
249 |
datasource1.name = 'wrong' |
|
250 |
datasource1.store() |
|
251 |
datasource2.name = 'wrong again' |
|
252 |
datasource2.store() |
|
253 |
build_agenda_datasources(pub) |
|
254 |
assert NamedDataSource.count() == 2 + 2 |
|
255 |
datasource1 = NamedDataSource.get(2 + 1) |
|
256 |
datasource2 = NamedDataSource.get(2 + 2) |
|
257 |
assert datasource1.name == 'Events A' |
|
258 |
assert datasource2.name == 'Events B' |
|
259 | ||
260 |
# all datasources does not exist, one is unknown |
|
261 |
datasource1.data_source['value'] = 'http://chrono.example.net/api/agenda/events-FOOBAR/datetimes/' |
|
262 |
datasource1.store() |
|
263 | ||
264 |
build_agenda_datasources(pub) |
|
265 |
assert NamedDataSource.count() == 2 + 2 |
|
266 |
# first datasource was deleted, because not found and not used |
|
267 |
datasource2 = NamedDataSource.get(2 + 2) |
|
268 |
datasource3 = NamedDataSource.get(2 + 3) |
|
269 |
assert datasource2.name == 'Events B' |
|
270 |
assert datasource2.external == 'agenda' |
|
271 |
assert datasource2.external_status is None |
|
272 |
assert datasource2.data_source == { |
|
273 |
'type': 'json', |
|
274 |
'value': 'http://chrono.example.net/api/agenda/events-B/datetimes/', |
|
275 |
} |
|
276 |
assert datasource3.name == 'Events A' |
|
277 |
assert datasource3.external == 'agenda' |
|
278 |
assert datasource3.external_status is None |
|
279 |
assert datasource3.data_source == { |
|
280 |
'type': 'json', |
|
281 |
'value': 'http://chrono.example.net/api/agenda/events-A/datetimes/', |
|
282 |
} |
|
283 | ||
284 |
# all datasources does not exist, one is unknown but used |
|
285 |
FormDef.wipe() |
|
286 |
formdef = FormDef() |
|
287 |
formdef.name = 'foobar' |
|
288 |
formdef.fields = [ |
|
289 |
fields.ItemField(id='0', label='string', type='item', data_source={'type': datasource3.slug}), |
|
290 |
] |
|
291 |
formdef.store() |
|
292 |
assert datasource3.is_used_in_formdef(formdef) |
|
293 |
datasource3.data_source['value'] = 'http://chrono.example.net/api/agenda/events-FOOBAR/datetimes/' |
|
294 |
datasource3.store() |
|
295 |
build_agenda_datasources(pub) |
|
296 |
assert NamedDataSource.count() == 2 + 3 |
|
297 |
datasource2 = NamedDataSource.get(2 + 2) |
|
298 |
datasource3 = NamedDataSource.get(2 + 3) |
|
299 |
datasource4 = NamedDataSource.get(2 + 4) |
|
300 |
assert datasource2.name == 'Events B' |
|
301 |
assert datasource2.external == 'agenda' |
|
302 |
assert datasource2.external_status is None |
|
303 |
assert datasource2.data_source == { |
|
304 |
'type': 'json', |
|
305 |
'value': 'http://chrono.example.net/api/agenda/events-B/datetimes/', |
|
306 |
} |
|
307 |
assert datasource3.name == 'Events A' |
|
308 |
assert datasource3.external == 'agenda' |
|
309 |
assert datasource3.external_status == 'not-found' |
|
310 |
assert datasource3.data_source == { |
|
311 |
'type': 'json', |
|
312 |
'value': 'http://chrono.example.net/api/agenda/events-FOOBAR/datetimes/', |
|
313 |
} |
|
314 |
assert datasource4.name == 'Events A' |
|
315 |
assert datasource4.external == 'agenda' |
|
316 |
assert datasource4.external_status is None |
|
317 |
assert datasource4.data_source == { |
|
318 |
'type': 'json', |
|
319 |
'value': 'http://chrono.example.net/api/agenda/events-A/datetimes/', |
|
320 |
} |
|
321 | ||
322 |
# a datasource was marked as unknown |
|
323 |
datasource4.external_status = 'not-found' |
|
324 |
datasource4.store() |
|
325 |
build_agenda_datasources(pub) |
|
326 |
assert NamedDataSource.count() == 2 + 3 |
|
327 |
datasource4 = NamedDataSource.get(2 + 4) |
|
328 |
assert datasource4.external_status is None |
wcs/admin/data_sources.py | ||
---|---|---|
43 | 43 |
if self.datasource is None: |
44 | 44 |
self.datasource = NamedDataSource() |
45 | 45 | |
46 |
def is_used(self): |
|
47 |
for formdef in get_formdefs_of_all_kinds(): |
|
48 |
if self.datasource.is_used_in_formdef(formdef): |
|
49 |
return True |
|
50 |
return False |
|
51 | ||
52 | 46 |
def get_form(self): |
53 | 47 |
form = Form(enctype='multipart/form-data', advanced_label=_('Additional options')) |
54 | 48 |
form.add(StringWidget, 'name', title=_('Name'), required=True, size=30, value=self.datasource.name) |
... | ... | |
139 | 133 |
'data-dynamic-display-value': 'geojson', |
140 | 134 |
}, |
141 | 135 |
) |
142 |
if self.datasource.slug and not self.is_used(): |
|
136 |
if self.datasource.slug and not self.datasource.is_used():
|
|
143 | 137 |
form.add( |
144 | 138 |
StringWidget, |
145 | 139 |
'slug', |
... | ... | |
332 | 326 | |
333 | 327 |
def delete(self): |
334 | 328 |
form = Form(enctype='multipart/form-data') |
335 |
if not self.datasource_ui.is_used():
|
|
329 |
if not self.datasource.is_used(): |
|
336 | 330 |
form.widgets.append( |
337 | 331 |
HtmlWidget('<p>%s</p>' % _('You are about to irrevocably delete this data source.')) |
338 | 332 |
) |
wcs/ctl/check_hobos.py | ||
---|---|---|
475 | 475 |
continue |
476 | 476 |
if service.get('service-id') == 'fargo': |
477 | 477 |
config.set('options', 'fargo_url', service.get('base_url')) |
478 |
elif service.get('service-id') == 'chrono': |
|
479 |
config.set('options', 'chrono_url', service.get('base_url')) |
|
478 | 480 | |
479 | 481 |
try: |
480 | 482 |
portal_agent_url = config.get('variables', 'portal_agent_url') |
wcs/data_sources.py | ||
---|---|---|
22 | 22 |
from django.utils import six |
23 | 23 |
from django.utils.encoding import force_text, force_bytes |
24 | 24 |
from django.utils.six.moves.urllib import parse as urllib |
25 |
from django.utils.six.moves.urllib import parse as urlparse |
|
25 | 26 | |
26 | 27 |
from quixote import get_publisher, get_request, get_session |
27 | 28 |
from quixote.html import TemplateIO |
28 | 29 | |
29 | 30 |
from .qommon import _, force_str |
31 |
from .qommon import misc |
|
32 |
from .qommon import get_logger |
|
33 |
from .qommon.cron import CronJob |
|
30 | 34 |
from .qommon.form import * |
31 | 35 |
from .qommon.humantime import seconds2humanduration |
32 | 36 |
from .qommon.misc import get_variadic_url |
33 |
from .qommon import misc |
|
34 |
from .qommon import get_logger |
|
35 | ||
37 |
from .qommon.publisher import get_publisher_class |
|
36 | 38 |
from .qommon.storage import StorableObject |
37 | 39 |
from .qommon.template import Template |
38 | 40 |
from .qommon.xml_storage import XmlStorableObject |
... | ... | |
145 | 147 |
return tupled_items |
146 | 148 | |
147 | 149 | |
148 |
def get_json_from_url(url, data_source): |
|
150 |
def get_json_from_url(url, data_source=None, log_message_part='JSON data source'):
|
|
149 | 151 |
url = sign_url_auto_orig(url) |
152 |
data_source = data_source or {} |
|
150 | 153 |
data_key = data_source.get('data_attribute') or 'data' |
151 | 154 |
geojson = data_source.get('type') == 'geojson' |
152 | 155 |
try: |
... | ... | |
162 | 165 |
if not isinstance(entries.get(data_key), list): |
163 | 166 |
raise ValueError('not a json dict with a %s list attribute' % data_key) |
164 | 167 |
except misc.ConnectionError as e: |
165 |
get_logger().warning('Error loading JSON data source (%s)' % str(e))
|
|
168 |
get_logger().warning('Error loading %s (%s)' % (log_message_part, str(e)))
|
|
166 | 169 |
return None |
167 | 170 |
except (ValueError, TypeError) as e: |
168 |
get_logger().warning('Error reading JSON data source output (%s)' % str(e))
|
|
171 |
get_logger().warning('Error reading %s output (%s)' % (log_message_part, str(e)))
|
|
169 | 172 |
return None |
170 | 173 |
return entries |
171 | 174 | |
... | ... | |
364 | 367 |
text_attribute = None |
365 | 368 |
id_property = None |
366 | 369 |
label_template_property = None |
370 |
external = None |
|
371 |
external_status = None |
|
367 | 372 | |
368 | 373 |
# declarations for serialization |
369 | 374 |
XML_NODES = [ |
... | ... | |
378 | 383 |
('text_attribute', 'str'), |
379 | 384 |
('id_property', 'str'), |
380 | 385 |
('label_template_property', 'str'), |
386 |
('external', 'str'), |
|
387 |
('external_status', 'str'), |
|
381 | 388 |
('data_source', 'data_source'), |
382 | 389 |
] |
383 | 390 | |
... | ... | |
700 | 707 |
url = get_variadic_url(url, vars) |
701 | 708 |
return url |
702 | 709 | |
703 |
def is_used_in_formdef(self, formdef):
|
|
704 |
from .fields import WidgetField
|
|
710 |
def is_used(self):
|
|
711 |
from wcs.formdef import get_formdefs_of_all_kinds
|
|
705 | 712 | |
713 |
for formdef in get_formdefs_of_all_kinds(): |
|
714 |
if self.is_used_in_formdef(formdef): |
|
715 |
return True |
|
716 |
return False |
|
717 | ||
718 |
def is_used_in_formdef(self, formdef): |
|
706 | 719 |
for field in formdef.fields or []: |
707 | 720 |
data_source = getattr(field, 'data_source', None) |
708 | 721 |
if not data_source: |
... | ... | |
733 | 746 | |
734 | 747 |
def inspect_keys(self): |
735 | 748 |
return [] |
749 | ||
750 | ||
751 |
def has_chrono(publisher): |
|
752 |
return publisher.get_site_option('chrono_url') is not None |
|
753 | ||
754 | ||
755 |
def chrono_url(publisher, url): |
|
756 |
chrono_url = publisher.get_site_option('chrono_url') |
|
757 |
return urlparse.urljoin(chrono_url, url) |
|
758 | ||
759 | ||
760 |
def collect_agenda_data(publisher): |
|
761 |
agenda_url = chrono_url(publisher, 'api/agenda/') |
|
762 |
result = get_json_from_url(agenda_url, log_message_part='agenda') |
|
763 |
if result is None: |
|
764 |
return |
|
765 | ||
766 |
# build datasources from chrono |
|
767 |
agenda_data = [] |
|
768 |
for agenda in result.get('data') or []: |
|
769 |
if agenda['kind'] == 'events': |
|
770 |
agenda_data.append({'text': agenda['text'], 'url': agenda['api']['datetimes_url']}) |
|
771 |
elif agenda['kind'] in ['meetings', 'virtual']: |
|
772 |
agenda_data.append( |
|
773 |
{'text': _('%s - Slot types') % agenda['text'], 'url': agenda['api']['meetings_url']} |
|
774 |
) |
|
775 |
# get also meeting types |
|
776 |
mt_url = chrono_url(publisher, 'api/agenda/%s/meetings/' % agenda['id']) |
|
777 |
mt_results = get_json_from_url(mt_url, log_message_part='agenda') |
|
778 |
if mt_results is None: |
|
779 |
return |
|
780 |
for meetingtype in mt_results.get('data') or []: |
|
781 |
agenda_data.append( |
|
782 |
{ |
|
783 |
'text': _('%s - Slots of type %s') % (agenda['text'], meetingtype['text']), |
|
784 |
'url': meetingtype['api']['datetimes_url'], |
|
785 |
} |
|
786 |
) |
|
787 |
return agenda_data |
|
788 | ||
789 | ||
790 |
def build_agenda_datasources(publisher): |
|
791 |
if not has_chrono(publisher): |
|
792 |
return |
|
793 | ||
794 |
agenda_data = collect_agenda_data(publisher) |
|
795 |
if agenda_data is None: |
|
796 |
return |
|
797 | ||
798 |
# fetch existing datasources |
|
799 |
existing_datasources = {} |
|
800 |
for datasource in NamedDataSource.select(): |
|
801 |
if datasource.external != 'agenda': |
|
802 |
continue |
|
803 |
existing_datasources[datasource.data_source['value']] = datasource |
|
804 |
seen_datasources = [] |
|
805 | ||
806 |
# build datasources from chrono |
|
807 |
for agenda in agenda_data: |
|
808 |
url = agenda['url'] |
|
809 |
datasource = existing_datasources.get(url) |
|
810 |
if datasource is None: |
|
811 |
datasource = NamedDataSource() |
|
812 |
datasource.external = 'agenda' |
|
813 |
datasource.data_source = {'type': 'json', 'value': url} |
|
814 |
datasource.external_status = None # reset |
|
815 |
datasource.name = agenda['text'] |
|
816 |
datasource.store() |
|
817 |
# maintain caches |
|
818 |
existing_datasources[url] = datasource |
|
819 |
seen_datasources.append(url) |
|
820 | ||
821 |
# now check outdated agenda datasources |
|
822 |
for url, datasource in existing_datasources.items(): |
|
823 |
if url in seen_datasources: |
|
824 |
continue |
|
825 |
if datasource.is_used(): |
|
826 |
datasource.external_status = 'not-found' |
|
827 |
datasource.store() |
|
828 |
continue |
|
829 |
datasource.remove_self() |
|
830 | ||
831 | ||
832 |
if get_publisher_class(): |
|
833 |
# every hour: check for agenda datasources |
|
834 |
get_publisher_class().register_cronjob( |
|
835 |
CronJob(build_agenda_datasources, name='build_agenda_datasources', minutes=[0]) |
|
836 |
) |
|
736 |
- |