From 0f38c1b3c8065c3730c2f29daf29905e9fb2dc17 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 11 Feb 2021 15:03:13 +0100
Subject: [PATCH] sql: set ordering only if column exists (#50500)

---
 tests/backoffice_pages/test_custom_view.py | 68 ++++++++++++++++++++++
 wcs/sql.py                                 | 12 +++-
 2 files changed, 77 insertions(+), 3 deletions(-)

diff --git a/tests/backoffice_pages/test_custom_view.py b/tests/backoffice_pages/test_custom_view.py
index d32920fc..172aa1dc 100644
--- a/tests/backoffice_pages/test_custom_view.py
+++ b/tests/backoffice_pages/test_custom_view.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 import os
+import re
 
 import pytest
 
@@ -467,6 +468,73 @@ def test_backoffice_missing_custom_view(pub):
     assert resp.location == 'http://example.net/backoffice/management/form-title/1/?plop'
 
 
+def test_backoffice_custom_view_sort_field(pub):
+    if not pub.is_using_postgresql():
+        pytest.skip('this requires SQL')
+        return
+
+    create_superuser(pub)
+
+    FormDef.wipe()
+    pub.custom_view_class.wipe()
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [
+        fields.ItemField(
+            id='1',
+            label='field 1',
+            type='item',
+            items=['foo', 'bar', 'baz'],
+            display_locations=['validation', 'summary', 'listings'],
+        ),
+    ]
+    formdef.workflow_roles = {'_receiver': 1}
+    formdef.store()
+
+    formdef.data_class().wipe()
+    formdata = formdef.data_class()()
+    formdata.data = {'1': 'foo', '1_display': 'foo'}
+    formdata.jump_status('new')
+    formdata.store()
+    formdata = formdef.data_class()()
+    formdata.data = {'1': 'bar', '1_display': 'bar'}
+    formdata.jump_status('new')
+    formdata.store()
+    formdata = formdef.data_class()()
+    formdata.data = {'1': 'baz', '1_display': 'baz'}
+    formdata.jump_status('new')
+    formdata.store()
+
+    custom_view = pub.custom_view_class()
+    custom_view.title = 'shared custom test view'
+    custom_view.formdef = formdef
+    custom_view.visibility = 'any'
+    custom_view.columns = {'list': [{'id': 'id'}]}
+    custom_view.filters = {}
+    custom_view.order_by = 'f1'
+    custom_view.is_default = True
+    custom_view.store()
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/management/form-title/shared-custom-test-view/')
+    assert resp.text.count('<tr') == 4
+    # bar, baz, foo
+    assert re.findall(r'<a href="(\d)/">1-(\d)</a>', resp.text) == [('2', '2'), ('3', '3'), ('1', '1')]
+
+    custom_view.order_by = '-f1'
+    custom_view.store()
+    resp = app.get('/backoffice/management/form-title/shared-custom-test-view/')
+    assert resp.text.count('<tr') == 4
+    # foo, baz, bar
+    assert re.findall(r'<a href="(\d)/">1-(\d)</a>', resp.text) == [('1', '1'), ('3', '3'), ('2', '2')]
+
+    custom_view.order_by = 'unknown'
+    custom_view.store()
+    # unknown sort field, ignore it
+    resp = app.get('/backoffice/management/form-title/shared-custom-test-view/')
+    assert resp.text.count('<tr') == 4
+
+
 def test_carddata_custom_view(pub):
     user = create_user(pub)
 
diff --git a/wcs/sql.py b/wcs/sql.py
index 2425d826..2f586c3b 100644
--- a/wcs/sql.py
+++ b/wcs/sql.py
@@ -1516,11 +1516,17 @@ class SqlMixin(object):
         # [SEC_ORDER] security note: it is not possible to use
         # prepared statements for ORDER BY clauses, therefore input
         # is controlled beforehand (see misc.get_order_by_or_400).
+        direction = 'ASC'
         if order_by.startswith('-'):
             order_by = order_by[1:]
-            return ' ORDER BY %s DESC' % order_by.replace('-', '_')
-        else:
-            return ' ORDER BY %s' % order_by.replace('-', '_')
+            direction = 'DESC'
+        order_by = order_by.replace('-', '_')
+
+        fields = [x[0] for x in cls._table_static_fields] + cls.get_data_fields()
+        if order_by not in fields:
+            return ''
+
+        return ' ORDER BY %s %s' % (order_by, direction)
 
     @classmethod
     @guard_postgres
-- 
2.29.2

