From 6f92c740c7b3c09a3843fd4e831236a121b33757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Sun, 10 Nov 2024 15:46:44 +0100 Subject: [PATCH] filter design and wip implementation --- filter-design.md | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ games/filters.py | 61 ++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 filter-design.md create mode 100644 games/filters.py diff --git a/filter-design.md b/filter-design.md new file mode 100644 index 0000000..42d94a1 --- /dev/null +++ b/filter-design.md @@ -0,0 +1,96 @@ +# Django + +Session.objects.filter(timestamp_start__year=2024) + +# JSON +```json +{ + "type": "session_start", + "operator": "equals", + "value": "2024" +} +``` + +# HTML +```html + +``` + +# Python: Python -> HTML +```python +filters = [ + { + "type": "session_start", + "operator": "equals", + "value": "2024" + } +] + +# predefined values +session_start_select = Select(name="filters", children=session_start_options) +session_start_options = [ + Option(value=create_filter("session_start", "equals", value=year)) + for year in range(2000, 2024) +] + +# user-selected values + + +``` + +# Python: JSON -> Django +```python +filter_types = { + "session_start": { + "equals": "timestamp_start__exact=", + "isnull": "timestamp_start__exact=None", + "greater_than": "timestamp_start__gt=", + "less_than": "timestamp_start__lt=", + }, +} +# filter_string = request.GET.get("filters") +filter_string = """ +{ + "type": "session_start", + "operator": "equals", + "value": "2024" +} +""" +def string_to_django_filter_dict(s: str): + if s[-1] == "=": + s + value + key, value = s.split("=") + return {key: value} + +filter_obj = json.loads(filter_string)[0] +field, operator, value = filter_obj + +if type in filter_types: + if operator in filter_types[type]: + queryset.filter(Q(**string_to_django_filter_dict(filter_types[type][operator])})) + else: + return False +``` + +# Python: Django -> JSON -> URI param +```python +filters = [ + { + "type": "session_start", + "operator": "equals", + "value": "2024" + } +] +context = { + "filters": json.dumps(filters) +} +return render("filter.html", context) +``` + +# Python: Django -> JSON (function) +```python +create_filter("session_start", "operator": "equals", "value": "2024") +``` diff --git a/games/filters.py b/games/filters.py new file mode 100644 index 0000000..d555892 --- /dev/null +++ b/games/filters.py @@ -0,0 +1,61 @@ +import json +from typing import TypeAlias, TypedDict, TypeVar + +from django.db.models import Model, Q +from django.db.models.query import QuerySet + +filter_types = { + "session_start": { + "equals": "timestamp_start__year__exact=", + "isnull": "timestamp_start__exact=None", + "greater_than": "timestamp_start__gt=", + "less_than": "timestamp_start__lt=", + }, +} + + +class Filter(TypedDict): + name: str + operator: str + value: str + + +FilterList: TypeAlias = list[Filter] + + +def string_to_django_filter_dict(s: str, value: str = "") -> dict[str, str | int]: + if s[-1] == "=": + s += value + key, value = s.split("=") + return {key: value} + + +T = TypeVar("T", bound=Model) + + +def apply_json_filter[T](s: str, queryset: QuerySet[T]) -> QuerySet[T]: + filter_obj = urlsafe_json_decode(s) + name, operator, value = (filter_obj[k] for k in ["name", "operator", "value"]) + if name in filter_types: + if operator in filter_types[name]: + filtered = queryset.filter( + Q(**string_to_django_filter_dict(filter_types[name][operator], value)) + ) + return filtered + return queryset + + +urlsafe_encode_table = str.maketrans({",": "^", ":": "-", " ": ""}) +urlsafe_decode_table = str.maketrans({"^": ",", "-": ":"}) + + +def urlsafe_json_encode[T](obj: T) -> str: + json_string = json.dumps(obj) + safe_string = json_string.translate(urlsafe_encode_table) + return safe_string + + +def urlsafe_json_decode[T](s: str) -> T: + unsafe_string = s.translate(urlsafe_decode_table) + obj = json.loads(unsafe_string) + return obj