From faa8bd05c44a6c86ad80d40396a6edc6a0269489 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Sat, 17 Dec 2022 11:09:47 +0100
Subject: [PATCH 2/5] sql: get display/structured columns for related fields
 (#72312)

---
 tests/backoffice_pages/test_columns.py |  2 +-
 wcs/backoffice/management.py           | 24 +++++++++++++++++++-----
 wcs/formdata.py                        |  5 +++--
 wcs/sql.py                             | 18 +++++++++++++++---
 4 files changed, 38 insertions(+), 11 deletions(-)

diff --git a/tests/backoffice_pages/test_columns.py b/tests/backoffice_pages/test_columns.py
index 0246fc279..3643d3f82 100644
--- a/tests/backoffice_pages/test_columns.py
+++ b/tests/backoffice_pages/test_columns.py
@@ -393,7 +393,7 @@ def test_backoffice_card_field_columns(pub):
     resp_csv = resp.click('Export a Spreadsheet')
     resp_csv.form['format'] = 'csv'
     resp_csv = resp_csv.form.submit('submit')
-    assert resp_csv.text.splitlines()[1].endswith(',"plop","2020-04-24","Yes","aa"')
+    assert resp_csv.text.splitlines()[1].endswith(',"plop","2020-04-24","Yes","A","aa"')
 
     resp_map = resp.click('Plot on a Map')
     geojson_url = re.findall(r'data-geojson-url="(.*?)"', resp_map.text)[0]
diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py
index 5c8ad1dac..4739cc69d 100644
--- a/wcs/backoffice/management.py
+++ b/wcs/backoffice/management.py
@@ -3911,14 +3911,15 @@ class FakeField:
 class RelatedField:
     is_related_field = True
     type = 'related-field'
-    store_display_value = None
     varname = None
+    related_field = None
 
     def __init__(self, carddef, field, parent_field):
         self.carddef = carddef
         self.related_field = field
         self.parent_field = parent_field
         self.parent_field_id = parent_field.id
+        self.key = self.related_field.key
 
     @property
     def id(self):
@@ -3940,6 +3941,14 @@ class RelatedField:
             self.related_field.label,
         )
 
+    @property
+    def store_display_value(self):
+        return self.related_field.store_display_value
+
+    @property
+    def store_structured_value(self):
+        return self.related_field.store_structured_value
+
     def get_view_value(self, value, **kwargs):
         if value is None:
             return ''
@@ -3953,27 +3962,31 @@ class RelatedField:
         return self.get_view_value(value)
 
     def get_csv_heading(self):
+        if self.related_field:
+            return self.related_field.get_csv_heading()
         return [self.label]
 
     def get_csv_value(self, value, **kwargs):
+        if self.related_field:
+            return self.related_field.get_csv_value(value, **kwargs)
         return [self.get_view_value(value)]
 
     def get_column_field_id(self):
         from wcs.sql import get_field_id
 
-        column_field_id = get_field_id(self.related_field)
-        if self.related_field.store_display_value:
-            column_field_id += '_display'
-        return column_field_id
+        return get_field_id(self.related_field)
 
 
 class UserRelatedField(RelatedField):
     # it is named 'user-label' and not 'user' for compatibility with existing
     # listings, as the 'classic' user column is named 'user-label'.
     parent_field_id = 'user-label'
+    store_display_value = None
+    store_structured_value = None
 
     def __init__(self, field):
         self.related_field = field
+        self.key = field.key
 
     def __repr__(self):
         return '<%s (field: %r)>' % (
@@ -3990,6 +4003,7 @@ class UserLabelRelatedField(UserRelatedField):
     # custom user-label column, targetting the "name" (= full name) column
     # of the users table
     id = 'user-label'
+    key = 'user-label'
     type = 'user-label'
     varname = 'user_label'
     has_relations = True
diff --git a/wcs/formdata.py b/wcs/formdata.py
index 07b50f8dc..4372cb7f7 100644
--- a/wcs/formdata.py
+++ b/wcs/formdata.py
@@ -878,7 +878,8 @@ class FormData(StorableObject):
             if field.type == 'anonymised':
                 return _('Yes') if self.anonymised else _('No')
 
-            field_value = data.get(field.id)
+            field_id = field.id
+            field_value = data.get(field_id)
             if field_value is None:
                 return ''
             if field.type in ['date', 'bool']:
@@ -888,7 +889,7 @@ class FormData(StorableObject):
             if max_length is not None:
                 # if max_length is set the target is a backoffice listing/table,
                 # return an html value, appropriately shortened.
-                field_value = data.get('%s_display' % field.id, field_value)
+                field_value = data.get('%s_display' % field_id, field_value)
                 return field.get_view_short_value(field_value, max_length)
             else:
                 # otherwise return the actual "raw" field value
diff --git a/wcs/sql.py b/wcs/sql.py
index 1f3f364ca..7bd4c04ab 100644
--- a/wcs/sql.py
+++ b/wcs/sql.py
@@ -2228,7 +2228,11 @@ class SqlMixin:
 
                 column_field_id = field.get_column_field_id()
                 columns.append('%s.%s' % (carddef_table_alias, column_field_id))
-                extra_fields.append(field.id)
+                if field.store_display_value:
+                    columns.append('%s.%s_display' % (carddef_table_alias, column_field_id))
+                if field.store_structured_value:
+                    columns.append('%s.%s_structured' % (carddef_table_alias, column_field_id))
+                extra_fields.append(field)
 
         conn, cur = get_connection_and_cursor()
         if cls._numerical_id:
@@ -2978,8 +2982,16 @@ class SqlDataMixin(SqlMixin):
         o.data = cls._row2obdata(row, cls._formdef)
         if extra_fields:
             # extra fields are tuck at the end
-            for i, field_id in enumerate(reversed(extra_fields)):
-                o.data[field_id] = row[-(i + 1)]
+            # count number of columns
+            count = (
+                len(extra_fields)
+                + len([x for x in extra_fields if x.store_display_value])
+                + len([x for x in extra_fields if x.store_structured_value])
+            )
+            i = len(row) - count
+            for field in extra_fields:
+                coldata, i = cls._col2obdata(row, i, field)
+                o.data.update(coldata)
         del o._last_update_time
         return o
 
-- 
2.39.0

