From 1c7c775029f8f2bc2679324e1a7288e847e124c5 Mon Sep 17 00:00:00 2001 From: Aaron Kable Date: Thu, 8 Jan 2026 08:50:54 +0800 Subject: [PATCH] refactor --- allianceauth/framework/datatables.py | 98 ++++++++++++------- .../custom/framework/datatables.md | 23 ++--- 2 files changed, 72 insertions(+), 49 deletions(-) diff --git a/allianceauth/framework/datatables.py b/allianceauth/framework/datatables.py index f80e88f8..731f8cbe 100644 --- a/allianceauth/framework/datatables.py +++ b/allianceauth/framework/datatables.py @@ -12,54 +12,80 @@ from allianceauth.services.hooks import get_extension_logger logger = get_extension_logger(__name__) class DataTablesView(View): - """ - Basic DataTables server side table rendering - """ + model: Model = None - templates: list[str] = [] columns: list[tuple] = [] + COL_SEARCH_REGEX = r"columns\[(?P[0-9]{1,})\]\[search\]\[value\]" + COL_SEARCHABLE_REGEX = r"columns\[(?P[0-9]{1,})\]\[searchable\]" + COL_SEARCHABLE_AS_REGEX_REGEX = r"columns\[(?P[0-9]{1,})\]\[search\]\[regex\]" + COL_ORDERABLE_REGEX = r"columns\[(?P[0-9]{1,})\]\[orderable\]" + + search_regex: re.Pattern = None + searchable_regex: re.Pattern = None + searchable_as_regex_regex: re.Pattern = None + order_regex: re.Pattern = None + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.search_regex = re.compile(self.COL_SEARCH_REGEX) + self.searchable_regex = re.compile(self.COL_SEARCHABLE_REGEX) + self.searchable_as_regex_regex = re.compile(self.COL_SEARCHABLE_AS_REGEX_REGEX) + self.order_regex = re.compile(self.COL_ORDERABLE_REGEX) + def get_model_qs(self, request: HttpRequest): return self.model.objects def get_param(self, request: HttpRequest, key: str, cast=str, default=""): return cast(request.GET.get(key, default)) - def filter_qs(self, search_string: str): - # Global Search + def filter_qs(self, search_string: str, column_conf: dict): + # Search filter_q = Q() - if len(search_string) > 0: - val = search_string - for x in self.columns: - if x[0]: - # Apply search to all Columns - filter_q |= Q(**{f'{x[1]}__icontains': val}) + for id, c in column_conf.items(): + _c = self.columns[id][0] + if c["searchable"] and len(_c) > 0: + if len(c["search"]) and len(_c): + if c["regex"]: + filter_q |= Q(**{f'{_c}__iregex': c["search"]}) + else: + filter_q |= Q(**{f'{_c}__icontains': c["search"]}) + if len(search_string) > 0: + filter_q |= Q(**{f'{_c}__icontains': search_string}) return filter_q - def filter_col_qs(self, get: dict): - # Row Search - # TODO check if we have a regex search or not - # TODO check if searchable or not - col_id_regex = r"columns\[(?P[0-9]{1,})\]\[search\]\[value\]" - regex = re.compile(col_id_regex) - filter_q = Q() + def get_columns_config(self, get: dict): + columns = defaultdict( + lambda: { + "searchable": False, + "orderable": False, + "regex": False, + "search": "" + } + ) for c in get: - _r = regex.findall(c) + _r = self.search_regex.findall(c) if _r: - _c = self.columns[int(_r[0])] - if _c[0]: - if len(get[c]): - filter_q |= Q(**{f'{_c[1]}__iregex': get[c]}) - return filter_q + columns[int(_r[0])]["search"] = get[c] + _r_s = self.searchable_regex.findall(c) + if _r_s: + columns[int(_r_s[0])]["searchable"] = get[c] == "true" + _r_s_r = self.searchable_as_regex_regex.findall(c) + if _r_s_r: + columns[int(_r_s_r[0])]["regex"] = get[c] == "true" + _r_o = self.order_regex.findall(c) + if _r_o: + columns[int(_r_o[0])]["orderable"] = get[c] == "true" + return columns - def order_str(self, order_col, order_dir): + def order_str(self, order_col, order_dir, column_conf: dict): order = "" - _o = self.columns[order_col] - if _o[0]: + _o = column_conf[order_col] + if _o["orderable"]: if order_dir == 'desc': - order = '-' + _o[1] + order = '-' + self.columns[order_col][0] else: - order = _o[1] + order = self.columns[order_col][0] return order def render_template(self, request: HttpRequest, template: str, ctx: dict): @@ -85,24 +111,26 @@ class DataTablesView(View): limit = start + length + column_conf = self.get_columns_config(request.GET) + # Searches - filter_q = Q() | self.filter_qs(search_string) | self.filter_col_qs(request.GET) + filter_q = Q() | self.filter_qs(search_string, column_conf) # Build response rows items = [] qs = self.get_model_qs(request).filter(filter_q).order_by() # Apply ordering - order = self.order_str(order_col, order_dir) + order = self.order_str(order_col, order_dir, column_conf) if order != "": qs = qs.order_by(order) # build output for row in qs[start:limit]: - ctx = {"row": row} + ctx = {"note": row} row = [] - for t in self.templates: - row.append(self.render_template(request, t, ctx)) + for t in self.columns: + row.append(self.render_template(request, t[1], ctx)) items.append(row) # Build our output dict diff --git a/docs/development/custom/framework/datatables.md b/docs/development/custom/framework/datatables.md index b32792f8..3493f802 100644 --- a/docs/development/custom/framework/datatables.md +++ b/docs/development/custom/framework/datatables.md @@ -130,22 +130,17 @@ from allianceauth.eveonline.models import EveCharacter ## Datatables server side view class EveCharacterTable(DataTablesView): model = EveCharacter - # Columns are rendered from these templates - # Templates can be a html file or template language directly in the list below - templates = [ - "appname/stubs/icon.html", - "appname/stubs/name.html", - "appname/stubs/corp.html", - "{{ row.alliance_name }} {{ row.alliance_id }}" - ] - # Define the columns for filtering and ordering + # Define the columns as a tuple. + # String for field name for filtering and ordering + # String for the render template + # Templates can be a html file or template language directly in the list below columns = [ - # (can_sort: bool, "field_for_queries_or_sort") - (False, ""), - (True, "character_name"), - (True, "corporation_name"), - (True, "alliance_name"), + # ("field_for_queries_or_sort", template: str) + ("", "appname/stubs/icon.html"), + ("character_name", "appname/stubs/name.html"), + ("corporation_name", "appname/stubs/corp.html"), + ("alliance_name", "{{ row.alliance_name }} {{ row.alliance_id }}"), ] # if you need to do some prefetch or pre-filtering you can overide this function