Browse Source

add option groups to filter selection boxes

Daniel Sheffield 1 year ago
parent
commit
2432585567
3 changed files with 84 additions and 37 deletions
  1. 57 33
      app/rest/pyapi.py
  2. 13 2
      app/rest/templates/include-exclude.tpl
  3. 14 2
      app/rest/templates/select.tpl

+ 57 - 33
app/rest/pyapi.py

@@ -108,6 +108,49 @@ def get_query(**params):
 def normalize_query(query: FormsDict, allow: Iterable[str] = None):
     return get_query(**get_filter(query, allow=allow))
 
+def get_option_groups(data, filter_data, k, g, _type):
+    in_chart = data['$/unit'].apply(lambda x: (x or False) and True)
+    groups = sorted(set(data[g] if g is not None else []))
+    groups.append(None)
+    if _type == "exclude":
+        prefix = "!"
+    else:
+        prefix = ""
+    
+    for group in groups:
+        selected = []
+        unselected = []
+        if group is None:
+            if _type == "include":
+                selected.extend(filter_data[k][0] - (set(data[in_chart][k] if g is not None else set())))
+                unselected.extend(((set(data[in_chart][k]) if g is None else set()) | filter_data[k][1]) - filter_data[k][0])
+            else:
+                selected.extend(filter_data[k][1])
+                unselected.extend((
+                    set(data[in_chart][k]) if g is None else set()
+                ) | (
+                    filter_data[k][0] - set(data[in_chart][k]) - filter_data[k][1]
+                ))
+        else:
+            if _type == "include":
+                selected.extend(filter_data[k][0] & set(data[in_chart & (data[g].apply(lambda x,axis=None: x == group))][k]))
+                unselected.extend(set(data[in_chart & (data[g].apply(lambda x,axis=None: x == group))][k]) - filter_data[k][0])
+            else:
+                unselected.extend(set(data[in_chart & (data[g].apply(lambda x,axis=None: x == group))][k]))
+        assert set(selected) - set(unselected) == set(selected), f"{set(selected)} {set(unselected)}"
+        
+        yield {
+            "name": group,
+            "options": sorted(map(lambda x: {
+                "selected": x[0],
+                "value": f"{prefix}{x[1]}",
+                "display": x[1]  
+            }, chain(
+                map(lambda x: (True, x), set(selected)),
+                map(lambda x: (False, x), set(unselected)),
+            )), key=lambda x: x["display"] if "display" in x else x["value"])
+        } 
+
 def get_form(action, method, filter_data, data):
     keys = sorted(filter(lambda x: x not in ('unit', 'tag'), filter_data), key=lambda x: {
         'product': 0,
@@ -115,49 +158,20 @@ def get_form(action, method, filter_data, data):
         'group': 2,
     }[x])
     in_chart = data['$/unit'].apply(lambda x: (x or False) and True)
-    group = data[in_chart].groupby([
-        k for k in keys
-    ]).size().reset_index()[[
-        k for k in keys
-    ]]
     return template(
         'app/rest/templates/form',
         action=action,
         method=method,
-        header=[
-            template(
-                'app/rest/templates/filter-heading',
-                fname=k,
-                first=(idx == 0),
-            ) for idx, k in enumerate(keys)
-        ],
         **{
             k: {
                 "name": k,
                 "_include": {
-                    "options": sorted(map(lambda x: {
-                        "selected": x[0],
-                        "value": x[1],
-                    }, chain(
-                        map(lambda x: (True, x), filter_data[k][0]),
-                        map(lambda x: (False, x), set([
-                            x[k] for _, x in group.iterrows()
-                        ]) - filter_data[k][0])
-                    )), key=lambda x: x["display"] if "display" in x else x["value"])
+                    "option_groups": get_option_groups(data, filter_data, k, g, "include"),
                 },
                 "_exclude": {
-                    "options": sorted(map(lambda x: {
-                        "selected": x[0],
-                        "value": f"!{x[1]}",
-                        "display": x[1]
-                    }, chain(
-                        map(lambda x: (True, x), filter_data[k][1]),
-                        map(lambda x: (False, x), set([
-                            x[k] for _, x in group.iterrows()
-                        ]) - filter_data[k][1])
-                    )), key=lambda x: x["display"] if "display" in x else x["value"])
+                    "option_groups": get_option_groups(data, filter_data, k, g, "exclude"),
                 }
-            } for k in keys
+            } for k, g in zip(keys, [*keys[1:], None])
         },
         tags={
             "name": "tag",
@@ -167,7 +181,17 @@ def get_form(action, method, filter_data, data):
                     "value": x[1],
                 },chain(
                     map(lambda x: (True, x), filter_data['tag'][0]),
-                    map(lambda x: (False, x), set(chain(*data[in_chart]['tags'])) - filter_data['tag'][0])
+                    map(lambda x: (False, x), (set(chain(*data[in_chart]['tags'])) | filter_data['tag'][1]) - filter_data['tag'][0])
+                )), key=lambda x: x["display"] if "display" in x else x["value"])
+            },
+            "_exclude": {
+                "options": sorted(map(lambda x: {
+                    "selected": x[0],
+                    "value": f"!{x[1]}",
+                    "display": x[1]
+                },chain(
+                    map(lambda x: (True, x), filter_data['tag'][0]),
+                    map(lambda x: (False, x), (set(chain(*data[in_chart]['tags'])) | filter_data['tag'][0]) - filter_data['tag'][1])
                 )), key=lambda x: x["display"] if "display" in x else x["value"])
             }
         },

+ 13 - 2
app/rest/templates/include-exclude.tpl

@@ -5,10 +5,21 @@
       <h3>{{name.title()}}</h3>
     </div>
   </div>
-  <% include('app/rest/templates/select', id=f"{name}-include", name=name, options=_include["options"], hint="Include", multiple=True) %>
+  <%
+    include('app/rest/templates/select', id=f"{name}-include", name=name,
+      option_groups=_include["option_groups"] if "option_groups" in _include else [{
+        "name": None,
+        "options": _include["options"]
+      }],
+      hint="Include", multiple=True)
+  %>
   <%
   if defined("_exclude"):
-    include('app/rest/templates/select', id=f"{name}-exclude", name=name, options=_exclude["options"], hint="Exclude", multiple=True)
+    include('app/rest/templates/select', id=f"{name}-exclude", name=name,
+      option_groups=_exclude["option_groups"] if "option_groups" in _exclude else [{
+        "name": None,
+        "options": _exclude["options"]
+      }], hint="Exclude", multiple=True)
   end
   %>
 </div>

+ 14 - 2
app/rest/templates/select.tpl

@@ -15,8 +15,20 @@
     if defined("hint"):
       include('app/rest/templates/option', value=hint, disabled=True)
     end
-    for opt in options:
-      include('app/rest/templates/option', **opt)
+    if not defined("option_groups"):
+      option_groups = [{
+        "name": None,
+        "options": options
+      }]
+    end
+    for group in option_groups:
+      if group["name"] is None:
+        for opt in group["options"]:
+          include('app/rest/templates/option', **opt)
+        end
+      else:
+        include('app/rest/templates/optgroup', **group)
+      end
     end
   %>
 </select>