From 03115e8a12b7dd33b732701399da906fc04c8a75 Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Thu, 10 Jul 2025 17:26:07 +0200 Subject: [PATCH 01/15] wip --- qfdmo/forms.py | 16 ++++++++++------ qfdmo/views/adresses.py | 7 +++++-- qfdmo/views/carte.py | 12 +++++++++++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/qfdmo/forms.py b/qfdmo/forms.py index f58ab920d..79eb2a152 100644 --- a/qfdmo/forms.py +++ b/qfdmo/forms.py @@ -6,6 +6,7 @@ from django.template.loader import render_to_string from django.utils.safestring import mark_safe from dsfr.forms import DsfrBaseForm +from typing_extensions import deprecated from qfdmo.fields import GroupeActionChoiceField from qfdmo.geo_api import epcis_from, formatted_epcis_as_list_of_tuple @@ -26,6 +27,13 @@ ) +class DisplayedActeursForm(forms.Form): + sous_categories = forms.ModelMultipleChoiceField( + queryset=SousCategorieObjet.objects.filter(afficher=True) + ) + + +@deprecated("This form will be dropped soon in favor of DisplayedActeursForm") class AddressesForm(forms.Form): def load_choices(self, request: HttpRequest, **kwargs) -> None: if address_placeholder := request.GET.get("address_placeholder"): @@ -502,9 +510,7 @@ def load_choices(self): action_displayed = forms.MultipleChoiceField( widget=DSFRCheckboxSelectMultiple( attrs={ - "class": ( - "fr-checkbox qf-inline-grid qf-grid-cols-4 qf-gap-4" " qf-m-1w" - ), + "class": ("fr-checkbox qf-inline-grid qf-grid-cols-4 qf-gap-4 qf-m-1w"), }, ), choices=[], @@ -530,9 +536,7 @@ def load_choices(self): action_list = forms.MultipleChoiceField( widget=DSFRCheckboxSelectMultiple( attrs={ - "class": ( - "fr-checkbox qf-inline-grid qf-grid-cols-4 qf-gap-4" " qf-m-1w" - ), + "class": ("fr-checkbox qf-inline-grid qf-grid-cols-4 qf-gap-4 qf-m-1w"), }, ), choices=[], diff --git a/qfdmo/views/adresses.py b/qfdmo/views/adresses.py index 9d0c28c10..44b3a17e5 100644 --- a/qfdmo/views/adresses.py +++ b/qfdmo/views/adresses.py @@ -444,9 +444,9 @@ def _compile_acteurs_queryset( if self.get_data_from_request_or_bounded_form("bonus"): filters &= Q(labels__bonus=True) - if sous_categorie_id := self.get_data_from_request_or_bounded_form("sc_id", 0): + if sous_categorie_ids := self.get_sous_categories(): filters &= Q( - proposition_services__sous_categories__id=sous_categorie_id, + proposition_services__sous_categories__id__in=sous_categorie_ids, ) actions_filters = Q() @@ -472,6 +472,9 @@ def _compile_acteurs_queryset( return filters, excludes + def get_sous_categories(self): + return [self.get_data_from_request_or_bounded_form("sc_id", 0)] + def get_cached_groupe_action_with_displayed_actions(self, action_displayed): # Cast needed because of the cache cached_groupe_action_instances = cast( diff --git a/qfdmo/views/carte.py b/qfdmo/views/carte.py index 5eb5ede33..ec64c8b18 100644 --- a/qfdmo/views/carte.py +++ b/qfdmo/views/carte.py @@ -4,7 +4,7 @@ from django.utils.functional import cached_property from django.views.generic import DetailView -from qfdmo.forms import CarteForm +from qfdmo.forms import CarteForm, DisplayedActeursForm from qfdmo.models import CarteConfig from qfdmo.views.adresses import SearchActeursView @@ -15,6 +15,13 @@ class CarteSearchActeursView(SearchActeursView): is_carte = True template_name = "qfdmo/carte.html" form_class = CarteForm + displayed_acteur_form: DisplayedActeursForm + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.displayed_acteur_form = DisplayedActeursForm(self.request.GET) + self.displayed_acteur_form.is_valid() def get_initial(self, *args, **kwargs): initial = super().get_initial(*args, **kwargs) @@ -40,6 +47,9 @@ def get_context_data(self, **kwargs): def _get_selected_action_ids(self): return [a.id for a in self._get_selected_action()] + def get_sous_categories(self): + return self.displayed_acteur_form.cleaned_data["sous_categories"] + class ProductCarteView(CarteSearchActeursView): def get_context_data(self, **kwargs): From e7cb922620f842a4452258ae00c4737549ca7cec Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Thu, 17 Jul 2025 10:34:21 +0200 Subject: [PATCH 02/15] WIP --- Makefile | 9 +++++++-- qfdmo/admin/acteur.py | 4 ++-- templates/snippets/share_and_embed.html | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 1d8993745..28d32a7d3 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,12 @@ init-certs: init-playwright: npx playwright install --with-deps +.PHONY: init-db +init-db: + # TODO: executer dans le conteneur docker directement + psql -d "$(DB_URL)" -f scripts/sql/create_databases.sql + psql -d "$(DB_URL)" -f scripts/sql/create_extensions.sql + .PHONY: init-dev init-dev: # git @@ -45,8 +51,7 @@ init-dev: cp .env.template .env cp ./dags/.env.template ./dags/.env # prepare django - psql -d "$(DB_URL)" -f scripts/sql/create_databases.sql - psql -d "$(DB_URL)" -f scripts/sql/create_extensions.sql + make init-db make migrate make createcachetable make createsuperuser diff --git a/qfdmo/admin/acteur.py b/qfdmo/admin/acteur.py index 4121ced03..e8a80e619 100644 --- a/qfdmo/admin/acteur.py +++ b/qfdmo/admin/acteur.py @@ -17,7 +17,7 @@ from import_export import admin as import_export_admin from import_export import fields, resources, widgets -from core.admin import CodeLibelleModelAdmin, NotMutableMixin +from core.admin import BaseAdmin, CodeLibelleModelAdmin, NotMutableMixin from qfdmo.admin.widgets import CategorieChoiceWidget, SousCategorieChoiceWidget from qfdmo.models import ( Acteur, @@ -143,7 +143,7 @@ def __init__(self, *args, **kwargs): self.fields["source"].queryset = Source.objects.all().order_by("libelle") -class BaseActeurAdmin(admin.GISModelAdmin): +class BaseActeurAdmin(admin.GISModelAdmin, BaseAdmin): form = BaseActeurForm gis_widget = CustomOSMWidget inlines = [ diff --git a/templates/snippets/share_and_embed.html b/templates/snippets/share_and_embed.html index f78c7e03b..03679446c 100644 --- a/templates/snippets/share_and_embed.html +++ b/templates/snippets/share_and_embed.html @@ -6,14 +6,14 @@

{% endif %}
    -
  • +
  • {% include "modals/share.html" with button_extra_classes="qf-w-full qf-justify-center fr-btn--secondary fr-btn--icon-left fr-icon fr-icon-share-line" %}
  • -
  • +
  • {% include "modals/embed.html" with button_extra_classes="qf-w-full qf-justify-center fr-btn--tertiary fr-btn--icon-left fr-icon fr-icon-code-s-slash-line" %}
From be33b4b72e470a5fdc59ebb44d392b3e4ae34fe8 Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Thu, 24 Jul 2025 17:24:37 +0200 Subject: [PATCH 03/15] Add multi sous cat support --- qfdmd/models.py | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/qfdmd/models.py b/qfdmd/models.py index 1afa41999..c27570074 100644 --- a/qfdmd/models.py +++ b/qfdmd/models.py @@ -1,10 +1,10 @@ import logging -from urllib.parse import urlencode import sites_faciles from django.contrib.gis.db import models from django.db.models import CheckConstraint, Q from django.db.models.functions import Now +from django.http import QueryDict from django.template.loader import render_to_string from django.urls.base import reverse from django.utils.functional import cached_property @@ -327,17 +327,27 @@ def get_etats_descriptions(self) -> tuple[str, str] | None: @property def carte_settings(self): # TODO : gérer plusieurs catégories ici - sous_categorie = self.sous_categories.filter(afficher_carte=True).first() - if not sous_categorie: - return {} - - return { - "direction": "jai", - "first_dir": "jai", - "limit": 25, - "sc_id": sous_categorie.id, - "sous_categorie_objet": sous_categorie.libelle, - } + sous_categories = self.sous_categories.filter(afficher_carte=True).all() + settings_querydict = QueryDict(mutable=True) + + if not sous_categories: + return settings_querydict + + settings_querydict.update( + { + "direction": "jai", + "first_dir": "jai", + "limit": 25, + "sc_id": sous_categories.first().id, + "sous_categorie_objet": sous_categories.first().libelle, + } + ) + + settings_querydict.setlist( + "sous_categorie", sous_categories.values_list("id", flat=True) + ) + + return settings_querydict @cached_property def en_savoir_plus(self): @@ -450,16 +460,21 @@ def get_url_carte(self, actions=None, map_container_id=None): carte_settings = self.produit.carte_settings if actions: carte_settings.update( - action_list=actions, - action_displayed=actions, + { + "action_list": actions, + "action_displayed": actions, + } ) if map_container_id: carte_settings.update( - map_container_id=map_container_id, + { + "map_container_id": map_container_id, + } ) - params = urlencode(carte_settings) + params = carte_settings.urlencode() + logger.info(f"{params=}") url = reverse("qfdmd:carte", args=[self.slug]) return f"{url}?{params}" From e3a1a217667baf1cc8146cf91a353c53e87855b0 Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Thu, 24 Jul 2025 17:27:40 +0200 Subject: [PATCH 04/15] Restore --- qfdmo/admin/acteur.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qfdmo/admin/acteur.py b/qfdmo/admin/acteur.py index e8a80e619..4121ced03 100644 --- a/qfdmo/admin/acteur.py +++ b/qfdmo/admin/acteur.py @@ -17,7 +17,7 @@ from import_export import admin as import_export_admin from import_export import fields, resources, widgets -from core.admin import BaseAdmin, CodeLibelleModelAdmin, NotMutableMixin +from core.admin import CodeLibelleModelAdmin, NotMutableMixin from qfdmo.admin.widgets import CategorieChoiceWidget, SousCategorieChoiceWidget from qfdmo.models import ( Acteur, @@ -143,7 +143,7 @@ def __init__(self, *args, **kwargs): self.fields["source"].queryset = Source.objects.all().order_by("libelle") -class BaseActeurAdmin(admin.GISModelAdmin, BaseAdmin): +class BaseActeurAdmin(admin.GISModelAdmin): form = BaseActeurForm gis_widget = CustomOSMWidget inlines = [ From 07fad5fecca9cf174a28dfc6db1fe3c64548ef51 Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Thu, 24 Jul 2025 20:54:02 +0200 Subject: [PATCH 05/15] Fix sous_categorie param name and form handling --- jinja2/qfdmo/carte.html | 3 +++ qfdmd/models.py | 2 +- qfdmo/forms.py | 5 ++++- qfdmo/views/carte.py | 27 +++++++++++++++++++++------ 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/jinja2/qfdmo/carte.html b/jinja2/qfdmo/carte.html index 6e86caac7..79a6e079f 100644 --- a/jinja2/qfdmo/carte.html +++ b/jinja2/qfdmo/carte.html @@ -34,6 +34,9 @@ data-search-form-visible data-search-solution-form-map-container-id-value="{{ map_container_id }}" > +
+ {{ displayed_acteur_form }} +
Date: Fri, 25 Jul 2025 13:48:53 +0200 Subject: [PATCH 06/15] WIP --- jinja2/qfdmo/carte/results.html | 2 +- qfdmo/models/acteur.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/jinja2/qfdmo/carte/results.html b/jinja2/qfdmo/carte/results.html index f9fc00c11..7e79da822 100644 --- a/jinja2/qfdmo/carte/results.html +++ b/jinja2/qfdmo/carte/results.html @@ -35,7 +35,7 @@ > {% for acteur in acteurs %} {% endfor %} diff --git a/qfdmo/models/acteur.py b/qfdmo/models/acteur.py index 7abdf24e1..a73bc47c7 100644 --- a/qfdmo/models/acteur.py +++ b/qfdmo/models/acteur.py @@ -5,7 +5,7 @@ import string import uuid from copy import deepcopy -from typing import Any, List, cast +from typing import Any, cast from urllib.parse import urlencode import opening_hours @@ -1103,16 +1103,16 @@ def get_absolute_url(self): return reverse("qfdmo:acteur-detail", args=[self.uuid]) def acteur_actions( - self, direction=None, actions_codes=None, sous_categorie_id=None + self, direction=None, actions_codes=None, sous_categorie_ids=None ): pss = self.proposition_services.all() # Cast needed because of the cache cached_action_instances = cast( - List[Action], cache.get_or_set("_action_instances", get_action_instances) + list[Action], cache.get_or_set("_action_instances", get_action_instances) ) - if sous_categorie_id: - pss = pss.filter(sous_categories__id__in=[sous_categorie_id]) + if sous_categorie_ids: + pss = pss.filter(sous_categories__id__in=sous_categorie_ids) if direction: pss = pss.filter(action__directions__code__in=[direction]) if actions_codes: @@ -1166,14 +1166,20 @@ def json_acteur_for_display( carte: bool = False, carte_config: CarteConfig = None, sous_categorie_id: str | None = None, + displayed_acteur_form=None, ) -> str: # TODO: refacto jinja: once the shared/results.html template # will be migrated to django template, this method should # live in a template_tags instead. + sous_categorie_ids = [sous_categorie_id] + + if sous_categories := displayed_acteur_form.cleaned_data.get("sous_categories"): + sous_categorie_ids = sous_categories.values_list("id", flat=True) + actions = self.acteur_actions( direction=direction, actions_codes=action_list, - sous_categorie_id=sous_categorie_id, + sous_categorie_ids=sous_categorie_ids, ) def sort_actions_by_action_principale_and_order(a): From 2fcb84c33be6d9f867e243a1094076974d85a03c Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Fri, 25 Jul 2025 14:32:48 +0200 Subject: [PATCH 07/15] Remove legacy approach --- qfdmd/models.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qfdmd/models.py b/qfdmd/models.py index da6c6f120..e2a352da6 100644 --- a/qfdmd/models.py +++ b/qfdmd/models.py @@ -326,7 +326,6 @@ def get_etats_descriptions(self) -> tuple[str, str] | None: @property def carte_settings(self): - # TODO : gérer plusieurs catégories ici sous_categories = self.sous_categories.filter(afficher_carte=True).all() settings_querydict = QueryDict(mutable=True) @@ -338,8 +337,6 @@ def carte_settings(self): "direction": "jai", "first_dir": "jai", "limit": 25, - "sc_id": sous_categories.first().id, - "sous_categorie_objet": sous_categories.first().libelle, } ) From 03f36bc3504ee16f65193bc4cb499ecedaccdf0b Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Fri, 25 Jul 2025 14:32:56 +0200 Subject: [PATCH 08/15] Update state.ts --- static/to_compile/controllers/assistant/state.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/to_compile/controllers/assistant/state.ts b/static/to_compile/controllers/assistant/state.ts index 2bb8b74df..16d685850 100644 --- a/static/to_compile/controllers/assistant/state.ts +++ b/static/to_compile/controllers/assistant/state.ts @@ -114,17 +114,17 @@ export default class extends Controller { updateUIFromGlobalState(outlet) { const value = this.locationValue let touched = false - if (value.adresse && value.adresse !== outlet.inputTarget.value) { + if (value.adresse) { outlet.inputTarget.value = value.adresse touched = true } - if (value.latitude && outlet.latitudeTarget.value !== value.latitude) { + if (value.latitude) { outlet.latitudeTarget.value = value.latitude touched = true } - if (value.longitude && outlet.longitudeTarget.value !== value.longitude) { + if (value.longitude) { outlet.longitudeTarget.value = value.longitude touched = true } From f63b5c15ff1bd018d2ae9a64d68826be79dbeb3e Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Fri, 25 Jul 2025 14:35:19 +0200 Subject: [PATCH 09/15] Remove debug logging statements --- qfdmo/views/carte.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qfdmo/views/carte.py b/qfdmo/views/carte.py index ebaedfb32..a1e135a41 100644 --- a/qfdmo/views/carte.py +++ b/qfdmo/views/carte.py @@ -20,7 +20,6 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def get_initial(self, *args, **kwargs): - initial = super().get_initial(*args, **kwargs) action_displayed = self._set_action_displayed() grouped_action_choices = self._get_grouped_action_choices(action_displayed) @@ -37,8 +36,6 @@ def get_initial(self, *args, **kwargs): return initial def get_context_data(self, **kwargs): - logger.info("get_context_data 🪏") - logger.info(f"{self.request.GET=}") self.displayed_acteur_form = DisplayedActeursForm(self.request.GET) if not self.displayed_acteur_form.is_valid(): logger.error(f"Form is valid {self.displayed_acteur_form=}") @@ -56,11 +53,9 @@ def _get_selected_action_ids(self): return [a.id for a in self._get_selected_action()] def get_sous_categories(self): - logger.info("get_sous_categories 💣") if sous_categories := self.displayed_acteur_form.cleaned_data.get( "sous_categories" ): - logger.info(f"{sous_categories=}") return sous_categories.values_list("pk", flat=True) return [] From 12f0a71f7713e6394f64d95cff2189259d76265a Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Fri, 25 Jul 2025 14:55:53 +0200 Subject: [PATCH 10/15] Extract legacy form data access into a mixin The change moves request data access methods into a new LegacyMethodsMixin to prepare for future form validation improvements. --- qfdmo/views/adresses.py | 141 ++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 49 deletions(-) diff --git a/qfdmo/views/adresses.py b/qfdmo/views/adresses.py index 44b3a17e5..865eb2861 100644 --- a/qfdmo/views/adresses.py +++ b/qfdmo/views/adresses.py @@ -82,7 +82,92 @@ def get_context_data(self, **kwargs): return context +class LegacyMethodsMixin: + """ + A previous approach in carte form / views consisted on getting values from + request directly. + + This is not the best way to get these using django, usually a form is + instantiated from the request to bring powerful form validation. + + In order to gradually migrate the view from the previous to the new approach, every + data manually pulled from the request is isolated in its own method. + + These method can then easily be overriden in the class that inherits from + SearchActeursView. + + The current mixin has been created in order to easily read the legacy methods + so that they can be removed later. + """ + + def get_sous_categorie_objet(self) -> str: + return self.get_data_from_request_or_bounded_form("sous_categorie_objet") + + def get_sc_id(self, initial) -> str | None: + return ( + self.get_data_from_request_or_bounded_form("sc_id") + if initial["sous_categorie_objet"] + else None + ) + + def get_sous_categories_ids(self) -> list[int]: + return [self.get_data_from_request_or_bounded_form("sc_id", 0)] + + def get_adresse(self) -> str: + return self.get_data_from_request_or_bounded_form("adresse") + + def get_longitude(self) -> str: + return self.get_data_from_request_or_bounded_form("longitude") + + def get_latitude(self) -> str: + return self.get_data_from_request_or_bounded_form("latitude") + + def get_digital(self) -> str: + return self.get_data_from_request_or_bounded_form("digital", "0") + + def get_data_from_request_or_bounded_form(self, key: str, default=None): + """Temporary dummy method + + There is a flaw in the way the form is instantiated, because the + form is never bounded to its data. + The request is directly used to perform various tasks, like + populating some multiple choice field choices, hence missing all + the validation provided by django forms. + + To prepare a future refactor of this form, the method here calls + the cleaned_data when the form is bounded and the request.GET + QueryDict when it is not bounded. + Note : we call getlist and not get because in some cases, the request + parameters needs to be treated as a list. + + The form is currently used for various use cases: + - The map form + - The "iframe form" form (for https://epargnonsnosressources.gouv.fr) + - The turbo-frames + The form should be bounded at least when used in turbo-frames. + + The name is explicitely very verbose because it is not meant to stay + a long time as is. + + TODO: refacto forms : get rid of this method and use cleaned_data when + form is valid and request.GET for non-field request parameters + + Edit 25 july 2025 : the refactoring has started and can be found in + LegacyMethodMixin above. + """ + try: + return self.cleaned_data.get(key, default) + except AttributeError: + pass + + try: + return self.request.GET.get(key, default) + except AttributeError: + return self.request.GET.getlist(key, default) + + class SearchActeursView( + LegacyMethodsMixin, DigitalMixin, TurboFormMixin, FormView, @@ -95,15 +180,15 @@ class SearchActeursView( def get_initial(self): initial = super().get_initial() # TODO: refacto forms : delete this line - initial["sous_categorie_objet"] = self.request.GET.get("sous_categorie_objet") + initial["sous_categorie_objet"] = self.get_sous_categorie_objet() # TODO: refacto forms : delete this line - initial["adresse"] = self.request.GET.get("adresse") - initial["digital"] = self.request.GET.get("digital", "0") + initial["adresse"] = self.get_adresse() + initial["digital"] = self.get_digital() initial["direction"] = get_direction(self.request, self.is_carte) # TODO: refacto forms : delete this line - initial["latitude"] = self.request.GET.get("latitude") + initial["latitude"] = self.get_latitude() # TODO: refacto forms : delete this line - initial["longitude"] = self.request.GET.get("longitude") + initial["longitude"] = self.get_longitude() # TODO: refacto forms : delete this line initial["label_reparacteur"] = self.request.GET.get("label_reparacteur") initial["epci_codes"] = self.request.GET.getlist("epci_codes") @@ -116,10 +201,7 @@ def get_initial(self): initial["ess"] = self.request.GET.get("ess") # TODO: refacto forms : delete this line initial["bounding_box"] = self.request.GET.get("bounding_box") - initial["sc_id"] = ( - self.request.GET.get("sc_id") if initial["sous_categorie_objet"] else None - ) - + initial["sc_id"] = self.get_sc_id(initial) # Action to display and check action_displayed = self._set_action_displayed() initial["action_displayed"] = "|".join([a.code for a in action_displayed]) @@ -160,42 +242,6 @@ def get_form(self, form_class=None): return form - def get_data_from_request_or_bounded_form(self, key: str, default=None): - """Temporary dummy method - - There is a flaw in the way the form is instantiated, because the - form is never bounded to its data. - The request is directly used to perform various tasks, like - populating some multiple choice field choices, hence missing all - the validation provided by django forms. - - To prepare a future refactor of this form, the method here calls - the cleaned_data when the form is bounded and the request.GET - QueryDict when it is not bounded. - Note : we call getlist and not get because in some cases, the request - parameters needs to be treated as a list. - - The form is currently used for various use cases: - - The map form - - The "iframe form" form (for https://epargnonsnosressources.gouv.fr) - - The turbo-frames - The form should be bounded at least when used in turbo-frames. - - The name is explicitely very verbose because it is not meant to stay - a long time as is. - - TODO: refacto forms : get rid of this method and use cleaned_data when - form is valid and request.GET for non-field request parameters""" - try: - return self.cleaned_data.get(key, default) - except AttributeError: - pass - - try: - return self.request.GET.get(key, default) - except AttributeError: - return self.request.GET.getlist(key, default) - def get_context_data(self, **kwargs): form = self.get_form_class()(self.request.GET) @@ -444,7 +490,7 @@ def _compile_acteurs_queryset( if self.get_data_from_request_or_bounded_form("bonus"): filters &= Q(labels__bonus=True) - if sous_categorie_ids := self.get_sous_categories(): + if sous_categorie_ids := self.get_sous_categories_ids(): filters &= Q( proposition_services__sous_categories__id__in=sous_categorie_ids, ) @@ -472,9 +518,6 @@ def _compile_acteurs_queryset( return filters, excludes - def get_sous_categories(self): - return [self.get_data_from_request_or_bounded_form("sc_id", 0)] - def get_cached_groupe_action_with_displayed_actions(self, action_displayed): # Cast needed because of the cache cached_groupe_action_instances = cast( From 108a6bb9daad22b5a2dc92e6085b12b1e70198ff Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Fri, 25 Jul 2025 15:02:00 +0200 Subject: [PATCH 11/15] Fix sous_categorie_ids handling in acteur filtering --- qfdmo/models/acteur.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qfdmo/models/acteur.py b/qfdmo/models/acteur.py index a73bc47c7..754bb4a6d 100644 --- a/qfdmo/models/acteur.py +++ b/qfdmo/models/acteur.py @@ -1112,6 +1112,7 @@ def acteur_actions( ) if sous_categorie_ids: + print(f"{sous_categorie_ids=}") pss = pss.filter(sous_categories__id__in=sous_categorie_ids) if direction: pss = pss.filter(action__directions__code__in=[direction]) @@ -1171,10 +1172,15 @@ def json_acteur_for_display( # TODO: refacto jinja: once the shared/results.html template # will be migrated to django template, this method should # live in a template_tags instead. - sous_categorie_ids = [sous_categorie_id] + sous_categorie_ids = [] - if sous_categories := displayed_acteur_form.cleaned_data.get("sous_categories"): + if ( + sous_categories := displayed_acteur_form + and displayed_acteur_form.cleaned_data.get("sous_categories") + ): sous_categorie_ids = sous_categories.values_list("id", flat=True) + elif sous_categorie_id: + sous_categorie_ids.append(sous_categorie_id) actions = self.acteur_actions( direction=direction, From a62108721f3831ab06fe8621bc961c69dc2dfd93 Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Fri, 25 Jul 2025 15:44:21 +0200 Subject: [PATCH 12/15] Remove unnecessary code and refactor request handling The changes clean up redundant code and simplify request parameter handling by directly using request.GET instead of a wrapper method. --- qfdmo/views/adresses.py | 14 +++++--------- qfdmo/views/carte.py | 11 ++--------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/qfdmo/views/adresses.py b/qfdmo/views/adresses.py index 865eb2861..e1fea2742 100644 --- a/qfdmo/views/adresses.py +++ b/qfdmo/views/adresses.py @@ -105,25 +105,23 @@ def get_sous_categorie_objet(self) -> str: def get_sc_id(self, initial) -> str | None: return ( - self.get_data_from_request_or_bounded_form("sc_id") - if initial["sous_categorie_objet"] - else None + self.request.GET.get("sc_id") if initial["sous_categorie_objet"] else None ) def get_sous_categories_ids(self) -> list[int]: return [self.get_data_from_request_or_bounded_form("sc_id", 0)] def get_adresse(self) -> str: - return self.get_data_from_request_or_bounded_form("adresse") + return self.request.GET.get("adresse") def get_longitude(self) -> str: - return self.get_data_from_request_or_bounded_form("longitude") + return self.request.GET.get("longitude") def get_latitude(self) -> str: - return self.get_data_from_request_or_bounded_form("latitude") + return self.request.GET.get("latitude") def get_digital(self) -> str: - return self.get_data_from_request_or_bounded_form("digital", "0") + return self.request.GET.get("digital", "0") def get_data_from_request_or_bounded_form(self, key: str, default=None): """Temporary dummy method @@ -248,8 +246,6 @@ def get_context_data(self, **kwargs): kwargs.update( # TODO: refacto forms : define a BooleanField carte on CarteAddressesForm carte=self.is_carte, - # TODO: refacto forms, return bounded form in template - # form=form, location="{}", ) diff --git a/qfdmo/views/carte.py b/qfdmo/views/carte.py index a1e135a41..3ed75ddc5 100644 --- a/qfdmo/views/carte.py +++ b/qfdmo/views/carte.py @@ -16,9 +16,6 @@ class CarteSearchActeursView(SearchActeursView): template_name = "qfdmo/carte.html" form_class = CarteForm - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - def get_initial(self, *args, **kwargs): initial = super().get_initial(*args, **kwargs) action_displayed = self._set_action_displayed() @@ -37,6 +34,7 @@ def get_initial(self, *args, **kwargs): def get_context_data(self, **kwargs): self.displayed_acteur_form = DisplayedActeursForm(self.request.GET) + if not self.displayed_acteur_form.is_valid(): logger.error(f"Form is valid {self.displayed_acteur_form=}") @@ -58,7 +56,7 @@ def get_sous_categories(self): ): return sous_categories.values_list("pk", flat=True) - return [] + # return super().get_sous_categories() class ProductCarteView(CarteSearchActeursView): @@ -78,11 +76,6 @@ class CustomCarteView(DetailView, CarteSearchActeursView): model = CarteConfig context_object_name = "carte_config" - def get_context_data(self, *args, **kwargs): - ctx = super().get_context_data(*args, **kwargs) - - return ctx - @cached_property def groupe_actions(self): # TODO: cache From f8de5cfc1eba26969ce404d5075769c3f5ce4cdc Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Fri, 25 Jul 2025 15:46:07 +0200 Subject: [PATCH 13/15] Update models.py --- qfdmd/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qfdmd/models.py b/qfdmd/models.py index e2a352da6..ae6eca17a 100644 --- a/qfdmd/models.py +++ b/qfdmd/models.py @@ -471,7 +471,6 @@ def get_url_carte(self, actions=None, map_container_id=None): ) params = carte_settings.urlencode() - logger.info(f"{params=}") url = reverse("qfdmd:carte", args=[self.slug]) return f"{url}?{params}" From 059ac5a529fb840701789adb64b7cccbecd3a373 Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Fri, 25 Jul 2025 15:59:52 +0200 Subject: [PATCH 14/15] WIP --- qfdmo/models/acteur.py | 1 - qfdmo/views/adresses.py | 6 +++++- qfdmo/views/carte.py | 2 +- unit_tests/qfdmo/test_acteur.py | 3 --- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/qfdmo/models/acteur.py b/qfdmo/models/acteur.py index 754bb4a6d..cc12d0146 100644 --- a/qfdmo/models/acteur.py +++ b/qfdmo/models/acteur.py @@ -1112,7 +1112,6 @@ def acteur_actions( ) if sous_categorie_ids: - print(f"{sous_categorie_ids=}") pss = pss.filter(sous_categories__id__in=sous_categorie_ids) if direction: pss = pss.filter(action__directions__code__in=[direction]) diff --git a/qfdmo/views/adresses.py b/qfdmo/views/adresses.py index e1fea2742..7fea0ef90 100644 --- a/qfdmo/views/adresses.py +++ b/qfdmo/views/adresses.py @@ -109,7 +109,11 @@ def get_sc_id(self, initial) -> str | None: ) def get_sous_categories_ids(self) -> list[int]: - return [self.get_data_from_request_or_bounded_form("sc_id", 0)] + sous_categorie = self.get_data_from_request_or_bounded_form("sc_id", 0) + if not sous_categorie: + return [] + + return [sous_categorie] def get_adresse(self) -> str: return self.request.GET.get("adresse") diff --git a/qfdmo/views/carte.py b/qfdmo/views/carte.py index 3ed75ddc5..adeb89500 100644 --- a/qfdmo/views/carte.py +++ b/qfdmo/views/carte.py @@ -56,7 +56,7 @@ def get_sous_categories(self): ): return sous_categories.values_list("pk", flat=True) - # return super().get_sous_categories() + return super().get_sous_categories() class ProductCarteView(CarteSearchActeursView): diff --git a/unit_tests/qfdmo/test_acteur.py b/unit_tests/qfdmo/test_acteur.py index ba04dfa33..18a4c3171 100644 --- a/unit_tests/qfdmo/test_acteur.py +++ b/unit_tests/qfdmo/test_acteur.py @@ -470,9 +470,6 @@ def test_duplicate_labels(self): label2 = LabelQualiteFactory() revision_acteur.labels.add(label1) revision_acteur.labels.add(label2) - - print(revision_acteur.labels.all()) - revision_acteur_duplicate = revision_acteur.duplicate() assert revision_acteur_duplicate.labels.count() == 2 From 576ee0d15497ee2d8882be9cd276b67dc6b3537d Mon Sep 17 00:00:00 2001 From: Fabien Le Frapper Date: Fri, 1 Aug 2025 12:25:26 +0200 Subject: [PATCH 15/15] Rename get_sous_categories to get_sous_categories_ids --- qfdmo/views/carte.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qfdmo/views/carte.py b/qfdmo/views/carte.py index adeb89500..624d7e889 100644 --- a/qfdmo/views/carte.py +++ b/qfdmo/views/carte.py @@ -50,13 +50,13 @@ def get_context_data(self, **kwargs): def _get_selected_action_ids(self): return [a.id for a in self._get_selected_action()] - def get_sous_categories(self): + def get_sous_categories_ids(self): if sous_categories := self.displayed_acteur_form.cleaned_data.get( "sous_categories" ): return sous_categories.values_list("pk", flat=True) - return super().get_sous_categories() + return super().get_sous_categories_ids() class ProductCarteView(CarteSearchActeursView):