Skip to content

Commit c4d89df

Browse files
authored
Event: Supports mapping a Guidle tag to multiple tags
The import tagmap now reads repeated source rows as multiple target tags, so a single Guidle/JSON tag can expand into several onegov tags. Example: 'Brauchtum / Fest': ['Brauchtum', 'Fest']. A single tag in Guidle maps to multiple tags in onegov-cloud TYPE: Feature LINK: ogc-3208
1 parent e0d0828 commit c4d89df

5 files changed

Lines changed: 121 additions & 13 deletions

File tree

src/onegov/event/cli.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@
5151
cli = command_group()
5252

5353

54+
def parse_tagmap(tagmap_file: TextIOWrapper) -> dict[str, list[str]]:
55+
""" Reads a tagmap CSV, mapping each source tag to its target tags.
56+
57+
Repeating a source tag on multiple rows maps it to multiple target tags.
58+
59+
"""
60+
tagmap: dict[str, list[str]] = {}
61+
for row in csvreader(tagmap_file):
62+
if len(row) < 2:
63+
continue
64+
tagmap.setdefault(row[0], []).append(row[1])
65+
return tagmap
66+
67+
5468
@cli.command('clear')
5569
@pass_group_context
5670
def clear(
@@ -185,7 +199,7 @@ def import_json(
185199
186200
"""
187201
if tagmap_file is not None:
188-
tagmap = {row[0]: row[1] for row in csvreader(tagmap_file)}
202+
tagmap = parse_tagmap(tagmap_file)
189203
else:
190204
tagmap = None
191205

@@ -251,7 +265,11 @@ def _import_json(request: CoreRequest, app: Framework) -> None:
251265
tags = item['cat1']
252266
if tagmap and tags:
253267
unknown_tags |= set(tags) - tagmap.keys()
254-
tags = {tagmap[tag] for tag in tags if tag in tagmap}
268+
tags = {
269+
target
270+
for tag in tags if tag in tagmap
271+
for target in tagmap[tag]
272+
}
255273

256274
coordinates = None
257275
if item['latitude'] and item['longitude']:
@@ -428,7 +446,7 @@ def import_guidle(
428446
429447
"""
430448
if tagmap_file is not None:
431-
tagmap = {row[0]: row[1] for row in csvreader(tagmap_file)}
449+
tagmap = parse_tagmap(tagmap_file)
432450
else:
433451
tagmap = None
434452

src/onegov/event/utils/guidle.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,13 @@ def pdf(self) -> tuple[str, str] | tuple[None, None]:
217217

218218
def tags(
219219
self,
220-
tagmap: Mapping[str, str] | None = None
220+
tagmap: Mapping[str, Sequence[str]] | None = None
221221
) -> tuple[set[str], set[str]]:
222-
""" Returns a set of known and a set of unkonwn tags. """
222+
""" Returns a set of known and a set of unkonwn tags.
223+
224+
A source tag in the tagmap may map to more than one target tag.
225+
226+
"""
223227

224228
tag_elements = self.find(
225229
'guidle:classifications/'
@@ -233,7 +237,11 @@ def tags(
233237
}
234238
if tagmap:
235239
return (
236-
{tagmap[tag] for tag in tags if tag in tagmap},
240+
{
241+
target
242+
for tag in tags if tag in tagmap
243+
for target in tagmap[tag]
244+
},
237245
tags - tagmap.keys()
238246
)
239247
return tags, set()

tests/onegov/event/fixtures/guidle.xml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,35 @@ Lorem ipsum dolor sit amet, consetetur <a href="lorem.de">sadipscing</a> elitr,
9494
</guidle:classification>
9595
</guidle:classifications>
9696
</guidle:offer>
97+
<guidle:offer id="551262855">
98+
<guidle:lastUpdateDate>2017-10-21T22:44:12.834+02:00</guidle:lastUpdateDate>
99+
<guidle:offerDetail id="551262858" languageCode="de">
100+
<guidle:title>Vortrag ohne Tags</guidle:title>
101+
</guidle:offerDetail>
102+
<guidle:schedules>
103+
<guidle:date>
104+
<guidle:startDate>2018-10-26</guidle:startDate>
105+
<guidle:startTime>20:00:00</guidle:startTime>
106+
</guidle:date>
107+
</guidle:schedules>
108+
</guidle:offer>
109+
<guidle:offer id="551262856">
110+
<guidle:lastUpdateDate>2017-10-21T22:44:12.834+02:00</guidle:lastUpdateDate>
111+
<guidle:offerDetail id="551262859" languageCode="de">
112+
<guidle:title>Lesung mit unbekanntem Tag</guidle:title>
113+
</guidle:offerDetail>
114+
<guidle:schedules>
115+
<guidle:date>
116+
<guidle:startDate>2018-10-26</guidle:startDate>
117+
<guidle:startTime>20:00:00</guidle:startTime>
118+
</guidle:date>
119+
</guidle:schedules>
120+
<guidle:classifications>
121+
<guidle:classification id="204591954" name="Veranstaltungskalender" type="PRIMARY">
122+
<guidle:tag id="1585" name="Schauspiel" subcategoryId="204592149" subcategoryName="Theater"/>
123+
</guidle:classification>
124+
</guidle:classifications>
125+
</guidle:offer>
97126
</guidle:group>
98127
</guidle:groupSet>
99128
</guidle:exportData>

tests/onegov/event/test_cli.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@
55
from click.testing import CliRunner
66
from onegov.core.utils import module_path
77
from onegov.event.cli import cli
8+
from onegov.event.models import Event
89
from os import path
910
from unittest.mock import MagicMock
1011
from unittest.mock import patch
1112

1213

14+
from typing import TYPE_CHECKING
15+
if TYPE_CHECKING:
16+
from onegov.core.orm import SessionManager
17+
18+
1319
def test_import_ical(cfg_path: str, temporary_directory: str) -> None:
1420
runner = CliRunner()
1521

@@ -140,6 +146,7 @@ def test_import_ical(cfg_path: str, temporary_directory: str) -> None:
140146
def test_import_guidle(
141147
cfg_path: str,
142148
temporary_directory: str,
149+
session_manager: SessionManager,
143150
xml: str
144151
) -> None:
145152

@@ -157,7 +164,7 @@ def test_import_guidle(
157164
])
158165
assert result.exit_code == 0
159166
assert "Events successfully imported" in result.output
160-
assert "4 added, 0 updated, 0 deleted" in result.output
167+
assert "6 added, 0 updated, 0 deleted" in result.output
161168

162169
# Reimport, not changed due to last_update
163170
with patch('onegov.event.cli.niquests.get', return_value=response):
@@ -190,10 +197,14 @@ def test_import_guidle(
190197
], 'y')
191198
assert result.exit_code == 0
192199

193-
# Create tagmap
200+
# Create tagmap, mapping 'Konzert Pop / Rock / Jazz' to two tags
194201
tagmap = path.join(temporary_directory, 'tagmap.csv')
195202
with open(tagmap, 'w') as f:
196-
f.write("Konzert Pop / Rock / Jazz,Konzert\nSport,Sport")
203+
f.write(
204+
"Konzert Pop / Rock / Jazz,Konzert\n"
205+
"Konzert Pop / Rock / Jazz,Musik\n"
206+
"Sport,Sport"
207+
)
197208

198209
# Re-import with tagmap
199210
with patch('onegov.event.cli.niquests.get', return_value=response):
@@ -205,8 +216,31 @@ def test_import_guidle(
205216
])
206217
assert result.exit_code == 0
207218
assert "Events successfully imported" in result.output
208-
assert "Tags not in tagmap: \"Kulinarik\"!"
209-
assert "4 added, 0 updated, 0 deleted" in result.output
219+
assert "Tags not in tagmap:" in result.output
220+
assert '"Kulinarik"' in result.output
221+
assert '"Theater"' in result.output
222+
assert "6 added, 0 updated, 0 deleted" in result.output
223+
224+
# 'Konzert Pop / Rock / Jazz' maps to two tags, both of which are applied,
225+
# while the unmapped 'Kulinarik' is dropped without dropping the event
226+
session_manager.set_current_schema('foo-bar')
227+
events = session_manager.session().query(Event).all()
228+
assert len(events) == 6
229+
230+
tagged = [e for e in events if e.tags]
231+
untagged = [e for e in events if not e.tags]
232+
233+
# the four schedules of the offer with a mapped tag keep both target tags
234+
assert len(tagged) == 4
235+
assert all(sorted(e.tags) == ['Konzert', 'Musik'] for e in tagged)
236+
237+
# the offer without any tags and the offer whose only tag is not in the
238+
# tagmap are both imported anyway, just without any tags
239+
assert len(untagged) == 2
240+
assert {e.title for e in untagged} == {
241+
'Vortrag ohne Tags',
242+
'Lesung mit unbekanntem Tag',
243+
}
210244

211245

212246
@pytest.mark.parametrize("xml", [

tests/onegov/event/test_utils.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def tzdatetime(
3636
])
3737
def test_import_guidle(session: Session, xml: str) -> None:
3838
offers = list(GuidleExportData(etree.parse(xml)).offers())
39-
assert len(offers) == 1
39+
assert len(offers) == 3
4040

4141
assert offers[0].uid == '551262854'
4242
assert offers[0].title == "Theatervorstellung"
@@ -66,9 +66,28 @@ def test_import_guidle(session: Session, xml: str) -> None:
6666
{'Konzert Pop / Rock / Jazz', 'Kulinarik'},
6767
set()
6868
)
69-
assert offers[0].tags({'Konzert Pop / Rock / Jazz': 'Konzert'}) == (
69+
assert offers[0].tags({'Konzert Pop / Rock / Jazz': ['Konzert']}) == (
7070
{'Konzert'}, {'Kulinarik'}
7171
)
72+
assert offers[0].tags({
73+
'Konzert Pop / Rock / Jazz': ['Konzert', 'Musik'],
74+
'Kulinarik': ['Kulinarik'],
75+
}) == (
76+
{'Konzert', 'Musik', 'Kulinarik'}, set()
77+
)
78+
79+
# an offer without any tags yields no known and no unknown tags
80+
assert offers[1].tags() == (set(), set())
81+
assert offers[1].tags({'Konzert Pop / Rock / Jazz': ['Konzert']}) == (
82+
set(), set()
83+
)
84+
85+
# an offer whose tags are not in the tagmap yields no known tags, but
86+
# reports all of them as unknown
87+
assert offers[2].tags() == ({'Theater'}, set())
88+
assert offers[2].tags({'Konzert Pop / Rock / Jazz': ['Konzert']}) == (
89+
set(), {'Theater'}
90+
)
7291

7392
schedules = list(offers[0].schedules())
7493
assert len(schedules) == 4

0 commit comments

Comments
 (0)