filter design and wip implementation
This commit is contained in:
		
							
								
								
									
										96
									
								
								filter-design.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								filter-design.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					# Django
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Session.objects.filter(timestamp_start__year=2024)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# JSON
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    "type": "session_start",
 | 
				
			||||||
 | 
					    "operator": "equals",
 | 
				
			||||||
 | 
					    "value": "2024"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# HTML
 | 
				
			||||||
 | 
					```html
 | 
				
			||||||
 | 
					<select name="filters">
 | 
				
			||||||
 | 
					    <option value='[{"type": "session_start", "operator": "equals", "value": "2024"}]'>2024</option>
 | 
				
			||||||
 | 
					    <option value='[{"type": "session_start", "operator": "equals", "value": "2023"}]'>2023</option>
 | 
				
			||||||
 | 
					</select>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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")
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										61
									
								
								games/filters.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								games/filters.py
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
				
			||||||
		Reference in New Issue
	
	Block a user