We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Frontend feature flags
## Feature Request

**Is your feature request related to a problem or unsupported use case? Please describe.**
We use feature flags configurable through environment variable to show/hide next features. Currently, those flags are only accessible by the backend. Those feature flags should be accessible by the frontend.

**Describe the solution you'd like**
We should bind FEATURES settings into the Richie frontend context through the web_processors.

**Do you want to work on it through a Pull Request?**
Yup


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/richie/apps/core/context_processors.py`
Content:
```
1 """
2 Template context processors
3 """
4 import json
5 from collections import OrderedDict
6 from urllib.parse import urlparse
7 
8 from django.conf import settings
9 from django.contrib.sites.models import Site
10 from django.core.files.storage import get_storage_class
11 from django.http.request import HttpRequest
12 from django.middleware.csrf import get_token
13 from django.utils.translation import get_language_from_request
14 
15 from richie.apps.core.templatetags.joanie import is_joanie_enabled
16 from richie.apps.courses.defaults import RICHIE_MAX_ARCHIVED_COURSE_RUNS
17 from richie.apps.courses.models import Organization
18 
19 from . import defaults
20 
21 
22 def site_metas(request: HttpRequest):
23     """
24     Context processor to add all information required by Richie CMS templates and frontend.
25 
26     If `CDN_DOMAIN` settings is defined we add it in the context. It allows
27     to load statics js on a CDN like cloudfront.
28     """
29     site_current = Site.objects.get_current()
30     protocol = "https" if request.is_secure() else "http"
31 
32     context = {
33         **{
34             f"GLIMPSE_PAGINATION_{k.upper()}": v
35             for k, v in {
36                 **defaults.GLIMPSE_PAGINATION,
37                 **getattr(settings, "RICHIE_GLIMPSE_PAGINATION", {}),
38             }.items()
39         },
40         "SITE": {
41             "name": site_current.name,
42             "domain": site_current.domain,
43             "web_url": f"{protocol:s}://{site_current.domain:s}",
44         },
45         "FRONTEND_CONTEXT": {
46             "context": {
47                 "csrftoken": get_token(request),
48                 "environment": getattr(settings, "ENVIRONMENT", ""),
49                 "release": getattr(settings, "RELEASE", ""),
50                 "sentry_dsn": getattr(settings, "SENTRY_DSN", ""),
51                 **WebAnalyticsContextProcessor().frontend_context_processor(request),
52             }
53         },
54         **WebAnalyticsContextProcessor().context_processor(request),
55     }
56 
57     if getattr(settings, "CDN_DOMAIN", None):
58         context["CDN_DOMAIN"] = settings.CDN_DOMAIN
59 
60     storage_url = get_storage_class()().url("any-page")
61     # Add a MEDIA_URL_PREFIX to context to prefix the media url files to have an absolute URL
62     if storage_url.startswith("//"):
63         # Eg. //my-cdn-user.cdn-provider.com/media/
64         context["MEDIA_URL_PREFIX"] = f"{request.scheme:s}:"
65     elif storage_url.startswith("/"):
66         # Eg. /media/
67         context["MEDIA_URL_PREFIX"] = f"{protocol:s}://{site_current.domain:s}"
68     else:
69         # Eg. https://my-cdn-user.cdn-provider.com/media/
70         context["MEDIA_URL_PREFIX"] = ""
71 
72     # Performance configurations
73     if urlparse(storage_url).hostname:
74         # Use dns-prefetch when using external media host, like a CDN.
75         context["MEDIA_URL_IS_EXTERNAL"] = True
76         # Optionally preconnect to the CDN
77         context["MEDIA_HOSTNAME_PRECONNECT"] = getattr(
78             settings, "MEDIA_HOSTNAME_PRECONNECT", False
79         )
80 
81     authentication_delegation = getattr(
82         settings, "RICHIE_AUTHENTICATION_DELEGATION", None
83     )
84     if authentication_delegation:
85         context["AUTHENTICATION"] = {
86             "profile_urls": json.dumps(
87                 {
88                     key: {
89                         "label": str(url["label"]),
90                         "action": str(
91                             url["href"].format(
92                                 base_url=authentication_delegation["BASE_URL"]
93                             )
94                         ),
95                     }
96                     for key, url in authentication_delegation.get(
97                         "PROFILE_URLS", {}
98                     ).items()
99                 }
100             ),
101         }
102 
103         context["FRONTEND_CONTEXT"]["context"]["authentication"] = {
104             "endpoint": authentication_delegation["BASE_URL"],
105             "backend": authentication_delegation["BACKEND"],
106         }
107 
108     if is_joanie_enabled():
109         context["FRONTEND_CONTEXT"]["context"]["joanie_backend"] = {
110             "endpoint": settings.JOANIE_BACKEND["BASE_URL"],
111         }
112 
113     if getattr(settings, "RICHIE_LMS_BACKENDS", None):
114         context["FRONTEND_CONTEXT"]["context"]["lms_backends"] = [
115             {
116                 "endpoint": lms["BASE_URL"],
117                 "backend": lms["JS_BACKEND"],
118                 "course_regexp": lms["JS_COURSE_REGEX"],
119             }
120             for lms in getattr(settings, "RICHIE_LMS_BACKENDS", [])
121         ]
122 
123     context["FRONTEND_CONTEXT"] = json.dumps(context["FRONTEND_CONTEXT"])
124 
125     if getattr(settings, "RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT", None):
126         context[
127             "RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT"
128         ] = settings.RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT
129 
130     context["RICHIE_MAX_ARCHIVED_COURSE_RUNS"] = getattr(
131         settings, "RICHIE_MAX_ARCHIVED_COURSE_RUNS", RICHIE_MAX_ARCHIVED_COURSE_RUNS
132     )
133 
134     return context
135 
136 
137 class WebAnalyticsContextProcessor:
138     """
139     Context processor to add Web Analytics tracking information to Richie CMS templates and
140     frontend.
141     """
142 
143     # pylint: disable=no-self-use
144     def frontend_context_processor(self, request: HttpRequest) -> dict:
145         """
146         Additional web analytics information for the frontend react
147         """
148         context = {}
149         if getattr(settings, "WEB_ANALYTICS", None):
150             context["web_analytics_providers"] = json.dumps(
151                 list(getattr(settings, "WEB_ANALYTICS", {}).keys())
152             )
153         return context
154 
155     def context_processor(self, request: HttpRequest) -> dict:
156         """
157         Real implementation of the context processor for the Web Analytics core app sub-module
158         """
159         context = {}
160         if hasattr(request, "current_page"):
161             # load web analytics settings to the context
162             if getattr(settings, "WEB_ANALYTICS", None):
163                 context["WEB_ANALYTICS"] = settings.WEB_ANALYTICS
164                 context["WEB_ANALYTICS_DIMENSIONS"] = self.get_dimensions(request)
165         return context
166 
167     # pylint: disable=no-self-use
168     def get_dimensions(self, request: HttpRequest) -> dict:
169         """
170         Compute the web analytics dimensions (dict) that would be added to the Django context
171         They are a dictionary like:
172         ```
173         {
174             "organizations_codes": ["UNIV_LISBON", "UNIV_PORTO"],
175             "course_code": ["COURSE_XPTO"],
176             "course_runs_titles": [
177                 "Summer edition",
178                 "Winter edition"
179             ],
180             "course_runs_resource_links": [
181                 "http://example.edx:8073/courses/course-v1:edX+DemoX+Demo_Course/info",
182                 "http://example.edx:8073/courses/course-v1:edX+DemoX+Demo_Course_2/info"
183             ],
184             "page_title": ["Introduction to Programming"],
185         }
186         ```
187 
188         Args:
189             request (HttpRequest): The Http request
190             web_analytics_context (dict): the context relevant for the web analytics sub module
191 
192         Returns:
193             dict: a dict with the dimensions, where each value is a list
194         """
195         # Warn do not change the order of evaluation of this methods, because on Google Analytics
196         # the key is a 'dimension1' instead of a more generic key like 'organizations'.
197         # That's why we are using an OrderedDict instead of a normal Python dict (don't support
198         # ordering)
199         dimensions = OrderedDict()
200 
201         page = request.current_page or None
202         language = get_language_from_request(request, check_path=True)
203 
204         organizations_codes = []
205         if page and not page.is_home:
206             organizations_codes = Organization.get_organizations_codes(page, language)
207         dimensions["organizations_codes"] = organizations_codes
208 
209         course = getattr(page, "course", None)
210         dimensions["course_code"] = [getattr(course, "code", "")]
211 
212         course_runs = course.course_runs if course else []
213         dimensions["course_runs_titles"] = [
214             course_run.title
215             for course_run in course_runs
216             if course_run is not None and course_run.safe_title is not None
217         ]
218 
219         dimensions["course_runs_resource_links"] = map(
220             lambda course_run: course_run.resource_link, course_runs
221         )
222 
223         dimensions["page_title"] = [page.get_title() if page else ""]
224         return dimensions
225
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/richie/apps/core/context_processors.py b/src/richie/apps/core/context_processors.py
--- a/src/richie/apps/core/context_processors.py
+++ b/src/richie/apps/core/context_processors.py
@@ -120,6 +120,10 @@
             for lms in getattr(settings, "RICHIE_LMS_BACKENDS", [])
         ]
 
+    context["FRONTEND_CONTEXT"]["context"]["features"] = getattr(
+        settings, "FEATURES", {}
+    )
+
     context["FRONTEND_CONTEXT"] = json.dumps(context["FRONTEND_CONTEXT"])
 
     if getattr(settings, "RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT", None):

{"golden_diff": "diff --git a/src/richie/apps/core/context_processors.py b/src/richie/apps/core/context_processors.py\n--- a/src/richie/apps/core/context_processors.py\n+++ b/src/richie/apps/core/context_processors.py\n@@ -120,6 +120,10 @@\n             for lms in getattr(settings, \"RICHIE_LMS_BACKENDS\", [])\n         ]\n \n+    context[\"FRONTEND_CONTEXT\"][\"context\"][\"features\"] = getattr(\n+        settings, \"FEATURES\", {}\n+    )\n+\n     context[\"FRONTEND_CONTEXT\"] = json.dumps(context[\"FRONTEND_CONTEXT\"])\n \n     if getattr(settings, \"RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT\", None):\n", "issue": "Frontend feature flags\n## Feature Request\r\n\r\n**Is your feature request related to a problem or unsupported use case? Please describe.**\r\nWe use feature flags configurable through environment variable to show/hide next features. Currently, those flags are only accessible by the backend. Those feature flags should be accessible by the frontend.\r\n\r\n**Describe the solution you'd like**\r\nWe should bind FEATURES settings into the Richie frontend context through the web_processors.\r\n\r\n**Do you want to work on it through a Pull Request?**\r\nYup\r\n\n", "before_files": [{"content": "\"\"\"\nTemplate context processors\n\"\"\"\nimport json\nfrom collections import OrderedDict\nfrom urllib.parse import urlparse\n\nfrom django.conf import settings\nfrom django.contrib.sites.models import Site\nfrom django.core.files.storage import get_storage_class\nfrom django.http.request import HttpRequest\nfrom django.middleware.csrf import get_token\nfrom django.utils.translation import get_language_from_request\n\nfrom richie.apps.core.templatetags.joanie import is_joanie_enabled\nfrom richie.apps.courses.defaults import RICHIE_MAX_ARCHIVED_COURSE_RUNS\nfrom richie.apps.courses.models import Organization\n\nfrom . import defaults\n\n\ndef site_metas(request: HttpRequest):\n    \"\"\"\n    Context processor to add all information required by Richie CMS templates and frontend.\n\n    If `CDN_DOMAIN` settings is defined we add it in the context. It allows\n    to load statics js on a CDN like cloudfront.\n    \"\"\"\n    site_current = Site.objects.get_current()\n    protocol = \"https\" if request.is_secure() else \"http\"\n\n    context = {\n        **{\n            f\"GLIMPSE_PAGINATION_{k.upper()}\": v\n            for k, v in {\n                **defaults.GLIMPSE_PAGINATION,\n                **getattr(settings, \"RICHIE_GLIMPSE_PAGINATION\", {}),\n            }.items()\n        },\n        \"SITE\": {\n            \"name\": site_current.name,\n            \"domain\": site_current.domain,\n            \"web_url\": f\"{protocol:s}://{site_current.domain:s}\",\n        },\n        \"FRONTEND_CONTEXT\": {\n            \"context\": {\n                \"csrftoken\": get_token(request),\n                \"environment\": getattr(settings, \"ENVIRONMENT\", \"\"),\n                \"release\": getattr(settings, \"RELEASE\", \"\"),\n                \"sentry_dsn\": getattr(settings, \"SENTRY_DSN\", \"\"),\n                **WebAnalyticsContextProcessor().frontend_context_processor(request),\n            }\n        },\n        **WebAnalyticsContextProcessor().context_processor(request),\n    }\n\n    if getattr(settings, \"CDN_DOMAIN\", None):\n        context[\"CDN_DOMAIN\"] = settings.CDN_DOMAIN\n\n    storage_url = get_storage_class()().url(\"any-page\")\n    # Add a MEDIA_URL_PREFIX to context to prefix the media url files to have an absolute URL\n    if storage_url.startswith(\"//\"):\n        # Eg. //my-cdn-user.cdn-provider.com/media/\n        context[\"MEDIA_URL_PREFIX\"] = f\"{request.scheme:s}:\"\n    elif storage_url.startswith(\"/\"):\n        # Eg. /media/\n        context[\"MEDIA_URL_PREFIX\"] = f\"{protocol:s}://{site_current.domain:s}\"\n    else:\n        # Eg. https://my-cdn-user.cdn-provider.com/media/\n        context[\"MEDIA_URL_PREFIX\"] = \"\"\n\n    # Performance configurations\n    if urlparse(storage_url).hostname:\n        # Use dns-prefetch when using external media host, like a CDN.\n        context[\"MEDIA_URL_IS_EXTERNAL\"] = True\n        # Optionally preconnect to the CDN\n        context[\"MEDIA_HOSTNAME_PRECONNECT\"] = getattr(\n            settings, \"MEDIA_HOSTNAME_PRECONNECT\", False\n        )\n\n    authentication_delegation = getattr(\n        settings, \"RICHIE_AUTHENTICATION_DELEGATION\", None\n    )\n    if authentication_delegation:\n        context[\"AUTHENTICATION\"] = {\n            \"profile_urls\": json.dumps(\n                {\n                    key: {\n                        \"label\": str(url[\"label\"]),\n                        \"action\": str(\n                            url[\"href\"].format(\n                                base_url=authentication_delegation[\"BASE_URL\"]\n                            )\n                        ),\n                    }\n                    for key, url in authentication_delegation.get(\n                        \"PROFILE_URLS\", {}\n                    ).items()\n                }\n            ),\n        }\n\n        context[\"FRONTEND_CONTEXT\"][\"context\"][\"authentication\"] = {\n            \"endpoint\": authentication_delegation[\"BASE_URL\"],\n            \"backend\": authentication_delegation[\"BACKEND\"],\n        }\n\n    if is_joanie_enabled():\n        context[\"FRONTEND_CONTEXT\"][\"context\"][\"joanie_backend\"] = {\n            \"endpoint\": settings.JOANIE_BACKEND[\"BASE_URL\"],\n        }\n\n    if getattr(settings, \"RICHIE_LMS_BACKENDS\", None):\n        context[\"FRONTEND_CONTEXT\"][\"context\"][\"lms_backends\"] = [\n            {\n                \"endpoint\": lms[\"BASE_URL\"],\n                \"backend\": lms[\"JS_BACKEND\"],\n                \"course_regexp\": lms[\"JS_COURSE_REGEX\"],\n            }\n            for lms in getattr(settings, \"RICHIE_LMS_BACKENDS\", [])\n        ]\n\n    context[\"FRONTEND_CONTEXT\"] = json.dumps(context[\"FRONTEND_CONTEXT\"])\n\n    if getattr(settings, \"RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT\", None):\n        context[\n            \"RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT\"\n        ] = settings.RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT\n\n    context[\"RICHIE_MAX_ARCHIVED_COURSE_RUNS\"] = getattr(\n        settings, \"RICHIE_MAX_ARCHIVED_COURSE_RUNS\", RICHIE_MAX_ARCHIVED_COURSE_RUNS\n    )\n\n    return context\n\n\nclass WebAnalyticsContextProcessor:\n    \"\"\"\n    Context processor to add Web Analytics tracking information to Richie CMS templates and\n    frontend.\n    \"\"\"\n\n    # pylint: disable=no-self-use\n    def frontend_context_processor(self, request: HttpRequest) -> dict:\n        \"\"\"\n        Additional web analytics information for the frontend react\n        \"\"\"\n        context = {}\n        if getattr(settings, \"WEB_ANALYTICS\", None):\n            context[\"web_analytics_providers\"] = json.dumps(\n                list(getattr(settings, \"WEB_ANALYTICS\", {}).keys())\n            )\n        return context\n\n    def context_processor(self, request: HttpRequest) -> dict:\n        \"\"\"\n        Real implementation of the context processor for the Web Analytics core app sub-module\n        \"\"\"\n        context = {}\n        if hasattr(request, \"current_page\"):\n            # load web analytics settings to the context\n            if getattr(settings, \"WEB_ANALYTICS\", None):\n                context[\"WEB_ANALYTICS\"] = settings.WEB_ANALYTICS\n                context[\"WEB_ANALYTICS_DIMENSIONS\"] = self.get_dimensions(request)\n        return context\n\n    # pylint: disable=no-self-use\n    def get_dimensions(self, request: HttpRequest) -> dict:\n        \"\"\"\n        Compute the web analytics dimensions (dict) that would be added to the Django context\n        They are a dictionary like:\n        ```\n        {\n            \"organizations_codes\": [\"UNIV_LISBON\", \"UNIV_PORTO\"],\n            \"course_code\": [\"COURSE_XPTO\"],\n            \"course_runs_titles\": [\n                \"Summer edition\",\n                \"Winter edition\"\n            ],\n            \"course_runs_resource_links\": [\n                \"http://example.edx:8073/courses/course-v1:edX+DemoX+Demo_Course/info\",\n                \"http://example.edx:8073/courses/course-v1:edX+DemoX+Demo_Course_2/info\"\n            ],\n            \"page_title\": [\"Introduction to Programming\"],\n        }\n        ```\n\n        Args:\n            request (HttpRequest): The Http request\n            web_analytics_context (dict): the context relevant for the web analytics sub module\n\n        Returns:\n            dict: a dict with the dimensions, where each value is a list\n        \"\"\"\n        # Warn do not change the order of evaluation of this methods, because on Google Analytics\n        # the key is a 'dimension1' instead of a more generic key like 'organizations'.\n        # That's why we are using an OrderedDict instead of a normal Python dict (don't support\n        # ordering)\n        dimensions = OrderedDict()\n\n        page = request.current_page or None\n        language = get_language_from_request(request, check_path=True)\n\n        organizations_codes = []\n        if page and not page.is_home:\n            organizations_codes = Organization.get_organizations_codes(page, language)\n        dimensions[\"organizations_codes\"] = organizations_codes\n\n        course = getattr(page, \"course\", None)\n        dimensions[\"course_code\"] = [getattr(course, \"code\", \"\")]\n\n        course_runs = course.course_runs if course else []\n        dimensions[\"course_runs_titles\"] = [\n            course_run.title\n            for course_run in course_runs\n            if course_run is not None and course_run.safe_title is not None\n        ]\n\n        dimensions[\"course_runs_resource_links\"] = map(\n            lambda course_run: course_run.resource_link, course_runs\n        )\n\n        dimensions[\"page_title\"] = [page.get_title() if page else \"\"]\n        return dimensions\n", "path": "src/richie/apps/core/context_processors.py"}], "after_files": [{"content": "\"\"\"\nTemplate context processors\n\"\"\"\nimport json\nfrom collections import OrderedDict\nfrom urllib.parse import urlparse\n\nfrom django.conf import settings\nfrom django.contrib.sites.models import Site\nfrom django.core.files.storage import get_storage_class\nfrom django.http.request import HttpRequest\nfrom django.middleware.csrf import get_token\nfrom django.utils.translation import get_language_from_request\n\nfrom richie.apps.core.templatetags.joanie import is_joanie_enabled\nfrom richie.apps.courses.defaults import RICHIE_MAX_ARCHIVED_COURSE_RUNS\nfrom richie.apps.courses.models import Organization\n\nfrom . import defaults\n\n\ndef site_metas(request: HttpRequest):\n    \"\"\"\n    Context processor to add all information required by Richie CMS templates and frontend.\n\n    If `CDN_DOMAIN` settings is defined we add it in the context. It allows\n    to load statics js on a CDN like cloudfront.\n    \"\"\"\n    site_current = Site.objects.get_current()\n    protocol = \"https\" if request.is_secure() else \"http\"\n\n    context = {\n        **{\n            f\"GLIMPSE_PAGINATION_{k.upper()}\": v\n            for k, v in {\n                **defaults.GLIMPSE_PAGINATION,\n                **getattr(settings, \"RICHIE_GLIMPSE_PAGINATION\", {}),\n            }.items()\n        },\n        \"SITE\": {\n            \"name\": site_current.name,\n            \"domain\": site_current.domain,\n            \"web_url\": f\"{protocol:s}://{site_current.domain:s}\",\n        },\n        \"FRONTEND_CONTEXT\": {\n            \"context\": {\n                \"csrftoken\": get_token(request),\n                \"environment\": getattr(settings, \"ENVIRONMENT\", \"\"),\n                \"release\": getattr(settings, \"RELEASE\", \"\"),\n                \"sentry_dsn\": getattr(settings, \"SENTRY_DSN\", \"\"),\n                **WebAnalyticsContextProcessor().frontend_context_processor(request),\n            }\n        },\n        **WebAnalyticsContextProcessor().context_processor(request),\n    }\n\n    if getattr(settings, \"CDN_DOMAIN\", None):\n        context[\"CDN_DOMAIN\"] = settings.CDN_DOMAIN\n\n    storage_url = get_storage_class()().url(\"any-page\")\n    # Add a MEDIA_URL_PREFIX to context to prefix the media url files to have an absolute URL\n    if storage_url.startswith(\"//\"):\n        # Eg. //my-cdn-user.cdn-provider.com/media/\n        context[\"MEDIA_URL_PREFIX\"] = f\"{request.scheme:s}:\"\n    elif storage_url.startswith(\"/\"):\n        # Eg. /media/\n        context[\"MEDIA_URL_PREFIX\"] = f\"{protocol:s}://{site_current.domain:s}\"\n    else:\n        # Eg. https://my-cdn-user.cdn-provider.com/media/\n        context[\"MEDIA_URL_PREFIX\"] = \"\"\n\n    # Performance configurations\n    if urlparse(storage_url).hostname:\n        # Use dns-prefetch when using external media host, like a CDN.\n        context[\"MEDIA_URL_IS_EXTERNAL\"] = True\n        # Optionally preconnect to the CDN\n        context[\"MEDIA_HOSTNAME_PRECONNECT\"] = getattr(\n            settings, \"MEDIA_HOSTNAME_PRECONNECT\", False\n        )\n\n    authentication_delegation = getattr(\n        settings, \"RICHIE_AUTHENTICATION_DELEGATION\", None\n    )\n    if authentication_delegation:\n        context[\"AUTHENTICATION\"] = {\n            \"profile_urls\": json.dumps(\n                {\n                    key: {\n                        \"label\": str(url[\"label\"]),\n                        \"action\": str(\n                            url[\"href\"].format(\n                                base_url=authentication_delegation[\"BASE_URL\"]\n                            )\n                        ),\n                    }\n                    for key, url in authentication_delegation.get(\n                        \"PROFILE_URLS\", {}\n                    ).items()\n                }\n            ),\n        }\n\n        context[\"FRONTEND_CONTEXT\"][\"context\"][\"authentication\"] = {\n            \"endpoint\": authentication_delegation[\"BASE_URL\"],\n            \"backend\": authentication_delegation[\"BACKEND\"],\n        }\n\n    if is_joanie_enabled():\n        context[\"FRONTEND_CONTEXT\"][\"context\"][\"joanie_backend\"] = {\n            \"endpoint\": settings.JOANIE_BACKEND[\"BASE_URL\"],\n        }\n\n    if getattr(settings, \"RICHIE_LMS_BACKENDS\", None):\n        context[\"FRONTEND_CONTEXT\"][\"context\"][\"lms_backends\"] = [\n            {\n                \"endpoint\": lms[\"BASE_URL\"],\n                \"backend\": lms[\"JS_BACKEND\"],\n                \"course_regexp\": lms[\"JS_COURSE_REGEX\"],\n            }\n            for lms in getattr(settings, \"RICHIE_LMS_BACKENDS\", [])\n        ]\n\n    context[\"FRONTEND_CONTEXT\"][\"context\"][\"features\"] = getattr(\n        settings, \"FEATURES\", {}\n    )\n\n    context[\"FRONTEND_CONTEXT\"] = json.dumps(context[\"FRONTEND_CONTEXT\"])\n\n    if getattr(settings, \"RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT\", None):\n        context[\n            \"RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT\"\n        ] = settings.RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT\n\n    context[\"RICHIE_MAX_ARCHIVED_COURSE_RUNS\"] = getattr(\n        settings, \"RICHIE_MAX_ARCHIVED_COURSE_RUNS\", RICHIE_MAX_ARCHIVED_COURSE_RUNS\n    )\n\n    return context\n\n\nclass WebAnalyticsContextProcessor:\n    \"\"\"\n    Context processor to add Web Analytics tracking information to Richie CMS templates and\n    frontend.\n    \"\"\"\n\n    # pylint: disable=no-self-use\n    def frontend_context_processor(self, request: HttpRequest) -> dict:\n        \"\"\"\n        Additional web analytics information for the frontend react\n        \"\"\"\n        context = {}\n        if getattr(settings, \"WEB_ANALYTICS\", None):\n            context[\"web_analytics_providers\"] = json.dumps(\n                list(getattr(settings, \"WEB_ANALYTICS\", {}).keys())\n            )\n        return context\n\n    def context_processor(self, request: HttpRequest) -> dict:\n        \"\"\"\n        Real implementation of the context processor for the Web Analytics core app sub-module\n        \"\"\"\n        context = {}\n        if hasattr(request, \"current_page\"):\n            # load web analytics settings to the context\n            if getattr(settings, \"WEB_ANALYTICS\", None):\n                context[\"WEB_ANALYTICS\"] = settings.WEB_ANALYTICS\n                context[\"WEB_ANALYTICS_DIMENSIONS\"] = self.get_dimensions(request)\n        return context\n\n    # pylint: disable=no-self-use\n    def get_dimensions(self, request: HttpRequest) -> dict:\n        \"\"\"\n        Compute the web analytics dimensions (dict) that would be added to the Django context\n        They are a dictionary like:\n        ```\n        {\n            \"organizations_codes\": [\"UNIV_LISBON\", \"UNIV_PORTO\"],\n            \"course_code\": [\"COURSE_XPTO\"],\n            \"course_runs_titles\": [\n                \"Summer edition\",\n                \"Winter edition\"\n            ],\n            \"course_runs_resource_links\": [\n                \"http://example.edx:8073/courses/course-v1:edX+DemoX+Demo_Course/info\",\n                \"http://example.edx:8073/courses/course-v1:edX+DemoX+Demo_Course_2/info\"\n            ],\n            \"page_title\": [\"Introduction to Programming\"],\n        }\n        ```\n\n        Args:\n            request (HttpRequest): The Http request\n            web_analytics_context (dict): the context relevant for the web analytics sub module\n\n        Returns:\n            dict: a dict with the dimensions, where each value is a list\n        \"\"\"\n        # Warn do not change the order of evaluation of this methods, because on Google Analytics\n        # the key is a 'dimension1' instead of a more generic key like 'organizations'.\n        # That's why we are using an OrderedDict instead of a normal Python dict (don't support\n        # ordering)\n        dimensions = OrderedDict()\n\n        page = request.current_page or None\n        language = get_language_from_request(request, check_path=True)\n\n        organizations_codes = []\n        if page and not page.is_home:\n            organizations_codes = Organization.get_organizations_codes(page, language)\n        dimensions[\"organizations_codes\"] = organizations_codes\n\n        course = getattr(page, \"course\", None)\n        dimensions[\"course_code\"] = [getattr(course, \"code\", \"\")]\n\n        course_runs = course.course_runs if course else []\n        dimensions[\"course_runs_titles\"] = [\n            course_run.title\n            for course_run in course_runs\n            if course_run is not None and course_run.safe_title is not None\n        ]\n\n        dimensions[\"course_runs_resource_links\"] = map(\n            lambda course_run: course_run.resource_link, course_runs\n        )\n\n        dimensions[\"page_title\"] = [page.get_title() if page else \"\"]\n        return dimensions\n", "path": "src/richie/apps/core/context_processors.py"}]}