query_filter.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import csv
  2. from dataclasses import dataclass, field
  3. from datetime import date
  4. from urllib.parse import quote, unquote
  5. EMPTY_FILTER_VALUE = "__empty__"
  6. EMPTY_FILTER_LABEL = "Ohne Wert"
  7. def decode_filter_values(value: str | None) -> list[str]:
  8. """Split the GET-decoded parameter before decoding individual values."""
  9. return list(dict.fromkeys(decoded for part in (value or "").split(",") if (decoded := unquote(part).strip())))
  10. def encode_filter_values(values) -> str:
  11. return ",".join(quote(value, safe="") for value in dict.fromkeys(str(value).strip() for value in values) if value)
  12. def parse_filter_date(value: str) -> date:
  13. selected = date.fromisoformat(value)
  14. if selected.isoformat() != value:
  15. raise ValueError("Expected YYYY-MM-DD")
  16. return selected
  17. def filter_clauses(config, params, exclude=None):
  18. clauses, values = [], {}
  19. for f in config:
  20. current = params.get(f.name, f.default_value)
  21. if f.name == exclude or f.filter_type == "hidden" or not current:
  22. continue
  23. parameter = f"filter_{f.name.lower()}"
  24. if f.filter_type == "date":
  25. operator = "<=" if "bis" in f.name.lower() else ">="
  26. clauses.append(f"F.[{f.key_column}] {operator} :{parameter}")
  27. # Bind a native date: SQL Server's datetime string conversion depends
  28. # on the connection language/DATEFORMAT, even for YYYY-MM-DD.
  29. values[parameter] = parse_filter_date(current)
  30. continue
  31. parts = decode_filter_values(current)
  32. terms = []
  33. for index, part in enumerate(parts):
  34. key = parameter if len(parts) == 1 else f"{parameter}_{index}"
  35. if part == EMPTY_FILTER_VALUE:
  36. terms.append(f"NULLIF(F.[{f.key_column}], '') IS NULL")
  37. continue
  38. operator = "=" if f.filter_type == "select" else "LIKE"
  39. terms.append(f"F.[{f.key_column}] {operator} :{key}")
  40. values[key] = part if f.filter_type == "select" else f"%{part}%"
  41. if terms:
  42. clauses.append("(" + " OR ".join(terms) + ")")
  43. return clauses, values
  44. @dataclass
  45. class FilterConfig:
  46. name: str
  47. text: str
  48. filter_type: str
  49. width: str
  50. key_column: str
  51. value_column: str
  52. sort_column: str
  53. visible: bool = True
  54. default_value: str = ""
  55. options: dict[str, str] | None = field(default_factory=dict)
  56. current_value: str | None = None
  57. @property
  58. def selected_values(self) -> list[str]:
  59. return decode_filter_values(self.current_value)
  60. @property
  61. def selected_items(self) -> list[dict[str, str]]:
  62. options = {str(key): str(value) for key, value in (self.options or {}).items()}
  63. options[EMPTY_FILTER_VALUE] = EMPTY_FILTER_LABEL
  64. return [{"value": value, "label": options.get(value, value)} for value in self.selected_values]
  65. @property
  66. def query_param(self) -> str:
  67. if self.current_value is None or self.current_value == "":
  68. if self.default_value is None or self.default_value == "":
  69. return None
  70. self.current_value = self.default_value
  71. if self.filter_type == "date":
  72. if "bis" in self.name.lower():
  73. return f" F.[{self.key_column}] <= '{self.current_value}' "
  74. return f" F.[{self.key_column}] >= '{self.current_value}' "
  75. # if self.filter_type == "select":
  76. # return f"{self.key_column} IN ({self.current_value})"
  77. return f" F.[{self.key_column}] LIKE '%{self.current_value}%' "
  78. @staticmethod
  79. def from_dict(
  80. name: str,
  81. filter_type: str,
  82. width: str,
  83. text: str,
  84. key_column: str,
  85. value_column: str,
  86. sort_column: str,
  87. default_value: str,
  88. ) -> "FilterConfig":
  89. if filter_type not in ["text", "select", "date", "hidden"]:
  90. filter_type = "text"
  91. if width not in ["1", "2", "3", "4"]:
  92. width = "2"
  93. if not text:
  94. text = name
  95. if not key_column:
  96. key_column = name
  97. if not value_column:
  98. value_column = key_column
  99. if not sort_column:
  100. sort_column = key_column
  101. if not default_value:
  102. default_value = ""
  103. return FilterConfig(
  104. name=name,
  105. text=text,
  106. filter_type=filter_type,
  107. width=width,
  108. key_column=key_column,
  109. value_column=value_column,
  110. sort_column=sort_column,
  111. default_value=default_value,
  112. )
  113. filter_type_option = {
  114. "text": "text",
  115. "select": "selectedOptionValue",
  116. "date": "selectedDate",
  117. "hidden": "selectedOptionValue",
  118. }
  119. def import_filter_config(area: str) -> list[FilterConfig]:
  120. res = []
  121. with open(f"templates\\{area}\\liste\\config\\liste_filter.csv", "r", newline="", encoding="latin-1") as csvfile:
  122. reader = csv.DictReader(csvfile, delimiter=";")
  123. for row in reader:
  124. config = FilterConfig.from_dict(*row.values())
  125. res.append(config)
  126. return res