feat: implement Animal Registry view with filtering and pagination (Step 8.1)

- Add status field to filter DSL parser and resolver
- Create AnimalRepository with list_animals and get_facet_counts
- Implement registry templates with table, facet sidebar, infinite scroll
- Create registry route handler with HTMX partial support
- Default shows only alive animals; status filter overrides

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 14:59:13 +00:00
parent 254466827c
commit 8e155080e4
12 changed files with 1630 additions and 28 deletions

View File

@@ -27,13 +27,52 @@ class TestSimpleFilters:
def test_all_supported_fields(self) -> None:
"""All supported fields should parse."""
result = parse_filter("location:strip1 species:duck sex:male life_stage:adult tag:healthy")
assert len(result.filters) == 5
result = parse_filter(
"location:strip1 species:duck sex:male life_stage:adult tag:healthy status:alive"
)
assert len(result.filters) == 6
assert result.filters[0].field == "location"
assert result.filters[1].field == "species"
assert result.filters[2].field == "sex"
assert result.filters[3].field == "life_stage"
assert result.filters[4].field == "tag"
assert result.filters[5].field == "status"
class TestStatusField:
"""Test the status field for filtering by animal status."""
def test_status_alive(self) -> None:
"""status:alive -> filter by alive status."""
result = parse_filter("status:alive")
assert result == FilterAST([FieldFilter("status", ["alive"])])
def test_status_multiple_values(self) -> None:
"""status:alive|dead -> OR of statuses."""
result = parse_filter("status:alive|dead")
assert result == FilterAST([FieldFilter("status", ["alive", "dead"])])
def test_status_all_values(self) -> None:
"""All status values should parse."""
result = parse_filter("status:alive|dead|harvested|sold|merged_into")
assert result == FilterAST(
[FieldFilter("status", ["alive", "dead", "harvested", "sold", "merged_into"])]
)
def test_status_negated(self) -> None:
"""-status:dead -> exclude dead animals."""
result = parse_filter("-status:dead")
assert result == FilterAST([FieldFilter("status", ["dead"], negated=True)])
def test_status_combined_with_other_fields(self) -> None:
"""status:alive species:duck -> combine status with other filters."""
result = parse_filter("status:alive species:duck")
assert result == FilterAST(
[
FieldFilter("status", ["alive"]),
FieldFilter("species", ["duck"]),
]
)
class TestOrSyntax: