Repository: digitalfabrik/integreat-cms
Pull request #3469: Deliver all contacts of a poi to API
Author: login: JoeyStk
Labels: name: prio: high
description: Needs to be resolved ASAP.
name: blocks-app
description: Issues that block the frontend
Created: 2025-02-25T16:43:48Z | Merged: 2025-03-03T12:39:13Z

### Short description
<!-- Describe this PR in one or two sentences. -->
Currently it's not possible to link more than one contact to a POI and if the linked contact has an area of responsibility, it's also not delivered to the API. This PR attempts to fix both.

### Proposed changes
<!-- Describe this PR in more detail. -->

- Iterate over the contacts and extract all websites, phone_numbers and emails and return them as a list
- Remove the query that the linked contact is not allowed to have an area of responsibility


### Side effects
<!-- List all related components that have not been explicitly changed but may be affected by this PR -->

- This PR changes the API, but locally I didn't run into any issues
- To test the API locally you need to adjust the test data and change `location_on_map` to `True`


### Resolved issues
<!-- List all issues which should be closed when this PR is merged. -->

Fixes: #3462
Fixes: #3460


__________________________________________________
<!-- Keep this link for the potential reviewer -->
[Pull Request Review Guidelines](https://digitalfabrik.github.io/integreat-cms/pull-request-review-guide.html)



Diff:
diff --git a/docs/src/api-docs.rst b/docs/src/api-docs.rst
index e829bce988..c59a5c09a1 100644
--- a/docs/src/api-docs.rst
+++ b/docs/src/api-docs.rst
@@ -337,6 +337,15 @@ RESPONSE
          "website": String | null,       // The website for this location if primary_contact exists
          "email": String | null,         // The email for this location if primary_contact exists
          "phone_number": String | null,  // The phone number for this location if primary_contact exists
+         "contacts": [                   // The contact data linked to this location
+            {
+                "area_of_responsibility": String | null   // The area of responsibility, e.g. advisory, of a contact
+                "name": String,              // The name of the contact
+                "email": String,             // An email address of the contact
+                "phone_number": String,      // A phone number of the contact
+                "website": String,           // The website of the contact
+            },
+         ],
          "category": {                   // The category of this location
             "id": Number,                // The id of the category
             "name": String,              // The translated name of the category
diff --git a/integreat_cms/api/v3/locations.py b/integreat_cms/api/v3/locations.py
index eb6019f1bd..478fdb9db9 100644
--- a/integreat_cms/api/v3/locations.py
+++ b/integreat_cms/api/v3/locations.py
@@ -71,9 +71,26 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+
+    # Note(johannes): Remove the primary_contact and the according three fields (phone_number, website, and email) once
+    # https://github.com/digitalfabrik/integreat-app/issues/3121 is resolved.
+    primary_contact = contacts.filter(area_of_responsibility="").first()
+
+    contacts = contacts.filter(archived=False)
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "area_of_responsibility": contact.area_of_responsibility
+                if contact.area_of_responsibility
+                else None,
+                "name": contact.name,
+                "email": contact.email,
+                "phone_number": contact.phone_number,
+                "website": contact.website,
+            }
+        )
 
     # Only return opening hours if they differ from the default value and the location is not temporarily closed
     opening_hours = None
@@ -95,6 +112,7 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
         "website": primary_contact.website if primary_contact else None,
         "email": primary_contact.email if primary_contact else None,
         "phone_number": primary_contact.phone_number if primary_contact else None,
+        "contacts": contact_data,
         "category": transform_location_category(
             poi.category,
             poi_translation.language.slug,
diff --git a/integreat_cms/cms/templates/ajax_contact_form/_contact_form_widget.html b/integreat_cms/cms/templates/ajax_contact_form/_contact_form_widget.html
index 46dcc1a5df..763c890373 100644
--- a/integreat_cms/cms/templates/ajax_contact_form/_contact_form_widget.html
+++ b/integreat_cms/cms/templates/ajax_contact_form/_contact_form_widget.html
@@ -4,14 +4,6 @@
     <label for="{{ contact_form.title.id_for_label }}">
         {{ contact_form.area_of_responsibility.label }}
     </label>
-    <div class="bg-yellow-100 border-l-4 border-yellow-500 text-orange-700 px-4 py-3 mb-5">
-        <p>
-            {% translate "Currently, contacts with an area of responsibility are not displayed in the location. You can find more information in the" %}
-            <a class="text-blue-500 underline"
-               href="https://wiki.integreat-app.de/kontakte-modul"
-               target="_blank">Wiki</a>.
-        </p>
-    </div>
     {% render_field contact_form.area_of_responsibility|append_attr:"form:ajax_contact_form" %}
     <label for="{{ contact_form.name.id_for_label }}">
         {{ contact_form.name.label }}
diff --git a/integreat_cms/cms/templates/contacts/contact_form.html b/integreat_cms/cms/templates/contacts/contact_form.html
index d54cffc0b6..1677b95258 100644
--- a/integreat_cms/cms/templates/contacts/contact_form.html
+++ b/integreat_cms/cms/templates/contacts/contact_form.html
@@ -54,14 +54,6 @@ <h3 class="heading font-bold text-black">
                         <label for="{{ contact_form.area_of_responsibility.id_for_label }}">
                             {{ contact_form.area_of_responsibility.label }}
                         </label>
-                        <div class="bg-yellow-100 border-l-4 border-yellow-500 text-orange-700 px-4 py-3 mb-5">
-                            <p>
-                                {% translate "Currently, contacts with an area of responsibility are not displayed in the location. You can find more information in the" %}
-                                <a class="text-blue-500 underline"
-                                   href="https://wiki.integreat-app.de/kontakte-modul"
-                                   target="_blank">Wiki</a>.
-                            </p>
-                        </div>
                         {% render_field contact_form.area_of_responsibility|add_error_class:"border-red-500" %}
                         <label for="{{ contact_form.name.id_for_label }}">
                             {{ contact_form.name.label }}
diff --git a/integreat_cms/locale/de/LC_MESSAGES/django.po b/integreat_cms/locale/de/LC_MESSAGES/django.po
index 526d9b2c6b..c594622d03 100644
--- a/integreat_cms/locale/de/LC_MESSAGES/django.po
+++ b/integreat_cms/locale/de/LC_MESSAGES/django.po
@@ -4969,14 +4969,6 @@ msgstr "Automatisch den Titel des verlinkten Inhalts als Linktext verwenden"
 msgid "Media Library..."
 msgstr "Medienbibliothek..."
 
-#: cms/templates/ajax_contact_form/_contact_form_widget.html
-#: cms/templates/contacts/contact_form.html
-msgid ""
-"Currently, contacts with an area of responsibility are not displayed in the "
-"location. You can find more information in the"
-msgstr ""
-"Aktuell werden Kontakte mit Zuständigkeitsbereich nicht im Ort angezeigt. Mehr Informationen finden Sie im"
-
 #: cms/templates/ajax_poi_form/_poi_address_container.html
 #: cms/templates/pois/poi_form_sidebar/position_box.html
 msgid "Address"
@@ -11453,6 +11445,13 @@ msgstr ""
 "Diese Seite konnte nicht importiert werden, da sie zu einer anderen Region "
 "gehört ({})."
 
+#~ msgid ""
+#~ "Currently, contacts with an area of responsibility are not displayed in "
+#~ "the location. You can find more information in the"
+#~ msgstr ""
+#~ "Aktuell werden Kontakte mit Zuständigkeitsbereich nicht im Ort angezeigt. "
+#~ "Mehr Informationen finden Sie im"
+
 #~ msgid "Administrative division"
 #~ msgstr "Verwaltungseinheit"
 
diff --git a/integreat_cms/release_notes/current/unreleased/3460.yml b/integreat_cms/release_notes/current/unreleased/3460.yml
new file mode 100644
index 0000000000..d4c798452a
--- /dev/null
+++ b/integreat_cms/release_notes/current/unreleased/3460.yml
@@ -0,0 +1,2 @@
+en: Change that archived contacts are not visible in app anymore
+de: Ändere, dass archivierte Kontakte nicht mehr in der App sichtbar sind
diff --git a/integreat_cms/release_notes/current/unreleased/3462.yml b/integreat_cms/release_notes/current/unreleased/3462.yml
new file mode 100644
index 0000000000..5b7e767abc
--- /dev/null
+++ b/integreat_cms/release_notes/current/unreleased/3462.yml
@@ -0,0 +1,2 @@
+en: Allow for contacts to have an area of responsibility
+de: Ermöglichen Sie es den Ansprechpartnern, einen Zuständigkeitsbereich zu haben
diff --git a/tests/api/api_config.py b/tests/api/api_config.py
index d3d63b2971..0eb3097050 100644
--- a/tests/api/api_config.py
+++ b/tests/api/api_config.py
@@ -72,21 +72,21 @@
         "/augsburg/de/wp-json/extensions/v3/locations/",
         "tests/api/expected-outputs/augsburg_de_locations.json",
         200,
-        6,
+        7,
     ),
     (
         "/api/v3/augsburg/en/locations/",
         "/augsburg/en/wp-json/extensions/v3/locations/",
         "tests/api/expected-outputs/augsburg_en_locations.json",
         200,
-        6,
+        7,
     ),
     (
         "/api/v3/augsburg/ar/locations/",
         "/augsburg/ar/wp-json/extensions/v3/locations/",
         "tests/api/expected-outputs/augsburg_ar_locations.json",
         200,
-        6,
+        7,
     ),
     (
         "/api/v3/augsburg/non-existing/locations/",
@@ -233,14 +233,14 @@
         "/nurnberg/de/wp-json/extensions/v3/locations/",
         "tests/api/expected-outputs/nurnberg_de_locations.json",
         200,
-        6,
+        7,
     ),
     (
         "/api/v3/nurnberg/en/locations/",
         "/nurnberg/en/wp-json/extensions/v3/locations/",
         "tests/api/expected-outputs/nurnberg_en_locations.json",
         200,
-        6,
+        7,
     ),
     (
         "/api/v3/nurnberg/ar/locations/",
diff --git a/tests/api/expected-outputs/augsburg_ar_locations.json b/tests/api/expected-outputs/augsburg_ar_locations.json
index 93539999a8..50350bc5d6 100644
--- a/tests/api/expected-outputs/augsburg_ar_locations.json
+++ b/tests/api/expected-outputs/augsburg_ar_locations.json
@@ -8,6 +8,7 @@
     "last_updated": "2023-01-22T13:52:56.726+01:00",
     "excerpt": "موقع الاختبار",
     "meta_description": "Meta description of the test location",
+    "contacts": [],
     "content": "<p>موقع الاختبار</p>",
     "appointment_url": null,
     "available_languages": {
diff --git a/tests/api/expected-outputs/augsburg_de_locations.json b/tests/api/expected-outputs/augsburg_de_locations.json
index a650ebe1d0..e06e7d5e90 100644
--- a/tests/api/expected-outputs/augsburg_de_locations.json
+++ b/tests/api/expected-outputs/augsburg_de_locations.json
@@ -8,6 +8,7 @@
     "last_updated": "2020-01-22T13:52:13.616+01:00",
     "excerpt": "Beschreibung des Test-Ortes",
     "meta_description": "Meta-Beschreibung des Test-Ortes",
+    "contacts": [],
     "content": "<p>Beschreibung des Test-Ortes</p>",
     "appointment_url": null,
     "available_languages": {
diff --git a/tests/api/expected-outputs/augsburg_en_locations.json b/tests/api/expected-outputs/augsburg_en_locations.json
index 59e207bb87..0d478816cf 100644
--- a/tests/api/expected-outputs/augsburg_en_locations.json
+++ b/tests/api/expected-outputs/augsburg_en_locations.json
@@ -8,6 +8,7 @@
     "last_updated": "2020-01-22T13:52:56.726+01:00",
     "excerpt": "Content of the test location",
     "meta_description": "Meta description of the test location",
+    "contacts": [],
     "content": "<p>Content of the test location</p>",
     "appointment_url": null,
     "available_languages": {
diff --git a/tests/api/expected-outputs/nurnberg_de_locations.json b/tests/api/expected-outputs/nurnberg_de_locations.json
index 2a9cc54b01..30aae17c81 100644
--- a/tests/api/expected-outputs/nurnberg_de_locations.json
+++ b/tests/api/expected-outputs/nurnberg_de_locations.json
@@ -8,6 +8,7 @@
     "last_updated": "2020-01-22T13:52:13.616+01:00",
     "excerpt": "Beschreibung des Test-Ortes",
     "meta_description": "Meta-Beschreibung des Test-Ortes",
+    "contacts": [],
     "content": "<p>Beschreibung des Test-Ortes</p>",
     "appointment_url": null,
     "available_languages": {
diff --git a/tests/api/expected-outputs/nurnberg_en_locations.json b/tests/api/expected-outputs/nurnberg_en_locations.json
index 50b719576d..3d3587b745 100644
--- a/tests/api/expected-outputs/nurnberg_en_locations.json
+++ b/tests/api/expected-outputs/nurnberg_en_locations.json
@@ -8,6 +8,7 @@
     "last_updated": "2020-01-22T13:52:56.726+01:00",
     "excerpt": "Content of the test location",
     "meta_description": "Meta description of the test location",
+    "contacts": [],
     "content": "<p>Content of the test location</p>",
     "appointment_url": null,
     "available_languages": {


Reviews:
user: login: charludo
body: Splendid, thank you for tackling this! 🎉 

Edit: oh, please also amend the API docs
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T13:10:16Z
user: login: JoeyStk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T13:22:19Z
user: login: JoeyStk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T13:22:42Z
user: login: charludo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T13:28:15Z
user: login: JoeyStk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T16:48:47Z
user: login: charludo
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T08:30:18Z
user: login: charludo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T08:30:35Z
user: login: charludo
body: Awesome, thank you! I only have two inconsequential nits, so I will already approve! 🚀 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T15:52:02Z
user: login: JoeyStk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-01T16:19:40Z
user: login: charludo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-02T09:16:22Z
user: login: MizukiTemma
body: Thank you so much 😸 This makes POI more informativ in our app.

I have two concerns: @laravoelk @digitalfabrik/app-team 
1. Currently archived contacts are not excluded. Should we remove them from the API? **Note**: if we do, #3460 will be solved together.
3. The priamry contact is delivered as `contacts` together with other contact objects. It is however used as contact information of the POI and its E-mail address, phone number and website are passed as `email`, `phone_number` and `website` separately. Shoudl we exlude it from the list of related contacts?
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-02T20:48:28Z
user: login: MizukiTemma
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T11:08:36Z
user: login: MizukiTemma
body: Thank you 🚀 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-03T11:38:06Z

Review comments:
diff_hunk: @@ -71,9 +71,24 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "id": contact.id,
+                "area_of_responsibility": contact.area_of_responsibility
+                if contact.area_of_responsibility
+                else None,
path: integreat_cms/api/v3/locations.py
user: login: charludo
body: Isn't this `None` by default as well, like the other fields?
created_at: 2025-02-26T13:07:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,9 +71,24 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "id": contact.id,
+                "area_of_responsibility": contact.area_of_responsibility
+                if contact.area_of_responsibility
+                else None,
+                "name": contact.name,
+                "email": contact.email,
+                "phone_number": contact.phone_number,
+                "website": contact.website,
+            }
+        )
+
+    # for transitional period
+    primary_contact = contacts.first()
path: integreat_cms/api/v3/locations.py
user: login: charludo
body: Would it be possible to choose the only contact without an area of responsibility here? (Or none, if all contacts have an AoR, like it was previously?) I assume the app should/wants to display that contact first in the list, if it exists.
created_at: 2025-02-26T13:09:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,9 +71,24 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "id": contact.id,
+                "area_of_responsibility": contact.area_of_responsibility
+                if contact.area_of_responsibility
+                else None,
path: integreat_cms/api/v3/locations.py
user: login: JoeyStk
body: I tried this yesterday and it returned an empty string. I had a chat with the app team and they said None would be better than an empty string
created_at: 2025-02-26T13:22:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,9 +71,24 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "id": contact.id,
+                "area_of_responsibility": contact.area_of_responsibility
+                if contact.area_of_responsibility
+                else None,
+                "name": contact.name,
+                "email": contact.email,
+                "phone_number": contact.phone_number,
+                "website": contact.website,
+            }
+        )
+
+    # for transitional period
+    primary_contact = contacts.first()
path: integreat_cms/api/v3/locations.py
user: login: JoeyStk
body: Yes, good point & exactly
created_at: 2025-02-26T13:22:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,9 +71,24 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "id": contact.id,
+                "area_of_responsibility": contact.area_of_responsibility
+                if contact.area_of_responsibility
+                else None,
path: integreat_cms/api/v3/locations.py
user: login: charludo
body: Hm, strange. Maybe we should ensure in the `TruncatingCharField` that empty strings get converted to `None`? Not sure though if that is the issue.

In any case, that's beyond the scope of this PR, so I will mark as resolved 👍🏼 
created_at: 2025-02-26T13:28:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,9 +71,24 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "id": contact.id,
+                "area_of_responsibility": contact.area_of_responsibility
+                if contact.area_of_responsibility
+                else None,
+                "name": contact.name,
+                "email": contact.email,
+                "phone_number": contact.phone_number,
+                "website": contact.website,
+            }
+        )
+
+    # for transitional period
+    primary_contact = contacts.first()
path: integreat_cms/api/v3/locations.py
user: login: JoeyStk
body: Should be fixed now :)
created_at: 2025-02-26T16:48:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,9 +71,24 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "id": contact.id,
+                "area_of_responsibility": contact.area_of_responsibility
+                if contact.area_of_responsibility
+                else None,
+                "name": contact.name,
+                "email": contact.email,
+                "phone_number": contact.phone_number,
+                "website": contact.website,
+            }
+        )
+
+    # for transitional period
path: integreat_cms/api/v3/locations.py
user: login: charludo
body: Please link an issue here which should remove the comment (see the suggested Todo comment format of ruff, for example: https://docs.astral.sh/ruff/rules/missing-todo-link/). 
created_at: 2025-02-28T08:27:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,6 +8,7 @@
     "last_updated": "2023-01-22T13:52:56.726+01:00",
     "excerpt": "موقع الاختبار",
     "meta_description": "Meta description of the test location",
+    "contact": [],
path: tests/api/expected-outputs/augsburg_ar_locations.json
user: login: charludo
body: ```suggestion
    "contacts": [],
```
created_at: 2025-02-28T08:29:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,6 +8,7 @@
     "last_updated": "2020-01-22T13:52:13.616+01:00",
     "excerpt": "Beschreibung des Test-Ortes",
     "meta_description": "Meta-Beschreibung des Test-Ortes",
+    "contact": [],
path: tests/api/expected-outputs/augsburg_de_locations.json
user: login: charludo
body: ```suggestion
    "contacts": [],
```
created_at: 2025-02-28T08:29:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,6 +8,7 @@
     "last_updated": "2020-01-22T13:52:56.726+01:00",
     "excerpt": "Content of the test location",
     "meta_description": "Meta description of the test location",
+    "contact": [],
path: tests/api/expected-outputs/augsburg_en_locations.json
user: login: charludo
body: ```suggestion
    "contacts": [],
```
created_at: 2025-02-28T08:29:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,6 +8,7 @@
     "last_updated": "2020-01-22T13:52:13.616+01:00",
     "excerpt": "Beschreibung des Test-Ortes",
     "meta_description": "Meta-Beschreibung des Test-Ortes",
+    "contact": [],
path: tests/api/expected-outputs/nurnberg_de_locations.json
user: login: charludo
body: ```suggestion
    "contacts": [],
```
created_at: 2025-02-28T08:29:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,6 +8,7 @@
     "last_updated": "2020-01-22T13:52:56.726+01:00",
     "excerpt": "Content of the test location",
     "meta_description": "Meta description of the test location",
+    "contact": [],
path: tests/api/expected-outputs/nurnberg_en_locations.json
user: login: charludo
body: ```suggestion
    "contacts": [],
```
created_at: 2025-02-28T08:29:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,9 +71,24 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "id": contact.id,
+                "area_of_responsibility": contact.area_of_responsibility
+                if contact.area_of_responsibility
+                else None,
+                "name": contact.name,
+                "email": contact.email,
+                "phone_number": contact.phone_number,
+                "website": contact.website,
+            }
+        )
+
+    # for transitional period
+    primary_contact = contacts.first()
path: integreat_cms/api/v3/locations.py
user: login: charludo
body: Yes, perfect 💪🏼 
created_at: 2025-02-28T08:30:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -337,6 +337,14 @@ RESPONSE
          "website": String | null,       // The website for this location if primary_contact exists
          "email": String | null,         // The email for this location if primary_contact exists
          "phone_number": String | null,  // The phone number for this location if primary_contact exists
+         "contacts": [                   // The contact data linked to this location
+            "id": contact.id,            // The id of the contact
+            "area_of_responsibility": String | null   // The area of responsibility, e.g. advisory, of a contact
+            "name": String,              // The name of the contact
+            "email": String,             // An email address of the contact
+            "phone_number": String,      // A phone number of the contact
+            "website": String,           // The website of the contact
+         ] | null,
path: docs/src/api-docs.rst
user: login: charludo
body: ```suggestion
         "contacts": [                   // The contact data linked to this location
            {
                "id": contact.id,            // The id of the contact
                "area_of_responsibility": String | null   // The area of responsibility, e.g. advisory, of a contact
                "name": String,              // The name of the contact
                "email": String,             // An email address of the contact
                "phone_number": String,      // A phone number of the contact
                "website": String,           // The website of the contact
            },
         ],
```

AFAICT it can't be `null` from the way the code handles it, only empty list. 
created_at: 2025-02-28T15:51:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,9 +71,25 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "id": contact.id,
path: integreat_cms/api/v3/locations.py
user: login: charludo
body: ```suggestion
```

(NOT SURE!) Is there a point to delivering this to the app?
created_at: 2025-02-28T15:51:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,9 +71,25 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+    contact_data = []
+    for contact in contacts:
+        contact_data.append(
+            {
+                "id": contact.id,
path: integreat_cms/api/v3/locations.py
user: login: JoeyStk
body: Oh, you're right, probably not :)
created_at: 2025-03-01T16:19:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -337,6 +337,16 @@ RESPONSE
          "website": String | null,       // The website for this location if primary_contact exists
          "email": String | null,         // The email for this location if primary_contact exists
          "phone_number": String | null,  // The phone number for this location if primary_contact exists
+         "contacts": [                   // The contact data linked to this location
+            {
+                "id": contact.id,            // The id of the contact
path: docs/src/api-docs.rst
user: login: charludo
body: ```suggestion
```
created_at: 2025-03-02T09:16:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,9 +71,26 @@ def transform_poi_translation(poi_translation: POITranslation) -> dict[str, Any]
 
     poi = poi_translation.poi
 
-    primary_contact = Contact.objects.filter(
-        location=poi, area_of_responsibility=""
-    ).first()
+    contacts = Contact.objects.filter(location=poi).all()
+
+    # Note(johannes): Remove the primary_contact and the according three fields (phone_number, website, and email) once
+    # https://github.com/digitalfabrik/integreat-app/issues/3121 is resolved.
+    primary_contact = contacts.filter(area_of_responsibility="").first()
+
+    contacts = contacts.filter(archived=False)
path: integreat_cms/api/v3/locations.py
user: login: MizukiTemma
body: ~Sorry, again. Should the primary contact always be delivered even if archived?~

🙈 stupid me 
created_at: 2025-03-03T11:08:36Z
author_association: MEMBER

PR comments:
user: login: JoeyStk
created_at: 2025-02-25T17:06:25Z
body: I need to adjust a few things after receiving feedback from the app team
author_association: CONTRIBUTOR
user: login: JoeyStk
created_at: 2025-02-25T17:50:07Z
body: This PR is ready for review again :)
author_association: CONTRIBUTOR
user: login: JoeyStk
created_at: 2025-02-28T13:33:27Z
body: @charludo Thank you a lot for your review and all of your help! :purple_heart: 

> oh, please also amend the API docs

Should be resolved now too :)
author_association: CONTRIBUTOR
user: login: JoeyStk
created_at: 2025-03-03T09:01:00Z
body: @MizukiTemma thanks a lot for your review. I think you brought up good points. 

> Currently archived contacts are not excluded. Should we remove them from the API? Note: if we do,
https://github.com/digitalfabrik/integreat-cms/issues/3460 will be solved together.

I think you're right here. The app team doesn't know which contacts are archived and which ones are not. I think the filtering needs to be on our side. I will add this, once this has been confirmed by the @digitalfabrik/app-team.

> The priamry contact is delivered as contacts together with other contact objects. It is however used as contact information of the POI and its E-mail address, phone number and website are passed as email, phone_number and website separately. Shoudl we exlude it from the list of related contacts?

As our plan is to remove the three fields e-mail, phone number and website in the near feature, it makes sense to keep everything in contacts (so as it currently is). Once the app team switched their implementation from the three fields to the all encompassing contacts endpoint, we can remove the three fields once and for all. But also here I'm waiting for confirmation from @digitalfabrik/app-team :)

author_association: CONTRIBUTOR
user: login: steffenkleinle
created_at: 2025-03-03T09:08:26Z
body: > @MizukiTemma thanks a lot for your review. I think you brought up good points.
> 
> > Currently archived contacts are not excluded. Should we remove them from the API? Note: if we do,
> > #3460 will be solved together.
> 
> I think you're right here. The app team doesn't know which contacts are archived and which ones are not. I think the filtering needs to be on our side. I will add this, once this has been confirmed by the @digitalfabrik/app-team.
> 
> > The priamry contact is delivered as contacts together with other contact objects. It is however used as contact information of the POI and its E-mail address, phone number and website are passed as email, phone_number and website separately. Shoudl we exlude it from the list of related contacts?
> 
> As our plan is to remove the three fields e-mail, phone number and website in the near feature, it makes sense to keep everything in contacts (so as it currently is). Once the app team switched their implementation from the three fields to the all encompassing contacts endpoint, we can remove the three fields once and for all. But also here I'm waiting for confirmation from @digitalfabrik/app-team :)

Thanks @MizukiTemma and @JoeyStk, good points. I agree with @JoeyStk on both questions, archived contacts should be filtered out on the backend side while filtering out the three fileds won't be necessary as we will do a clean switch :)
author_association: MEMBER
user: login: MizukiTemma
created_at: 2025-03-03T09:36:04Z
body: @steffenkleinle 
So, the primary contact can be distinguished from other contacts on the app side and we don't have to deliver it separately? 
author_association: MEMBER
user: login: JoeyStk
created_at: 2025-03-03T10:57:19Z
body: Still needs a release note for #3460 and then this is ready for review again :)
author_association: CONTRIBUTOR
user: login: JoeyStk
created_at: 2025-03-03T11:01:39Z
body: @MizukiTemma this PR is ready for review again :)
author_association: CONTRIBUTOR