Embed labels in filter criteria (Stash-style) to retire pill resolver
Store {id, label} objects instead of bare IDs in MultiCriterion value/excludes.
FilterSelect pills now render directly from the embedded labels — no DB round-trip
to _resolve_game/device/platform_options. The filter URL and saved presets are
self-describing. MultiCriterion.to_q() extracts ids for querying; bare ints are
still accepted for backward compatibility.
Closes #9
https://claude.ai/code/session_01EyAJcMoDktLrY9tSbdHViA
This commit is contained in:
+42
-6
@@ -313,18 +313,54 @@ class _SetCriterion(_Criterion):
|
||||
|
||||
@dataclass
|
||||
class MultiCriterion(_SetCriterion):
|
||||
"""Filter on a many-to-many or ForeignKey relationship by ID list."""
|
||||
"""Filter on a many-to-many or ForeignKey relationship by ID list.
|
||||
|
||||
value: list[int] = field(default_factory=list)
|
||||
excludes: list[int] = field(default_factory=list)
|
||||
Each entry in ``value`` and ``excludes`` may be either a bare integer id or
|
||||
a ``{"id": <int>, "label": <str>}`` dict (Stash-style embedded label). The
|
||||
label is display-only; only the id is used for querying.
|
||||
"""
|
||||
|
||||
value: list = field(default_factory=list)
|
||||
excludes: list = field(default_factory=list)
|
||||
|
||||
def _ids(self, items: list) -> list[int]:
|
||||
"""Extract integer ids from a mixed list of bare ints or {id, label} dicts."""
|
||||
result = []
|
||||
for item in items:
|
||||
if isinstance(item, dict):
|
||||
result.append(int(item["id"]))
|
||||
else:
|
||||
result.append(int(item))
|
||||
return result
|
||||
|
||||
def to_q(self, field_name: str) -> Q:
|
||||
modifier = self.modifier
|
||||
value_ids = self._ids(self.value)
|
||||
excludes_ids = self._ids(self.excludes)
|
||||
if modifier in (Modifier.INCLUDES, Modifier.EQUALS):
|
||||
q = Q()
|
||||
if value_ids:
|
||||
q &= Q(**{f"{field_name}__in": value_ids})
|
||||
if excludes_ids:
|
||||
q &= ~Q(**{f"{field_name}__in": excludes_ids})
|
||||
return q
|
||||
if modifier == Modifier.IS_NULL:
|
||||
return Q(**{f"{field_name}__isnull": True})
|
||||
if modifier == Modifier.NOT_NULL:
|
||||
return Q(**{f"{field_name}__isnull": False})
|
||||
extra = self._extra_q(field_name)
|
||||
if extra is not None:
|
||||
return extra
|
||||
raise ValueError(f"Unsupported modifier {modifier} for MultiCriterion")
|
||||
|
||||
def _extra_q(self, field_name: str) -> Q | None:
|
||||
value_ids = self._ids(self.value)
|
||||
if self.modifier == Modifier.EXCLUDES:
|
||||
return ~Q(**{f"{field_name}__in": self.value})
|
||||
return ~Q(**{f"{field_name}__in": value_ids})
|
||||
if self.modifier == Modifier.INCLUDES_ALL:
|
||||
q = Q()
|
||||
for value in self.value:
|
||||
q &= Q(**{field_name: value})
|
||||
for id_val in value_ids:
|
||||
q &= Q(**{field_name: id_val})
|
||||
return q
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user