Skip to content

Commit ce00cc2

Browse files
committed
✨ [FEAT] Add fetching related categories and parent sites in GeotrekSiteParser (refs #3569)
1 parent b104fa0 commit ce00cc2

File tree

3 files changed

+106
-24
lines changed

3 files changed

+106
-24
lines changed

geotrek/outdoor/parsers.py

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from django.conf import settings
12
from geotrek.common.parsers import (ApidaeBaseParser, AttachmentParserMixin, GeotrekParser, GlobalImportError, Parser)
2-
from geotrek.outdoor.models import Site
3+
from geotrek.outdoor.models import Practice, Rating, RatingScale, Sector, Site
34

45

56
class GeotrekSiteParser(GeotrekParser):
@@ -9,11 +10,10 @@ class GeotrekSiteParser(GeotrekParser):
910
model = Site
1011
replace_fields = {
1112
"eid": "uuid",
12-
"geom": "geometry"
13+
"geom": "geometry",
14+
"scale": "ratingscale"
1315
}
1416
url_categories = {
15-
"practice": "outdoor_practice",
16-
"ratings": "outdoor_rating",
1717
"themes": "theme",
1818
"type": "outdoor_sitetype",
1919
'labels': 'label',
@@ -23,7 +23,10 @@ class GeotrekSiteParser(GeotrekParser):
2323
}
2424
categories_keys_api_v2 = {
2525
"practice": "name",
26-
"ratings": "name",
26+
"sector": "name",
27+
"rating": "name",
28+
"scale": "name",
29+
"ratingscale": "name",
2730
"themes": "label",
2831
"type": "name",
2932
'labels': 'name',
@@ -33,21 +36,91 @@ class GeotrekSiteParser(GeotrekParser):
3336
}
3437
natural_keys = {
3538
"practice": "name",
36-
"ratings": "name",
3739
"themes": "label",
3840
"type": "name",
3941
'labels': 'name',
4042
'source': 'name',
4143
'managers': 'organism',
4244
'structure': 'name',
4345
}
46+
parents = {}
47+
48+
def get_id_from_mapping(self, mapping, value):
49+
for dest_id, source_id in mapping.items():
50+
if source_id == value:
51+
return dest_id
52+
return None
53+
54+
def init_outdoor_category(self, category, model, join_field=None, extra_fields={}):
55+
response = self.request_or_retry(f"{self.url}/api/v2/outdoor_{category}")
56+
results = response.json().get('results', [])
57+
if category not in self.field_options.keys():
58+
self.field_options[category] = {}
59+
if "mapping" not in self.field_options[category].keys():
60+
self.field_options[category]["mapping"] = {}
61+
for result in results:
62+
label = result["name"]
63+
if isinstance(label, dict):
64+
if label[settings.MODELTRANSLATION_DEFAULT_LANGUAGE]:
65+
replaced_label = self.replace_mapping(label[settings.MODELTRANSLATION_DEFAULT_LANGUAGE], f'outdoor_{category}')
66+
else:
67+
if label:
68+
replaced_label = self.replace_mapping(label, f'outdoor_{category}')
69+
fields = {}
70+
for field in extra_fields:
71+
if isinstance(result[field], dict):
72+
if result[field][settings.MODELTRANSLATION_DEFAULT_LANGUAGE]:
73+
fields[field] = result[field][settings.MODELTRANSLATION_DEFAULT_LANGUAGE]
74+
else:
75+
fields[field] = result[field]
76+
if join_field:
77+
mapping_key = self.replace_fields.get(join_field, join_field)
78+
mapped_value = self.get_id_from_mapping(self.field_options[mapping_key]["mapping"], result[join_field])
79+
if not mapped_value:
80+
continue # Ignore some results if related category was not retrieved
81+
fields[f"{join_field}_id"] = mapped_value
82+
category_obj, _ = model.objects.update_or_create(**{'name': replaced_label}, defaults=fields)
83+
self.field_options[category]["mapping"][category_obj.pk] = result['id']
4484

4585
def __init__(self, *args, **kwargs):
4686
super().__init__(*args, **kwargs)
87+
self.init_outdoor_category('sector', Sector)
88+
self.init_outdoor_category('practice', Practice, join_field='sector')
89+
self.init_outdoor_category('ratingscale', RatingScale, join_field='practice')
90+
self.init_outdoor_category('rating', Rating, join_field='scale', extra_fields=['description', 'order', 'color'])
4791
self.next_url = f"{self.url}/api/v2/outdoor_site"
48-
print("AFTER INIT 9999999999999999999999999999999999999999999999999999999")
92+
93+
def filter_practice(self, src, val):
94+
if val:
95+
practice_id = self.get_id_from_mapping(self.field_options["practice"]["mapping"], val)
96+
if practice_id:
97+
return Practice.objects.get(pk=practice_id)
98+
return None
99+
100+
def filter_ratings(self, src, val):
101+
ratings = []
102+
for subval in val:
103+
rating_id = self.get_id_from_mapping(self.field_options["rating"]["mapping"], subval)
104+
if rating_id:
105+
ratings.append(Rating.objects.get(pk=rating_id).pk)
106+
return ratings
107+
108+
def parse_row(self, row):
109+
super().parse_row(row)
110+
self.parents[row['uuid']] = row['parent_uuid']
49111

50112
def end(self):
51-
"""Add children after all treks imported are created in database."""
52-
#super().end()
53-
print("MAKE LINK BETWEEN SITES")
113+
"""Add children after all Sites imported are created in database."""
114+
for child, parent in self.parents.items():
115+
try:
116+
parent_site = Site.objects.get(eid=parent)
117+
except Site.DoesNotExist:
118+
self.add_warning(f"Trying to retrieve missing parent (UUID: {parent}) for child Site (UUID: {child})")
119+
continue
120+
try:
121+
child_site = Site.objects.get(eid=child)
122+
except Site.DoesNotExist:
123+
self.add_warning(f"Trying to retrieve missing child (UUID: {child}) for parent Site (UUID: {parent})")
124+
continue
125+
child_site.parent = parent_site
126+
child_site.save()

geotrek/outdoor/tests/data/geotrek_parser_v2/outdoor_rating.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
"fr": "3+"
1111
},
1212
"description": {
13-
"en": null,
14-
"fr": null
13+
"en": "A description",
14+
"fr": "Une description"
1515
},
1616
"scale": 6,
1717
"order": 302,
18-
"color": "#D9D9D9"
18+
"color": "#D9D9D8"
1919
},
2020
{
2121
"id": 36,

geotrek/outdoor/tests/test_parsers.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from geotrek.common.models import Organism, Theme, FileType, Attachment, Label
2020
from geotrek.common.tests.mixins import GeotrekParserTestMixin
2121
from geotrek.core.tests.factories import PathFactory
22-
from geotrek.outdoor.models import Site
22+
from geotrek.outdoor.models import Practice, Rating, RatingScale, Sector, Site
2323
from geotrek.outdoor.parsers import GeotrekSiteParser
2424
from geotrek.trekking.tests.factories import RouteFactory
2525
from geotrek.trekking.models import POI, POIType, Service, Trek, DifficultyLevel, Route
@@ -29,15 +29,12 @@
2929
)
3030

3131

32-
3332
class TestGeotrekSiteParser(GeotrekSiteParser):
3433
url = "https://test.fr"
3534
provider = 'geotrek1'
3635
field_options = {
3736
'type': {'create': True, },
38-
'ratings': {'create': True, },
3937
'themes': {'create': True},
40-
'practice': {'create': True},
4138
'geom': {'required': True},
4239
'labels': {'create': True},
4340
'source': {'create': True},
@@ -72,7 +69,6 @@ class TestGeotrek2SiteParser(GeotrekSiteParser):
7269
# 'geom': {'required': True},
7370
# }
7471

75-
7672
@override_settings(MODELTRANSLATION_DEFAULT_LANGUAGE="fr")
7773
@skipIf(settings.TREKKING_TOPOLOGY_ENABLED, 'Test without dynamic segmentation only')
7874
class SiteGeotrekParserTests(GeotrekParserTestMixin, TestCase):
@@ -85,14 +81,16 @@ def setUpTestData(cls):
8581
@mock.patch('requests.head')
8682
def test_create(self, mocked_head, mocked_get):
8783
self.mock_time = 0
88-
self.mock_json_order = [('outdoor', 'outdoor_practice.json'),
89-
('outdoor', 'outdoor_rating.json'),
90-
('outdoor', 'theme.json'),
84+
self.mock_json_order = [('outdoor', 'theme.json'),
9185
('outdoor', 'outdoor_sitetype.json'),
9286
('outdoor', 'label.json'),
9387
('outdoor', 'source.json'),
9488
('outdoor', 'organism.json'),
9589
('outdoor', 'structure.json'),
90+
('outdoor', 'outdoor_sector.json'),
91+
('outdoor', 'outdoor_practice.json'),
92+
('outdoor', 'outdoor_ratingscale.json'),
93+
('outdoor', 'outdoor_rating.json'),
9694
('outdoor', 'outdoor_site_ids.json'),
9795
('outdoor', 'outdoor_site.json')]
9896

@@ -104,17 +102,26 @@ def test_create(self, mocked_head, mocked_get):
104102

105103
call_command('import', 'geotrek.outdoor.tests.test_parsers.TestGeotrekSiteParser', verbosity=0)
106104
self.assertEqual(Site.objects.count(), 6)
105+
self.assertEqual(Sector.objects.count(), 2)
106+
self.assertEqual(RatingScale.objects.count(), 1)
107+
self.assertEqual(Rating.objects.count(), 3)
108+
self.assertEqual(Practice.objects.count(), 1)
107109
site = Site.objects.get(name_fr="Racine", name_en="Root")
108110
# TODO : all the ones that are commented do not work
109111
self.assertEqual(site.published, True)
110112
self.assertEqual(site.published_fr, True)
111113
self.assertEqual(site.published_en, True)
112114
self.assertEqual(site.published_it, False)
113115
self.assertEqual(site.published_es, False)
116+
self.assertEqual(str(site.practice.sector), 'Vertical')
114117
self.assertEqual(str(site.practice), 'Escalade')
115118
self.assertEqual(str(site.labels.first()), 'Label fr')
116-
#self.assertEqual(str(site.ratings.all()), 'Très facile')
117-
#self.assertEqual(str(site.practice.sector), 'Vertical')
119+
self.assertEqual(site.ratings.count(), 3)
120+
self.assertEqual(str(site.ratings.first()), 'Cotation : 3+')
121+
self.assertEqual(site.ratings.first().description, 'Une description')
122+
self.assertEqual(site.ratings.first().order, 302)
123+
self.assertEqual(site.ratings.first().color, '#D9D9D8')
124+
self.assertEqual(str(site.ratings.first().scale), 'Cotation (Escalade)')
118125
self.assertEqual(str(site.type), 'Ecole')
119126
self.assertAlmostEqual(site.geom[0][0][0][0], 970023.8976707931, places=5)
120127
self.assertAlmostEqual(site.geom[0][0][0][1], 6308806.903248067, places=5)
@@ -135,14 +142,16 @@ def test_create(self, mocked_head, mocked_get):
135142
self.assertEqual(site.orientation, ['NE', 'S'])
136143
self.assertEqual(site.ambiance, "Test ambiance fr")
137144
self.assertEqual(site.ambiance_en, "Test ambiance en")
138-
#self.assertEqual(site.parent) # TODO use other to test this
139145
self.assertEqual(site.wind, ['N', 'E'])
146+
self.assertEqual(str(site.structure), 'Test structure')
140147
# self.assertEqual(site.information_desks.count(), 1)
141148
# self.assertEqual(site.weblink.count(), 1)
142149
# self.assertEqual(site.excluded_pois.count(), 1)
143150
self.assertEqual(site.eid, "57a8fb52-213d-4dce-8224-bc997f892aae")
144151
# self.assertEqual(Attachment.objects.filter(object_id=site.pk).count(), 3)
145152
# self.assertEqual(Attachment.objects.get(object_id=site.pk, license__isnull=False).license.label, "License")
153+
child_site = Site.objects.get(name_fr="Noeud 1", name_en="Node")
154+
self.assertEqual(child_site.parent, site)
146155

147156
@mock.patch('requests.get')
148157
@mock.patch('requests.head')

0 commit comments

Comments
 (0)