from django.db.models import Q
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _
from django_filters import ChoiceFilter, MultipleChoiceFilter

from mapentity.filters import PolygonFilter, PythonPolygonFilter

from geotrek.core.models import Topology
from geotrek.authent.filters import StructureRelatedFilterSet
from geotrek.common.filters import RightFilter
from geotrek.zoning.filters import ZoningFilterSet
from geotrek.zoning.models import City, District

from .models import Intervention, Project

if 'geotrek.signage' in settings.INSTALLED_APPS:
    from geotrek.signage.models import Blade


class PolygonInterventionFilterMixin(object):
    def get_geom(self, value):
        return value

    def filter(self, qs, values):
        if not values:
            return qs
        lookup = self.lookup_expr

        blade_content_type = ContentType.objects.get_for_model(Blade)
        topologies = []
        for value in values:
            topologies += Topology.objects.filter(**{'geom__%s' % lookup: self.get_geom(value)}).values_list('id', flat=True)
        topologies_intervention = Intervention.objects.existing().filter(target_id__in=topologies).exclude(
            target_type=blade_content_type).distinct('pk').values_list('id', flat=True)

        interventions = list(topologies_intervention)
        if 'geotrek.signage' in settings.INSTALLED_APPS:
            blades = list(Blade.objects.filter(signage__in=topologies).values_list('id', flat=True))
            blades_intervention = Intervention.objects.existing().filter(target_id__in=blades,
                                                                         target_type=blade_content_type).values_list('id',
                                                                                                                     flat=True)
            interventions.extend(blades_intervention)
        if hasattr(self, 'lookup_queryset_in'):
            lookup_queryset = self.lookup_queryset_in
        else:
            lookup_queryset = 'pk__in'
        qs = qs.filter(**{'%s' % lookup_queryset: interventions})
        return qs


class PolygonTopologyFilter(PolygonInterventionFilterMixin, PolygonFilter):
    pass


class ProjectIntersectionFilterCity(PolygonInterventionFilterMixin, RightFilter):
    model = City

    def __init__(self, *args, **kwargs):
        super(ProjectIntersectionFilterCity, self).__init__(*args, **kwargs)
        self.lookup_expr = 'intersects'
        self.lookup_queryset_in = 'interventions__in'

    def get_geom(self, value):
        return value.geom


class ProjectIntersectionFilterDistrict(PolygonInterventionFilterMixin, RightFilter):
    model = District

    def __init__(self, *args, **kwargs):
        super(ProjectIntersectionFilterDistrict, self).__init__(*args, **kwargs)
        self.lookup_expr = 'intersects'
        self.lookup_queryset_in = 'interventions__in'

    def get_geom(self, value):
        return value.geom


class InterventionFilterSet(ZoningFilterSet, StructureRelatedFilterSet):
    ON_CHOICES = (('infrastructure', _("Infrastructure")), ('signage', _("Signage")), ('blade', _("Blade")),
                  ('topology', _("Path")), ('trek', _("Trek")), ('poi', _("POI")), ('service', _("Service")),
                  ('trail', _("Trail")))
    bbox = PolygonTopologyFilter(lookup_expr='intersects')
    year = MultipleChoiceFilter(choices=Intervention.objects.year_choices(),
                                field_name='date', lookup_expr='year', label=_("Year"))
    on = ChoiceFilter(field_name='target_type__model', choices=ON_CHOICES, label=_("On"), empty_label=_("On"))

    class Meta(StructureRelatedFilterSet.Meta):
        model = Intervention
        fields = StructureRelatedFilterSet.Meta.fields + [
            'status', 'type', 'stake', 'subcontracting', 'project', 'on',
        ]


class ProjectFilterSet(StructureRelatedFilterSet):
    bbox = PythonPolygonFilter(field_name='geom')
    year = MultipleChoiceFilter(
        label=_("Year of activity"), method='filter_year',
        choices=lambda: Project.objects.year_choices()  # Could change over time
    )
    city = ProjectIntersectionFilterCity(label=_('City'), required=False)
    district = ProjectIntersectionFilterDistrict(label=_('District'), required=False)

    class Meta(StructureRelatedFilterSet.Meta):
        model = Project
        fields = StructureRelatedFilterSet.Meta.fields + [
            'year', 'type', 'domain', 'contractors', 'project_owner',
            'project_manager', 'founders'
        ]

    def filter_year(self, qs, name, values):
        q = Q()
        for value in values:
            q |= Q(begin_year__lte=value, end_year__gte=value)
        return qs.filter(q)

geotrek/maintenance/filters.py
from django.db.models import Q
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _
from django_filters import ChoiceFilter, MultipleChoiceFilter

from mapentity.filters import PolygonFilter, PythonPolygonFilter

from geotrek.core.models import Topology
from geotrek.authent.filters import StructureRelatedFilterSet
from geotrek.common.filters import RightFilter
from geotrek.zoning.filters import ZoningFilterSet
from geotrek.zoning.models import City, District

from .models import Intervention, Project

if 'geotrek.signage' in settings.INSTALLED_APPS:
    from geotrek.signage.models import Blade


class PolygonInterventionFilterMixin(object):
    def get_geom(self, value):
        return value

    def filter(self, qs, values):
        if not values:
            return qs
        if not isinstance(values, list):
            values = [values]

        lookup = self.lookup_expr

        blade_content_type = ContentType.objects.get_for_model(Blade)
        topologies = []
        for value in values:
            topologies += Topology.objects.filter(**{'geom__%s' % lookup: self.get_geom(value)}).values_list('id', flat=True)
        topologies_intervention = Intervention.objects.existing().filter(target_id__in=topologies).exclude(
            target_type=blade_content_type).distinct('pk').values_list('id', flat=True)

        interventions = list(topologies_intervention)
        if 'geotrek.signage' in settings.INSTALLED_APPS:
            blades = list(Blade.objects.filter(signage__in=topologies).values_list('id', flat=True))
            blades_intervention = Intervention.objects.existing().filter(target_id__in=blades,
                                                                         target_type=blade_content_type).values_list('id',
                                                                                                                     flat=True)
            interventions.extend(blades_intervention)
        if hasattr(self, 'lookup_queryset_in'):
            lookup_queryset = self.lookup_queryset_in
        else:
            lookup_queryset = 'pk__in'
        qs = qs.filter(**{'%s' % lookup_queryset: interventions})
        return qs


class PolygonTopologyFilter(PolygonInterventionFilterMixin, PolygonFilter):
    pass


class ProjectIntersectionFilterCity(PolygonInterventionFilterMixin, RightFilter):
    model = City

    def __init__(self, *args, **kwargs):
        super(ProjectIntersectionFilterCity, self).__init__(*args, **kwargs)
        self.lookup_expr = 'intersects'
        self.lookup_queryset_in = 'interventions__in'

    def get_geom(self, value):
        return value.geom


class ProjectIntersectionFilterDistrict(PolygonInterventionFilterMixin, RightFilter):
    model = District

    def __init__(self, *args, **kwargs):
        super(ProjectIntersectionFilterDistrict, self).__init__(*args, **kwargs)
        self.lookup_expr = 'intersects'
        self.lookup_queryset_in = 'interventions__in'

    def get_geom(self, value):
        return value.geom


class InterventionFilterSet(ZoningFilterSet, StructureRelatedFilterSet):
    ON_CHOICES = (('infrastructure', _("Infrastructure")), ('signage', _("Signage")), ('blade', _("Blade")),
                  ('topology', _("Path")), ('trek', _("Trek")), ('poi', _("POI")), ('service', _("Service")),
                  ('trail', _("Trail")))
    bbox = PolygonTopologyFilter(lookup_expr='intersects')
    year = MultipleChoiceFilter(choices=Intervention.objects.year_choices(),
                                field_name='date', lookup_expr='year', label=_("Year"))
    on = ChoiceFilter(field_name='target_type__model', choices=ON_CHOICES, label=_("On"), empty_label=_("On"))

    class Meta(StructureRelatedFilterSet.Meta):
        model = Intervention
        fields = StructureRelatedFilterSet.Meta.fields + [
            'status', 'type', 'stake', 'subcontracting', 'project', 'on',
        ]


class ProjectFilterSet(StructureRelatedFilterSet):
    bbox = PythonPolygonFilter(field_name='geom')
    year = MultipleChoiceFilter(
        label=_("Year of activity"), method='filter_year',
        choices=lambda: Project.objects.year_choices()  # Could change over time
    )
    city = ProjectIntersectionFilterCity(label=_('City'), required=False)
    district = ProjectIntersectionFilterDistrict(label=_('District'), required=False)

    class Meta(StructureRelatedFilterSet.Meta):
        model = Project
        fields = StructureRelatedFilterSet.Meta.fields + [
            'year', 'type', 'domain', 'contractors', 'project_owner',
            'project_manager', 'founders'
        ]

    def filter_year(self, qs, name, values):
        q = Q()
        for value in values:
            q |= Q(begin_year__lte=value, end_year__gte=value)
        return qs.filter(q)

geotrek/maintenance/filters.py
diff --git a/geotrek/maintenance/filters.py b/geotrek/maintenance/filters.py
index 0aaebdf5dd..2e3d4590aa 100644
--- a/geotrek/maintenance/filters.py
+++ b/geotrek/maintenance/filters.py
@@ -25,6 +25,9 @@ def get_geom(self, value):
     def filter(self, qs, values):
         if not values:
             return qs
+        if not isinstance(values, list):
+            values = [values]
+
         lookup = self.lookup_expr
 
         blade_content_type = ContentType.objects.get_for_model(Blade)
diff --git a/geotrek/maintenance/tests/test_filters.py b/geotrek/maintenance/tests/test_filters.py
index f66cc4818c..ce516f3c1b 100644
--- a/geotrek/maintenance/tests/test_filters.py
+++ b/geotrek/maintenance/tests/test_filters.py
@@ -31,6 +31,19 @@ def setUpTestData(cls):
         InterventionFactory.create(target=topo_1)
         cls.seek_inter = InterventionFactory.create(target=seek_topo)
 
+    def test_in_bbox(self):
+        xmin, ymin, xmax, ymax = self.seek_inter.geom.transform(settings.API_SRID, clone=True).extent
+        bbox = Polygon([[xmin - 1, ymin - 1], [xmin - 1, ymax + 1], [xmax + 1, ymax + 1], [xmax + 1, ymin - 1], [xmin - 1, ymin - 1]])
+        qs = InterventionFilterSet({'bbox': bbox.wkt}).qs
+        self.assertEqual(len(qs), 1)
+        self.assertEqual(qs[0], self.seek_inter)
+
+    def test_out_bbox(self):
+        xmin, ymin, xmax, ymax = self.seek_inter.geom.transform(settings.API_SRID, clone=True).extent
+        bbox = Polygon([[xmax + 1, ymax + 1], [xmax + 1, ymax + 2], [xmax + 2, ymax + 2], [xmax + 2, ymax + 1], [xmax + 1, ymax + 1]])
+        qs = InterventionFilterSet({'bbox': bbox.wkt}).qs
+        self.assertEqual(len(qs), 0)
+
     def test_filter_by_physical_edge(self):
         edge = PhysicalEdgeFactory(paths=[self.seek_path])
 

Bug affichage intervention selon niveau de zoom
Bonjour, sur la 2.49.0 selon le niveau de zoom sélectionné, certaines interventions apparaissent et disparaissent. Faire glisser la carte provoque aussi ce comportement :


https://user-images.githubusercontent.com/45095227/108825316-2719b200-75c3-11eb-8406-11d57bcd2531.mov

J'en profite pour dire que je n'ai pas pu changer l'opacité des linéaires pour qu'ils soient plus visibles ( voir : https://github.com/GeotrekCE/Geotrek-admin/issues/2554)
