From c976d94bdda18db7db07f6b387d64a6e0919b48e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 20 Feb 2026 21:37:57 -0600 Subject: [PATCH] fix: add grid filter for animal birthdate --- src/wuttafarm/web/grids.py | 107 ++++++++++++++++++++-- src/wuttafarm/web/views/farmos/animals.py | 2 + 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/src/wuttafarm/web/grids.py b/src/wuttafarm/web/grids.py index 5e5e87c..8f4cde5 100644 --- a/src/wuttafarm/web/grids.py +++ b/src/wuttafarm/web/grids.py @@ -23,6 +23,8 @@ Custom grid stuff for use with farmOS / JSONAPI """ +import datetime + from wuttaweb.grids.filters import GridFilter @@ -35,12 +37,12 @@ class SimpleFilter(GridFilter): self.path = path or key def filter_equal(self, data, value): - if value: + if value := self.coerce_value(value): data.add_filter(self.path, "=", value) return data def filter_not_equal(self, data, value): - if value: + if value := self.coerce_value(value): data.add_filter(self.path, "<>", value) return data @@ -58,7 +60,7 @@ class StringFilter(SimpleFilter): default_verbs = ["contains", "equal", "not_equal"] def filter_contains(self, data, value): - if value: + if value := self.coerce_value(value): data.add_filter(self.path, "CONTAINS", value) return data @@ -80,22 +82,22 @@ class IntegerFilter(SimpleFilter): ] def filter_less_than(self, data, value): - if value: + if value := self.coerce_value(value): data.add_filter(self.path, "<", value) return data def filter_less_equal(self, data, value): - if value: + if value := self.coerce_value(value): data.add_filter(self.path, "<=", value) return data def filter_greater_than(self, data, value): - if value: + if value := self.coerce_value(value): data.add_filter(self.path, ">", value) return data def filter_greater_equal(self, data, value): - if value: + if value := self.coerce_value(value): data.add_filter(self.path, ">=", value) return data @@ -123,6 +125,88 @@ class NullableBooleanFilter(BooleanFilter): default_verbs = ["is_true", "is_false", "is_null", "is_not_null"] +# TODO: this may not work, it's not used anywhere yet +class DateFilter(SimpleFilter): + + data_type = "date" + + default_verbs = [ + "equal", + "not_equal", + "greater_than", + "greater_equal", + "less_than", + "less_equal", + # 'between', + ] + + default_verb_labels = { + "equal": "on", + "not_equal": "not on", + "greater_than": "after", + "greater_equal": "on or after", + "less_than": "before", + "less_equal": "on or before", + # "between": "between", + "is_null": "is null", + "is_not_null": "is not null", + "is_any": "is any", + } + + def coerce_value(self, value): + if value: + if isinstance(value, datetime.date): + return value + + try: + dt = datetime.datetime.strptime(value, "%Y-%m-%d") + except ValueError: + log.warning("invalid date value: %s", value) + else: + return dt.date() + + return None + + +# TODO: this is not very complete yet, so far used only for animal birthdate +class DateTimeFilter(DateFilter): + + default_verbs = ["equal", "is_null", "is_not_null"] + + def coerce_value(self, value): + """ + Convert user input to a proper ``datetime.date`` object. + """ + if value: + if isinstance(value, datetime.date): + return value + + try: + dt = datetime.datetime.strptime(value, "%Y-%m-%d") + except ValueError: + log.warning("invalid date value: %s", value) + else: + return dt.date() + + return None + + def filter_equal(self, data, value): + if value := self.coerce_value(value): + + start = datetime.datetime.combine(value, datetime.time(0)) + start = self.app.localtime(start, from_utc=False) + + stop = datetime.datetime.combine( + value + datetime.timedelta(days=1), datetime.time(0) + ) + stop = self.app.localtime(stop, from_utc=False) + + data.add_filter(self.path, ">=", int(start.timestamp())) + data.add_filter(self.path, "<", int(stop.timestamp())) + + return data + + class SimpleSorter: def __init__(self, key): @@ -171,10 +255,13 @@ class ResourceData: if self._data is None: params = {} + i = 0 for path, operator, value in self.filters: - params[f"filter[{path}][condition][path]"] = path - params[f"filter[{path}][condition][operator]"] = operator - params[f"filter[{path}][condition][value]"] = value + i += 1 + key = f"{i:03d}" + params[f"filter[{key}][condition][path]"] = path + params[f"filter[{key}][condition][operator]"] = operator + params[f"filter[{key}][condition][value]"] = value sorters = [] for path, sortdir in self.sorters: diff --git a/src/wuttafarm/web/views/farmos/animals.py b/src/wuttafarm/web/views/farmos/animals.py index e11ff59..5389d8f 100644 --- a/src/wuttafarm/web/views/farmos/animals.py +++ b/src/wuttafarm/web/views/farmos/animals.py @@ -37,6 +37,7 @@ from wuttafarm.web.grids import ( StringFilter, BooleanFilter, NullableBooleanFilter, + DateTimeFilter, ) from wuttafarm.web.forms.schema import AnimalTypeType @@ -120,6 +121,7 @@ class AnimalView(AssetMasterView): # birthdate g.set_renderer("birthdate", "date") g.set_sorter("birthdate", SimpleSorter("birthdate")) + g.set_filter("birthdate", DateTimeFilter) # sex g.set_enum("sex", enum.ANIMAL_SEX)