Repository: openfun/richie

Issue:
Main organization in elasticsearch
## Bug Report
Follow up of
https://github.com/openfun/richie/commit/22c7a27bc8f56e16774233c6a4847401de82bd7d

The main organization in elasticsearch is still using the first org in the index among the course's organizations and not the first org plugin in the course. I did not notice this at the time.

**Steps to Reproduce**

1. Have multiple organization
2. Have a course with multiple organization
3. Move the organization around (See that the main org has changed for the course and the course plugin) 
4. Reindex elasticsearch (See that it uses the first org in index among the course's org)

**Environment**
- Richie version: 1.16.1

**Additional context/Screenshots**
The workaround remains the same: have the index in a special order specific to your richie and it's courses.



Discussion:

Resolving patch:
diff --git a/src/richie/apps/search/indexers/courses.py b/src/richie/apps/search/indexers/courses.py
--- a/src/richie/apps/search/indexers/courses.py
+++ b/src/richie/apps/search/indexers/courses.py
@@ -77,8 +77,8 @@ class CoursesIndexer:
         "categories",
         "cover_image",
         "icon",
+        "organization_highlighted",
         "organizations",
-        "organizations_names",
         "title.*",
     ]
     form = CourseSearchForm
@@ -440,6 +440,10 @@ def get_es_document_for_course(cls, course, index=None, action="index"):
             .only("extended_object__node")
             .distinct()
         )
+        organization_main = course.get_main_organization()
+        organization_highlighted = (
+            organizations.get(id=organization_main.id) if organization_main else None
+        )
 
         # Prepare persons, making sure we get title information for persons
         # in the same query
@@ -506,6 +510,14 @@ def get_es_document_for_course(cls, course, index=None, action="index"):
             "description": {l: " ".join(st) for l, st in descriptions.items()},
             "icon": icon_images,
             "is_new": len(course_runs) == 1,
+            # Pick the highlighted organization from the organizations QuerySet to benefit from
+            # the prefetch of related title sets
+            "organization_highlighted": {
+                title.language: title.title
+                for title in organization_highlighted.extended_object.published_titles
+            }
+            if organization_highlighted
+            else None,
             "organizations": [
                 ES_INDICES.organizations.get_es_id(o.extended_object)
                 for o in organizations
@@ -575,17 +587,16 @@ def format_es_object_for_api(es_course, language=None):
         except KeyError:
             state["date_time"] = None
 
-        organizations_names = get_best_field_language(
-            source["organizations_names"], language
-        )
         return {
             "id": es_course["_id"],
             "absolute_url": get_best_field_language(source["absolute_url"], language),
             "categories": source["categories"],
             "cover_image": get_best_field_language(source["cover_image"], language),
             "icon": get_best_field_language(source["icon"], language),
-            "organization_highlighted": organizations_names[0]
-            if organizations_names
+            "organization_highlighted": get_best_field_language(
+                source["organization_highlighted"], language
+            )
+            if source.get("organization_highlighted", None)
             else None,
             "organizations": source["organizations"],
             "state": CourseState(**state),


Test patch:
diff --git a/tests/apps/search/test_indexers_courses.py b/tests/apps/search/test_indexers_courses.py
--- a/tests/apps/search/test_indexers_courses.py
+++ b/tests/apps/search/test_indexers_courses.py
@@ -242,6 +242,10 @@ def test_indexers_courses_get_es_documents_from_models(self, _mock_picture):
                 },
             },
             "is_new": False,
+            "organization_highlighted": {
+                "en": "english main organization title",
+                "fr": "titre organisation principale français",
+            },
             "organizations": ["L-0004", "L-0006"],
             "organizations_names": {
                 "en": [
@@ -269,6 +273,49 @@ def test_indexers_courses_get_es_documents_from_models(self, _mock_picture):
         self.assertEqual(len(indexed_courses), 1)
         self.assertEqual(indexed_courses[0], expected_course)
 
+    def test_indexers_courses_get_es_document_no_organization(self):
+        """
+        Courses with no linked organizations should get indexed without raising exceptions.
+        """
+        course = CourseFactory(
+            page_title="Enhanced incremental circuit", should_publish=True
+        )
+        indexed_courses = list(
+            CoursesIndexer.get_es_documents(index="some_index", action="some_action")
+        )
+        self.assertEqual(
+            indexed_courses,
+            [
+                {
+                    "_id": str(course.extended_object.publisher_public_id),
+                    "_index": "some_index",
+                    "_op_type": "some_action",
+                    "_type": "course",
+                    "absolute_url": {"en": "/en/enhanced-incremental-circuit/"},
+                    "categories": [],
+                    "categories_names": {},
+                    "complete": {
+                        "en": [
+                            "Enhanced incremental circuit",
+                            "incremental circuit",
+                            "circuit",
+                        ]
+                    },
+                    "course_runs": [],
+                    "cover_image": {},
+                    "description": {},
+                    "icon": {},
+                    "is_new": False,
+                    "organization_highlighted": None,
+                    "organizations": [],
+                    "organizations_names": {},
+                    "persons": [],
+                    "persons_names": {},
+                    "title": {"en": "Enhanced incremental circuit"},
+                }
+            ],
+        )
+
     def test_indexers_courses_get_es_documents_no_start(self):
         """
         Course runs with no start date should not get indexed.
@@ -438,6 +485,7 @@ def test_indexers_courses_format_es_object_for_api(self):
                 "categories": [43, 86],
                 "cover_image": {"en": "cover_image.jpg"},
                 "icon": {"en": "icon.jpg"},
+                "organization_highlighted": {"en": "Org 84"},
                 "organizations": [42, 84],
                 "organizations_names": {"en": ["Org 42", "Org 84"]},
                 "title": {"en": "Duis eu arcu erat"},
@@ -456,7 +504,7 @@ def test_indexers_courses_format_es_object_for_api(self):
                 "categories": [43, 86],
                 "cover_image": "cover_image.jpg",
                 "icon": "icon.jpg",
-                "organization_highlighted": "Org 42",
+                "organization_highlighted": "Org 84",
                 "organizations": [42, 84],
                 "title": "Duis eu arcu erat",
                 "state": CourseState(
@@ -477,6 +525,7 @@ def test_indexers_courses_format_es_object_for_api_no_organization(self):
                 "categories": [43, 86],
                 "cover_image": {"en": "cover_image.jpg"},
                 "icon": {"en": "icon.jpg"},
+                "organization_highlighted": None,
                 "organizations": [],
                 "organizations_names": {},
                 "title": {"en": "Duis eu arcu erat"},
@@ -515,6 +564,7 @@ def test_indexers_courses_format_es_object_for_api_no_icon(self):
                 "categories": [43, 86],
                 "cover_image": {"en": "cover_image.jpg"},
                 "icon": {},
+                "organization_highlighted": {"en": "Org 84"},
                 "organizations": [42, 84],
                 "organizations_names": {"en": ["Org 42", "Org 84"]},
                 "title": {"en": "Duis eu arcu erat"},
@@ -533,7 +583,7 @@ def test_indexers_courses_format_es_object_for_api_no_icon(self):
                 "categories": [43, 86],
                 "cover_image": "cover_image.jpg",
                 "icon": None,
-                "organization_highlighted": "Org 42",
+                "organization_highlighted": "Org 84",
                 "organizations": [42, 84],
                 "title": "Duis eu arcu erat",
                 "state": CourseState(
@@ -553,6 +603,7 @@ def test_indexers_courses_format_es_object_for_api_no_cover(self):
                 "categories": [43, 86],
                 "cover_image": {},
                 "icon": {"en": "icon.jpg"},
+                "organization_highlighted": {"en": "Org 42"},
                 "organizations": [42, 84],
                 "organizations_names": {"en": ["Org 42", "Org 84"]},
                 "title": {"en": "Duis eu arcu erat"},
diff --git a/tests/apps/search/test_query_courses.py b/tests/apps/search/test_query_courses.py
--- a/tests/apps/search/test_query_courses.py
+++ b/tests/apps/search/test_query_courses.py
@@ -30,6 +30,7 @@
             )
         },
         "is_new": True,
+        "organization_highlighted": {"en": "Org 311"},
         "organizations": ["P-00030001", "P-00030004", "L-000300010001"],
         "organizations_names": {"en": ["Org 31", "Org 34", "Org 311"]},
         "persons": ["2"],
@@ -48,6 +49,7 @@
             )
         },
         "is_new": True,
+        "organization_highlighted": {"en": "Org 33"},
         "organizations": ["P-00030001", "P-00030003", "L-000300010002"],
         "organizations_names": {"en": ["Org 31", "Org 33", "Org 312"]},
         "persons": [],
@@ -66,6 +68,7 @@
             )
         },
         "is_new": False,
+        "organization_highlighted": {"en": "Org 321"},
         "organizations": ["P-00030002", "P-00030003", "L-000300020001"],
         "organizations_names": {"en": ["Org 32", "Org 33", "Org 321"]},
         "persons": [],
@@ -83,6 +86,7 @@
             )
         },
         "is_new": False,
+        "organization_highlighted": {"en": "Org 34"},
         "organizations": ["P-00030002", "P-00030004", "L-000300020002"],
         "organizations_names": {"en": ["Org 32", "Org 34", "Org 322"]},
         "persons": ["2"],
@@ -337,7 +341,7 @@ def test_query_courses_match_all(self, *_):
                         ],
                         "cover_image": "cover_image.jpg",
                         "icon": "icon.jpg",
-                        "organization_highlighted": "Org 31",
+                        "organization_highlighted": "Org 311",
                         "organizations": ["P-00030001", "P-00030004", "L-000300010001"],
                         "state": {
                             "priority": 0,
@@ -360,7 +364,7 @@ def test_query_courses_match_all(self, *_):
                         ],
                         "cover_image": "cover_image.jpg",
                         "icon": "icon.jpg",
-                        "organization_highlighted": "Org 32",
+                        "organization_highlighted": "Org 321",
                         "organizations": ["P-00030002", "P-00030003", "L-000300020001"],
                         "state": {
                             "priority": 0,
@@ -383,7 +387,7 @@ def test_query_courses_match_all(self, *_):
                         ],
                         "cover_image": "cover_image.jpg",
                         "icon": "icon.jpg",
-                        "organization_highlighted": "Org 32",
+                        "organization_highlighted": "Org 34",
                         "organizations": ["P-00030002", "P-00030004", "L-000300020002"],
                         "state": {
                             "priority": 1,
@@ -406,7 +410,7 @@ def test_query_courses_match_all(self, *_):
                         ],
                         "cover_image": "cover_image.jpg",
                         "icon": "icon.jpg",
-                        "organization_highlighted": "Org 31",
+                        "organization_highlighted": "Org 33",
                         "organizations": ["P-00030001", "P-00030003", "L-000300010002"],
                         "state": {
                             "priority": 4,
diff --git a/tests/apps/search/test_viewsets_courses.py b/tests/apps/search/test_viewsets_courses.py
--- a/tests/apps/search/test_viewsets_courses.py
+++ b/tests/apps/search/test_viewsets_courses.py
@@ -294,8 +294,8 @@ def mock_search_implementation(index, **_):
                 "categories",
                 "cover_image",
                 "icon",
+                "organization_highlighted",
                 "organizations",
-                "organizations_names",
                 "title.*",
             ],
             body={