From d7a7bc56212e2bc1396d1c0c6defa57d21b16e0a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 19 Nov 2020 11:53:02 +0100
Subject: [PATCH] misc: add with_custom_view filter (#48553)

---
 tests/test_carddef.py             | 68 ++++++++++++++++++++++++++--
 tests/test_formdata.py            | 74 ++++++++++++++++++++++++++-----
 wcs/carddef.py                    | 25 ++---------
 wcs/custom_views.py               | 21 ++++++++-
 wcs/qommon/templatetags/qommon.py |  7 ++-
 wcs/variables.py                  | 13 ++++++
 6 files changed, 168 insertions(+), 40 deletions(-)

diff --git a/tests/test_carddef.py b/tests/test_carddef.py
index 7f73d089..35df0952 100644
--- a/tests/test_carddef.py
+++ b/tests/test_carddef.py
@@ -223,22 +223,82 @@ def test_template_access(pub):
     carddef.name = 'foo'
     carddef.fields = [
         StringField(id='1', label='Test', type='string', varname='foo'),
+        StringField(id='2', label='key', type='string', varname='key'),
     ]
     carddef.store()
     carddef.data_class().wipe()
 
     for i in range(10):
         carddata = carddef.data_class()()
-        carddata.data = {'1': 'blah'}
+        if i % 3 == 0:
+            carddata.data = {'1': 'blah'}
+        if i % 3 == 1:
+            carddata.data = {'1': 'foo'}
+        if i % 3 == 2:
+            carddata.data = {'1': 'bar'}
+        carddata.data['2'] = str(i)
         carddata.just_created()
         carddata.store()
 
     context = pub.substitutions.get_context_variables(mode='lazy')
-    tmpl = Template('{{cards.foo.objects|filter_by:"foo"|filter_value:"blah"|count}}')
-    assert tmpl.render(context) == '10'
 
+    tmpl = Template('{{cards.foo.objects|filter_by:"foo"|filter_value:"blah"|count}}')
+    assert tmpl.render(context) == '4'
     tmpl = Template('{{cards|objects:"foo"|filter_by:"foo"|filter_value:"blah"|count}}')
-    assert tmpl.render(context) == '10'
+    assert tmpl.render(context) == '4'
+
+    pub.custom_view_class.wipe()
+
+    custom_view1 = pub.custom_view_class()
+    custom_view1.title = 'datasource card view'
+    custom_view1.formdef = carddef
+    custom_view1.columns = {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}]}
+    custom_view1.filters = {'filter-1': 'on', 'filter-1-value': 'blah'}
+    custom_view1.visibility = 'datasource'
+    custom_view1.order_by = '-f2'
+    custom_view1.store()
+
+    custom_view2 = pub.custom_view_class()
+    custom_view2.title = 'shared card view'
+    custom_view2.formdef = carddef
+    custom_view2.columns = {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}]}
+    custom_view2.filters = {'filter-1': 'on', 'filter-1-value': 'foo'}
+    custom_view2.visibility = 'any'
+    custom_view2.order_by = 'f2'
+    custom_view2.store()
+
+    custom_view3 = pub.custom_view_class()
+    custom_view3.title = 'private card view'
+    custom_view3.formdef = carddef
+    custom_view3.columns = {'list': [{'id': 'id'}]}
+    custom_view3.filters = {}
+    custom_view3.visibility = 'owner'
+    custom_view3.usier_id = 42
+    custom_view3.order_by = 'id'
+    custom_view3.store()
+
+    tmpl = Template('{{cards.foo.objects|with_custom_view:"datasource-card-view"|count}}')
+    assert tmpl.render(context) == '4'
+    tmpl = Template('{{cards|objects:"foo"|with_custom_view:"datasource-card-view"|count}}')
+    assert tmpl.render(context) == '4'
+    tmpl = Template('{% for data in cards|objects:"foo"|with_custom_view:"datasource-card-view" %}{{ data.internal_id }},{% endfor %}')
+    assert tmpl.render(context) == '10,7,4,1,'
+
+    tmpl = Template('{{cards.foo.objects|with_custom_view:"shared-card-view"|count}}')
+    assert tmpl.render(context) == '3'
+    tmpl = Template('{{cards|objects:"foo"|with_custom_view:"shared-card-view"|count}}')
+    assert tmpl.render(context) == '3'
+    tmpl = Template('{% for data in cards|objects:"foo"|with_custom_view:"shared-card-view" %}{{ data.internal_id }},{% endfor %}')
+    assert tmpl.render(context) == '2,5,8,'
+
+    tmpl = Template('{{cards.foo.objects|with_custom_view:"private-card-view"|count}}')
+    assert tmpl.render(context) == '0'
+    tmpl = Template('{{cards|objects:"foo"|with_custom_view:"private-card-view"|count}}')
+    assert tmpl.render(context) == '0'
+    tmpl = Template('{{cards.foo.objects|with_custom_view:"unknown"|count}}')
+    assert tmpl.render(context) == '0'
+    tmpl = Template('{{cards|objects:"foo"|with_custom_view:"unknown"|count}}')
+    assert tmpl.render(context) == '0'
 
 
 def test_data_source_access_invalid_id(pub):
diff --git a/tests/test_formdata.py b/tests/test_formdata.py
index 6b15fb6b..76a9599c 100644
--- a/tests/test_formdata.py
+++ b/tests/test_formdata.py
@@ -1134,25 +1134,30 @@ def test_lazy_formdata_queryset_get_from_first(pub, variable_test_data):
     assert tmpl.render(context) == 'bar'
 
 
-def test_lazy_global_forms(pub, variable_test_data):
-    lazy_formdata = variable_test_data
-    formdef = lazy_formdata._formdef
+def test_lazy_global_forms(pub):
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'foobarlazy'
+    formdef.fields = [
+        fields.StringField(id='1', label='Test', type='string', varname='foo'),
+        fields.StringField(id='2', label='key', type='string', varname='key'),
+    ]
     formdef.store()
-    data_class = lazy_formdata._formdef.data_class()
+    formdef.data_class().wipe()
     for i in range(6):
-        formdata = data_class()
-        formdata.data = {'0': 'bar'}
+        formdata = formdef.data_class()()
+        formdata.data = {'1': 'bar', '2': str(i)}
         formdata.just_created()
         formdata.store()
     for i in range(4):
-        formdata = data_class()
-        formdata.data = {'0': 'foo'}
+        formdata = formdef.data_class()()
+        formdata.data = {'1': 'foo', '2': str(i+6)}
         formdata.just_created()
         formdata.jump_status('finished')
         formdata.store()
 
-    formdata = data_class()
-    formdata.data = {'0': 'bar'}
+    formdata = formdef.data_class()()
+    formdata.data = {'1': 'bar'}
     formdata.status = 'draft'
     formdata.store()
 
@@ -1161,8 +1166,53 @@ def test_lazy_global_forms(pub, variable_test_data):
     tmpl = Template('{{forms.foobarlazy.slug}}')
     assert tmpl.render(context) == 'foobarlazy'
 
-    tmpl = Template('{{forms|objects:"foobarlazy"|filter_by:"foo_foo"|filter_value:"bar"|count}}')
-    assert tmpl.render(context) == '7'
+    tmpl = Template('{{forms|objects:"foobarlazy"|filter_by:"foo"|filter_value:"bar"|count}}')
+    assert tmpl.render(context) == '6'
+
+    pub.custom_view_class.wipe()
+
+    custom_view1 = pub.custom_view_class()
+    custom_view1.title = 'datasource form view'
+    custom_view1.formdef = formdef
+    custom_view1.columns = {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}]}
+    custom_view1.filters = {'filter-1': 'on', 'filter-1-value': 'bar'}
+    custom_view1.visibility = 'datasource'
+    custom_view1.order_by = '-f2'
+    custom_view1.store()
+
+    custom_view2 = pub.custom_view_class()
+    custom_view2.title = 'shared form view'
+    custom_view2.formdef = formdef
+    custom_view2.columns = {'list': [{'id': 'id'}, {'id': 'time'}, {'id': 'status'}]}
+    custom_view2.filters = {'filter-1': 'on', 'filter-1-value': 'foo'}
+    custom_view2.visibility = 'any'
+    custom_view2.order_by = 'f2'
+    custom_view2.store()
+
+    custom_view3 = pub.custom_view_class()
+    custom_view3.title = 'private form view'
+    custom_view3.formdef = formdef
+    custom_view3.columns = {'list': [{'id': 'id'}]}
+    custom_view3.filters = {}
+    custom_view3.visibility = 'owner'
+    custom_view3.usier_id = 42
+    custom_view3.order_by = 'id'
+    custom_view3.store()
+
+    tmpl = Template('{{forms|objects:"foobarlazy"|with_custom_view:"datasource-form-view"|count}}')
+    assert tmpl.render(context) == '6'
+    tmpl = Template('{% for data in forms|objects:"foobarlazy"|with_custom_view:"datasource-form-view" %}{{ data.internal_id }},{% endfor %}')
+    assert tmpl.render(context) == '6,5,4,3,2,1,'
+
+    tmpl = Template('{{forms|objects:"foobarlazy"|with_custom_view:"shared-form-view"|count}}')
+    assert tmpl.render(context) == '4'
+    tmpl = Template('{% for data in forms|objects:"foobarlazy"|with_custom_view:"shared-form-view" %}{{ data.internal_id }},{% endfor %}')
+    assert tmpl.render(context) == '7,8,9,10,'
+
+    tmpl = Template('{{forms|objects:"foobarlazy"|with_custom_view:"private-form-view"|count}}')
+    assert tmpl.render(context) == '0'
+    tmpl = Template('{{forms|objects:"foobarlazy"|with_custom_view:"unknown"|count}}')
+    assert tmpl.render(context) == '0'
 
 
 def test_lazy_variables(pub, variable_test_data):
diff --git a/wcs/carddef.py b/wcs/carddef.py
index fc15ecef..d0a0e9c8 100644
--- a/wcs/carddef.py
+++ b/wcs/carddef.py
@@ -172,25 +172,6 @@ class CardDef(FormDef):
         except IndexError:
             return None
 
-    @classmethod
-    def get_data_source_criterias(cls, carddef, custom_view):
-        from wcs.backoffice.management import FormPage
-        form_page = FormPage(formdef=carddef, view=custom_view, update_breadcrumbs=False)
-        criterias = form_page.get_view_criterias()
-
-        selected_filter = custom_view.get_filter()
-        if selected_filter and selected_filter != 'all':
-            if selected_filter == 'pending':
-                applied_filters = ['wf-%s' % x.id for x in carddef.workflow.get_not_endpoint_status()]
-            elif selected_filter == 'done':
-                applied_filters = ['wf-%s' % x.id for x in carddef.workflow.get_endpoint_status()]
-            else:
-                applied_filters = ['wf-%s' % selected_filter]
-            if applied_filters:
-                criterias.append(Contains('status', applied_filters))
-
-        return criterias
-
     @classmethod
     def get_data_source_items(cls, data_source_id, query=None, limit=None,
             custom_view=None, get_by_id=None, get_by_text=None):
@@ -209,7 +190,7 @@ class CardDef(FormDef):
                 if not custom_view:
                     return []
             order_by = custom_view.order_by
-            criterias.extend(cls.get_data_source_criterias(carddef, custom_view))
+            criterias.extend(custom_view.get_criterias())
             for criteria in criterias:
                 if not Template.is_template_string(criteria.value):
                     continue
@@ -259,7 +240,7 @@ class CardDef(FormDef):
         if len(parts) != 3:
             return []
         try:
-            carddef = cls.get_by_urlname(parts[1])
+            cls.get_by_urlname(parts[1])
         except KeyError:
             return []
         custom_view = cls.get_data_source_custom_view(data_source_id)
@@ -267,7 +248,7 @@ class CardDef(FormDef):
             return []
         varnames = []
         from .fields import Field
-        for criteria in cls.get_data_source_criterias(carddef, custom_view):
+        for criteria in custom_view.get_criterias():
             if not isinstance(criteria.value, str):
                 continue
             varnames.extend(Field.get_referenced_varnames(formdef, criteria.value))
diff --git a/wcs/custom_views.py b/wcs/custom_views.py
index b7f0b60d..c3a6c8c5 100644
--- a/wcs/custom_views.py
+++ b/wcs/custom_views.py
@@ -22,7 +22,7 @@ from quixote import get_publisher
 
 from wcs.carddef import CardDef
 from wcs.formdef import FormDef
-from wcs.qommon.storage import StorableObject, Equal
+from wcs.qommon.storage import StorableObject, Equal, Contains
 from wcs.qommon.misc import simplify
 from .qommon.misc import xml_node_text
 
@@ -142,6 +142,25 @@ class CustomView(StorableObject):
     def get_default_filters(self):
         return [key[7:] for key in self.filters if key.startswith('filter-')]
 
+    def get_criterias(self):
+        from wcs.backoffice.management import FormPage
+        form_page = FormPage(formdef=self.formdef, view=self, update_breadcrumbs=False)
+        criterias = form_page.get_view_criterias()
+        print(criterias)
+
+        selected_filter = self.get_filter()
+        if selected_filter and selected_filter != 'all':
+            if selected_filter == 'pending':
+                applied_filters = ['wf-%s' % x.id for x in self.formdef.workflow.get_not_endpoint_status()]
+            elif selected_filter == 'done':
+                applied_filters = ['wf-%s' % x.id for x in self.formdef.workflow.get_endpoint_status()]
+            else:
+                applied_filters = ['wf-%s' % selected_filter]
+            if applied_filters:
+                criterias.append(Contains('status', applied_filters))
+
+        return criterias
+
     def export_to_xml(self, charset=None):
         root = ET.Element(self.xml_root_node)
         fields = [
diff --git a/wcs/qommon/templatetags/qommon.py b/wcs/qommon/templatetags/qommon.py
index e3dfc527..d01f06e2 100644
--- a/wcs/qommon/templatetags/qommon.py
+++ b/wcs/qommon/templatetags/qommon.py
@@ -605,10 +605,15 @@ def pending(queryset):
 def objects(forms_source, slug):
     if hasattr(slug, 'get_value'):
         slug = slug.get_value()  # unlazy
-    # assume formdef_source is an instance of CardsSouce of FormsSource
+    # assume formdef_source is an instance of CardsSource of FormsSource
     return getattr(forms_source, slug).objects
 
 
+@register.filter
+def with_custom_view(queryset, custom_view_slug):
+    return queryset.with_custom_view(custom_view_slug)
+
+
 @register.filter
 def order_by(queryset, attribute):
     if hasattr(attribute, 'get_value'):
diff --git a/wcs/variables.py b/wcs/variables.py
index d4ea9567..d629b291 100644
--- a/wcs/variables.py
+++ b/wcs/variables.py
@@ -91,6 +91,19 @@ class LazyFormDefObjectsManager(object):
                 break
         return self._clone(self._criterias + [Equal('status', wf_status)])
 
+    def with_custom_view(self, custom_view_slug):
+        lookup_criterias = [
+            Equal('formdef_type', self._formdef.xml_root_node),
+            NotEqual('visibility', 'owner'),
+            Equal('formdef_id', self._formdef.id),
+            Equal('slug', custom_view_slug),
+        ]
+        try:
+            custom_view = get_publisher().custom_view_class.select(lookup_criterias)[0]
+        except IndexError:
+            return self._clone([Equal('status', '_none')])
+        return self._clone(self._criterias + custom_view.get_criterias(), order_by=custom_view.order_by)
+
     def exclude_self(self):
         assert self._formdata
         return self._clone(self._criterias + [NotEqual('id', str(self._formdata.id))])
-- 
2.28.0

