feat: add event backdating with collapsible datetime picker
Add ability to specify custom date/time when recording events, enabling historical data entry. Forms show "Now - Set custom date" with a collapsible datetime picker that converts to milliseconds. - Add event_datetime_field() component in templates/actions.py - Add datetime picker to all event forms (cohort, hatch, outcome, tag, attrs, move, feed) - Add _parse_ts_utc() helper to parse form timestamp or use current - Add tests for backdating functionality 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1552,3 +1552,134 @@ class TestStatusCorrectValidation:
|
||||
)
|
||||
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Backdating Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestParseTsUtcHelper:
|
||||
"""Tests for the _parse_ts_utc helper function."""
|
||||
|
||||
def test_parse_ts_utc_returns_current_for_none(self):
|
||||
"""Returns current time when value is None."""
|
||||
import time
|
||||
|
||||
from animaltrack.web.routes.actions import _parse_ts_utc
|
||||
|
||||
before = int(time.time() * 1000)
|
||||
result = _parse_ts_utc(None)
|
||||
after = int(time.time() * 1000)
|
||||
|
||||
assert before <= result <= after
|
||||
|
||||
def test_parse_ts_utc_returns_current_for_zero(self):
|
||||
"""Returns current time when value is '0'."""
|
||||
import time
|
||||
|
||||
from animaltrack.web.routes.actions import _parse_ts_utc
|
||||
|
||||
before = int(time.time() * 1000)
|
||||
result = _parse_ts_utc("0")
|
||||
after = int(time.time() * 1000)
|
||||
|
||||
assert before <= result <= after
|
||||
|
||||
def test_parse_ts_utc_returns_current_for_empty(self):
|
||||
"""Returns current time when value is empty string."""
|
||||
import time
|
||||
|
||||
from animaltrack.web.routes.actions import _parse_ts_utc
|
||||
|
||||
before = int(time.time() * 1000)
|
||||
result = _parse_ts_utc("")
|
||||
after = int(time.time() * 1000)
|
||||
|
||||
assert before <= result <= after
|
||||
|
||||
def test_parse_ts_utc_returns_provided_value(self):
|
||||
"""Returns the provided timestamp when valid."""
|
||||
from animaltrack.web.routes.actions import _parse_ts_utc
|
||||
|
||||
# Use a past timestamp
|
||||
past_ts = 1700000000000 # Some time in 2023
|
||||
|
||||
result = _parse_ts_utc(str(past_ts))
|
||||
|
||||
assert result == past_ts
|
||||
|
||||
def test_parse_ts_utc_returns_current_for_invalid(self):
|
||||
"""Returns current time when value is invalid."""
|
||||
import time
|
||||
|
||||
from animaltrack.web.routes.actions import _parse_ts_utc
|
||||
|
||||
before = int(time.time() * 1000)
|
||||
result = _parse_ts_utc("not-a-number")
|
||||
after = int(time.time() * 1000)
|
||||
|
||||
assert before <= result <= after
|
||||
|
||||
|
||||
class TestBackdatingCohort:
|
||||
"""Tests for backdating cohort creation."""
|
||||
|
||||
def test_cohort_uses_provided_timestamp(self, client, seeded_db, location_strip1_id):
|
||||
"""POST uses provided ts_utc for backdating."""
|
||||
# Use a past timestamp (Feb 13, 2025 in ms)
|
||||
backdated_ts = 1739404800000
|
||||
|
||||
resp = client.post(
|
||||
"/actions/animal-cohort",
|
||||
data={
|
||||
"species": "duck",
|
||||
"location_id": location_strip1_id,
|
||||
"count": "2",
|
||||
"life_stage": "adult",
|
||||
"sex": "female",
|
||||
"origin": "purchased",
|
||||
"ts_utc": str(backdated_ts),
|
||||
"nonce": "test-backdate-cohort-1",
|
||||
},
|
||||
)
|
||||
|
||||
assert resp.status_code == 200
|
||||
|
||||
# Verify the event was created with the backdated timestamp
|
||||
event_row = seeded_db.execute(
|
||||
"SELECT ts_utc FROM events WHERE type = 'AnimalCohortCreated' ORDER BY id DESC LIMIT 1"
|
||||
).fetchone()
|
||||
assert event_row is not None
|
||||
assert event_row[0] == backdated_ts
|
||||
|
||||
def test_cohort_uses_current_time_when_ts_utc_zero(self, client, seeded_db, location_strip1_id):
|
||||
"""POST uses current time when ts_utc is 0."""
|
||||
import time
|
||||
|
||||
before = int(time.time() * 1000)
|
||||
|
||||
resp = client.post(
|
||||
"/actions/animal-cohort",
|
||||
data={
|
||||
"species": "duck",
|
||||
"location_id": location_strip1_id,
|
||||
"count": "2",
|
||||
"life_stage": "adult",
|
||||
"sex": "female",
|
||||
"origin": "purchased",
|
||||
"ts_utc": "0",
|
||||
"nonce": "test-backdate-cohort-2",
|
||||
},
|
||||
)
|
||||
|
||||
after = int(time.time() * 1000)
|
||||
|
||||
assert resp.status_code == 200
|
||||
|
||||
# Verify the event was created with a current timestamp
|
||||
event_row = seeded_db.execute(
|
||||
"SELECT ts_utc FROM events WHERE type = 'AnimalCohortCreated' ORDER BY id DESC LIMIT 1"
|
||||
).fetchone()
|
||||
assert event_row is not None
|
||||
assert before <= event_row[0] <= after
|
||||
|
||||
Reference in New Issue
Block a user