Skip to content

Commit e882d75

Browse files
Destinyyyymlvernay
andauthored
[Backend] chore: import_aveugles (#2364)
* chore: import_aveugles * Fix case with `[]` * chore: telephone serializer empty edge case fix * chore: settrace removed * Change PATCH - `def validate` was putting all accessibility to None even those not provided in payload. Dedicated management in case of partial. - in PATCH through API we should be able to change a value but not set it to None --------- Co-authored-by: Marie-Laure Vernay <mlvernay@gmail.com>
1 parent 3629662 commit e882d75

File tree

3 files changed

+160
-67
lines changed

3 files changed

+160
-67
lines changed

erp/imports/serializers.py

Lines changed: 78 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ def validate_code_postal(self, obj):
202202

203203
def validate_telephone(self, obj):
204204
# weird invisible char
205+
if not obj:
206+
return obj
207+
205208
obj = obj.replace(" ", "")
206209
obj = obj.replace(" ", "")
207210
return obj
@@ -235,81 +238,85 @@ def validate(self, obj):
235238
if "accessibilite" not in obj:
236239
raise serializers.ValidationError("Veuillez fournir les données d'accessibilité.")
237240

238-
if self.instance:
239-
# if we are updating an ERP, only accessibility, asp_id and import_email are editable
240-
self.instance.import_email = obj.get("import_email")
241-
self.instance.asp_id = obj.get("asp_id")
242-
accessibilite = Accessibilite(**obj["accessibilite"])
243-
accessibilite.full_clean()
244-
245-
sources_data_list = []
246-
sources_data = obj.get("sources") or []
247-
for source_data in sources_data:
248-
external_source = ExternalSource(**source_data)
249-
external_source.full_clean(exclude=("erp",))
250-
sources_data_list.append(model_to_dict(external_source))
251-
252-
return (
253-
model_to_dict(self.instance)
254-
| {"accessibilite": model_to_dict(accessibilite)}
255-
| {"sources": sources_data_list}
256-
)
257-
258-
if not obj.get("voie") and not obj.get("lieu_dit"):
259-
raise serializers.ValidationError("Veuillez entrer une voie OU un lieu-dit")
260-
261-
for i in range(3):
262-
try:
263-
address = get_address_query_to_geocode(obj)
264-
locdata = geocoder.geocode(address, postcode=obj["code_postal"])
265-
if not locdata:
266-
raise RuntimeError
267-
self._geom = locdata["geom"]
268-
obj["voie"] = locdata["voie"]
269-
obj["lieu_dit"] = locdata["lieu_dit"]
270-
obj["code_postal"] = locdata["code_postal"]
271-
obj["commune"] = locdata["commune"]
272-
obj["code_insee"] = locdata["code_insee"]
273-
obj["geoloc_provider"] = locdata["provider"]
274-
obj["numero"] = locdata["numero"]
275-
obj["ban_id"] = locdata.get("ban_id")
276-
obj.pop("latitude", None)
277-
obj.pop("longitude", None)
278-
break
279-
except (RuntimeError, KeyError):
280-
if i < 2:
281-
continue
282-
283-
if obj.get("latitude") is not None and obj.get("longitude") is not None:
284-
self._geom = Point((obj["longitude"], obj["latitude"]), srid=4326)
285-
obj.pop("latitude")
286-
obj.pop("longitude")
241+
if not self.partial:
242+
if self.instance:
243+
# if we are updating an ERP, only accessibility, asp_id and import_email are editable
244+
self.instance.import_email = obj.get("import_email")
245+
self.instance.asp_id = obj.get("asp_id")
246+
accessibilite = Accessibilite(**obj["accessibilite"])
247+
accessibilite.full_clean()
248+
249+
sources_data_list = []
250+
sources_data = obj.get("sources") or []
251+
for source_data in sources_data:
252+
external_source = ExternalSource(**source_data)
253+
external_source.full_clean(exclude=("erp",))
254+
sources_data_list.append(model_to_dict(external_source))
255+
256+
return (
257+
model_to_dict(self.instance)
258+
| {"accessibilite": model_to_dict(accessibilite)}
259+
| {"sources": sources_data_list}
260+
)
261+
262+
if not obj.get("voie") and not obj.get("lieu_dit"):
263+
raise serializers.ValidationError("Veuillez entrer une voie OU un lieu-dit")
264+
265+
for i in range(3):
266+
try:
267+
address = get_address_query_to_geocode(obj)
268+
locdata = geocoder.geocode(address, postcode=obj["code_postal"])
269+
if not locdata:
270+
raise RuntimeError
271+
self._geom = locdata["geom"]
272+
obj["voie"] = locdata["voie"]
273+
obj["lieu_dit"] = locdata["lieu_dit"]
274+
obj["code_postal"] = locdata["code_postal"]
275+
obj["commune"] = locdata["commune"]
276+
obj["code_insee"] = locdata["code_insee"]
277+
obj["geoloc_provider"] = locdata["provider"]
278+
obj["numero"] = locdata["numero"]
279+
obj["ban_id"] = locdata.get("ban_id")
280+
obj.pop("latitude", None)
281+
obj.pop("longitude", None)
287282
break
283+
except (RuntimeError, KeyError):
284+
if i < 2:
285+
continue
286+
287+
if obj.get("latitude") is not None and obj.get("longitude") is not None:
288+
self._geom = Point((obj["longitude"], obj["latitude"]), srid=4326)
289+
obj.pop("latitude")
290+
obj.pop("longitude")
291+
break
292+
293+
raise serializers.ValidationError(f"Adresse non localisable: {address}")
288294

289-
raise serializers.ValidationError(f"Adresse non localisable: {address}")
295+
obj["commune_ext"] = Commune.objects.filter(
296+
nom__iexact=obj["commune"], code_postaux__contains=[obj["code_postal"]]
297+
).first()
290298

291-
obj["commune_ext"] = Commune.objects.filter(
292-
nom__iexact=obj["commune"], code_postaux__contains=[obj["code_postal"]]
293-
).first()
299+
self._ensure_no_duplicate(obj)
294300

295-
self._ensure_no_duplicate(obj)
301+
erp_data = obj.copy()
302+
erp_data.pop("accessibilite")
303+
erp_data.pop("sources")
296304

297-
erp_data = obj.copy()
298-
erp_data.pop("accessibilite")
299-
erp_data.pop("sources")
305+
Erp(**erp_data).full_clean(exclude=("source_id", "asp_id", "user", "metadata", "search_vector"))
300306

301-
Erp(**erp_data).full_clean(exclude=("source_id", "asp_id", "user", "metadata", "search_vector"))
302-
Accessibilite(**obj["accessibilite"]).full_clean()
307+
if "accessibilite" in obj:
308+
Accessibilite(**obj["accessibilite"]).full_clean()
303309

304310
sources_data = obj.get("sources") or []
305311
for source_data in sources_data:
306312
ExternalSource(**source_data).full_clean(exclude=("erp",))
307313

308314
return obj
309315

310-
def update(self, instance, validated_data, partial=True):
311-
# If enrich_only, it won't update any access info already there
316+
def update(self, instance, validated_data):
312317
enrich_only = self.context.get("enrich_only") or False
318+
raw_data = self.initial_data
319+
313320
# if we are updating an ERP, only accessibility, asp_id and import_email are editable
314321
if validated_data.get("import_email"):
315322
instance.import_email = validated_data["import_email"]
@@ -320,15 +327,20 @@ def update(self, instance, validated_data, partial=True):
320327
instance.save(update_fields=["asp_id"])
321328

322329
accessibilite = instance.accessibilite
323-
for attr in ("id", "erp"):
324-
validated_data["accessibilite"].pop(attr, False)
330+
acc_data = validated_data.get("accessibilite", {})
331+
raw_acc_data = raw_data.get("accessibilite", {})
332+
333+
for attr, new_value in acc_data.items():
334+
if attr in ("id", "erp"):
335+
continue
325336

326-
for attr in validated_data["accessibilite"]:
327337
if enrich_only and getattr(accessibilite, attr, None) is not None:
328338
continue
329339

330-
new_value = validated_data["accessibilite"][attr]
331-
if new_value not in (None, [], ()):
340+
if self.partial and attr in raw_acc_data and raw_acc_data[attr] in (None, "", [], ()):
341+
continue
342+
343+
if not enrich_only or new_value not in (None, [], ()):
332344
setattr(accessibilite, attr, new_value)
333345
self._handle_children_reinit(accessibilite, attr)
334346

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import csv
2+
3+
import reversion
4+
from django.core.management.base import BaseCommand
5+
from django.db import IntegrityError
6+
from rest_framework.exceptions import ValidationError
7+
8+
from erp.imports.serializers import ErpImportSerializer
9+
from erp.models import Erp
10+
11+
12+
class Command(BaseCommand):
13+
help = "Import cinema aveugles accessibility"
14+
15+
def handle(self, *args, **kwargs):
16+
with open("cinemas_audiodescription.csv", "r") as file:
17+
reader = csv.DictReader(file, delimiter=";")
18+
19+
for row in reader:
20+
if not row["erp_id"]:
21+
continue
22+
23+
accessibilite = dict()
24+
accessibilite["erp_id"] = row["erp_id"]
25+
26+
if (
27+
row["accueil_audiodescription"]
28+
== "avec équipement permanent, casques et boîtiers disponibles à l’accueil"
29+
):
30+
accessibilite["accueil_audiodescription"] = ["avec_équipement_permanent"]
31+
32+
if (
33+
row["accueil_audiodescription"]
34+
== "avec équipement permanent nécessitant le téléchargement d'une application sur smartphone"
35+
):
36+
accessibilite["accueil_audiodescription"] = ["avec_app"]
37+
38+
if row["accueil_audiodescription"] == "avec équipement occasionnel selon la programmation":
39+
accessibilite["accueil_audiodescription"] = ["avec_équipement_occasionnel"]
40+
41+
accessibilite["accueil_personnels"] = row["accueil_personnels"]
42+
accessibilite["entree_balise_sonore"] = True if row["entree_balise_sonore"] == "True" else False
43+
accessibilite["accueil_audiodescription_presence"] = (
44+
True if row["accueil_audiodescription_presence"] == "True" else False
45+
)
46+
accessibilite["commentaire"] = row["commentaire"]
47+
48+
if not accessibilite["accueil_audiodescription_presence"]:
49+
accessibilite["accueil_audiodescription"] = []
50+
51+
erp = Erp.objects.get(pk=row["erp_id"])
52+
53+
self._update_erp(
54+
data={"accessibilite": accessibilite, "activite": erp.activite.nom, **erp.__dict__}, erp=erp
55+
)
56+
57+
def _update_erp(self, data, erp=None):
58+
serializer = ErpImportSerializer(instance=erp, data=data)
59+
try:
60+
serializer.is_valid(raise_exception=True)
61+
except Exception as e:
62+
if (
63+
isinstance(e, ValidationError)
64+
and "non_field_errors" in e.get_codes()
65+
and "duplicate" in e.get_codes()["non_field_errors"]
66+
):
67+
existing_erp_pk = int(e.detail["non_field_errors"][1])
68+
existing_erp = Erp.objects.get(pk=existing_erp_pk)
69+
70+
return self._update_erp(data=data, erp=existing_erp)
71+
return
72+
try:
73+
with reversion.create_revision():
74+
erp = serializer.save()
75+
reversion.set_comment("Created via cinema blindness accessibility import")
76+
except reversion.errors.RevertError:
77+
erp = serializer.save()
78+
except IntegrityError as integrity_error:
79+
raise integrity_error
80+
81+
print(f"ERP {erp.id} updated available at: {erp.get_absolute_uri()}")

tests/api/tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ def test_post_patch(self, api_client, activite):
546546
response = api_client.patch(reverse("erp-detail", kwargs={"slug": erp.slug}), data=payload, format="json")
547547
assert response.status_code == 200, response.json()
548548
erp.accessibilite.refresh_from_db()
549-
assert erp.accessibilite.transport_station_presence is False, "Should kept access info"
549+
assert erp.accessibilite.transport_station_presence is False, "Should have kept access info"
550550
assert erp.accessibilite.commentaire == "Updated comment"
551551

552552
payload = {"accessibilite": {"transport_station_presence": None}}

0 commit comments

Comments
 (0)