Repository: GeotrekCE/Geotrek-admin

Issue:
API V2 cache problem with custom configuration in nginx.conf.in
API v2 responses doesn't take care about [```X-FORWARDED-PROTO```](https://github.com/GeotrekCE/Geotrek-admin/blob/master/conf/nginx.conf.in#L55) value defined in nginx configuration.
This header is used by django to know if generated url (pictogram, attachment) is http or https.
In some cases, custom configuration in nginx.conf.in to work behind haproxy, API response does not match with headers.

For the moment, any change about it in nginx template should be followed by clearing api_v2 cache in admin panel


Discussion:

Resolving patch:
diff --git a/geotrek/api/v2/viewsets.py b/geotrek/api/v2/viewsets.py
--- a/geotrek/api/v2/viewsets.py
+++ b/geotrek/api/v2/viewsets.py
@@ -33,7 +33,8 @@ def get_ordered_query_params(self):
 
     def get_base_cache_string(self):
         """ return cache string as url path + ordered query params """
-        return f"{self.request.path}:{self.get_ordered_query_params()}:{self.request.accepted_renderer.format}"
+        proto_scheme = self.request.headers.get('X-Forwarded-Proto', self.request.scheme)  # take care about scheme defined in nginx.conf
+        return f"{self.request.path}:{self.get_ordered_query_params()}:{self.request.accepted_renderer.format}:{proto_scheme}"
 
     def get_object_cache_key(self, pk):
         """ return specific object cache key based on object date_update column"""


Test patch:
diff --git a/geotrek/api/tests/test_v2.py b/geotrek/api/tests/test_v2.py
--- a/geotrek/api/tests/test_v2.py
+++ b/geotrek/api/tests/test_v2.py
@@ -13,6 +13,7 @@
 from django.utils import timezone
 from freezegun.api import freeze_time
 from mapentity.tests.factories import SuperUserFactory
+from rest_framework.test import APITestCase
 
 from geotrek import __version__
 from geotrek.authent import models as authent_models
@@ -38,6 +39,7 @@
 from geotrek.tourism.tests import factories as tourism_factory
 from geotrek.trekking import models as trek_models
 from geotrek.trekking.tests import factories as trek_factory
+from geotrek.trekking.tests.factories import PracticeFactory
 from geotrek.zoning import models as zoning_models
 from geotrek.zoning.tests import factories as zoning_factory
 
@@ -4255,3 +4257,41 @@ def test_cache_is_used_when_getting_trek_profile_svg(self):
             response = self.client.get(reverse('apiv2:trek-profile', args=(self.trek.pk,)), {"format": "svg"})
         self.assertEqual(response.status_code, 200)
         self.assertIn('image/svg+xml', response['Content-Type'])
+
+
+class GenericCacheTestCase(APITestCase):
+    @classmethod
+    def setUpTestData(cls):
+        cls.practice = PracticeFactory.create()
+
+    def test_cache_invalidates_along_x_forwarded_proto_header(self):
+        with self.assertNumQueries(2):
+            response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,)))
+        data = response.json()
+        self.assertTrue(data['pictogram'].startswith('http://'))
+
+        # after cache hit, query number is 1
+        with self.assertNumQueries(1):
+            response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,)))
+        data = response.json()
+        self.assertTrue(data['pictogram'].startswith('http://'))
+
+        # we used custom header, cache is invalidate and url is now https
+        with self.assertNumQueries(2):
+            response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,)),
+                                       HTTP_X_FORWARDED_PROTO='https')
+        data = response.json()
+        self.assertTrue(data['pictogram'].startswith('https://'))
+
+        # cache is hit
+        with self.assertNumQueries(1):
+            response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,)),
+                                       HTTP_X_FORWARDED_PROTO='https')
+        data = response.json()
+        self.assertTrue(data['pictogram'].startswith('https://'))
+
+        # first request is always cached
+        with self.assertNumQueries(1):
+            response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,)))
+        data = response.json()
+        self.assertTrue(data['pictogram'].startswith('http://'))