Preserve form field values in eggs.py on validation errors
When a validation error occurs in the harvest or sell forms, the entered field values are now preserved and redisplayed to the user. This prevents the frustration of having to re-enter all values after a single field fails validation. Template changes (eggs.py): - Added default_* parameters to harvest_form and sell_form - Updated LabelInput/LabelTextArea fields to use these values Route changes (routes/eggs.py): - Updated _render_harvest_error to accept quantity and notes - Updated _render_sell_error to accept quantity, total_price_cents, buyer, and notes - All error return calls now pass form values through 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -365,7 +365,14 @@ async def product_collected(request: Request, session):
|
|||||||
# Validate location_id
|
# Validate location_id
|
||||||
if not location_id:
|
if not location_id:
|
||||||
return _render_harvest_error(
|
return _render_harvest_error(
|
||||||
request, db, locations, products, None, "Please select a location"
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
None,
|
||||||
|
"Please select a location",
|
||||||
|
quantity=quantity_str,
|
||||||
|
notes=notes,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate quantity
|
# Validate quantity
|
||||||
@@ -373,12 +380,26 @@ async def product_collected(request: Request, session):
|
|||||||
quantity = int(quantity_str)
|
quantity = int(quantity_str)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return _render_harvest_error(
|
return _render_harvest_error(
|
||||||
request, db, locations, products, location_id, "Quantity must be a number"
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
location_id,
|
||||||
|
"Quantity must be a number",
|
||||||
|
quantity=quantity_str,
|
||||||
|
notes=notes,
|
||||||
)
|
)
|
||||||
|
|
||||||
if quantity < 0:
|
if quantity < 0:
|
||||||
return _render_harvest_error(
|
return _render_harvest_error(
|
||||||
request, db, locations, products, location_id, "Quantity cannot be negative"
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
location_id,
|
||||||
|
"Quantity cannot be negative",
|
||||||
|
quantity=quantity_str,
|
||||||
|
notes=notes,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get timestamp - use provided or current (supports backdating)
|
# Get timestamp - use provided or current (supports backdating)
|
||||||
@@ -389,7 +410,14 @@ async def product_collected(request: Request, session):
|
|||||||
|
|
||||||
if not resolved_ids:
|
if not resolved_ids:
|
||||||
return _render_harvest_error(
|
return _render_harvest_error(
|
||||||
request, db, locations, products, location_id, "No ducks at this location"
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
location_id,
|
||||||
|
"No ducks at this location",
|
||||||
|
quantity=quantity_str,
|
||||||
|
notes=notes,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create product service
|
# Create product service
|
||||||
@@ -422,7 +450,16 @@ async def product_collected(request: Request, session):
|
|||||||
route="/actions/product-collected",
|
route="/actions/product-collected",
|
||||||
)
|
)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
return _render_harvest_error(request, db, locations, products, location_id, str(e))
|
return _render_harvest_error(
|
||||||
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
location_id,
|
||||||
|
str(e),
|
||||||
|
quantity=quantity_str,
|
||||||
|
notes=notes,
|
||||||
|
)
|
||||||
|
|
||||||
# Save user defaults (only if user exists in database)
|
# Save user defaults (only if user exists in database)
|
||||||
if UserRepository(db).get(actor):
|
if UserRepository(db).get(actor):
|
||||||
@@ -486,19 +523,48 @@ async def product_sold(request: Request, session):
|
|||||||
|
|
||||||
# Validate product_code
|
# Validate product_code
|
||||||
if not product_code:
|
if not product_code:
|
||||||
return _render_sell_error(request, db, locations, products, None, "Please select a product")
|
return _render_sell_error(
|
||||||
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
None,
|
||||||
|
"Please select a product",
|
||||||
|
quantity=quantity_str,
|
||||||
|
total_price_cents=total_price_str,
|
||||||
|
buyer=buyer,
|
||||||
|
notes=notes,
|
||||||
|
)
|
||||||
|
|
||||||
# Validate quantity
|
# Validate quantity
|
||||||
try:
|
try:
|
||||||
quantity = int(quantity_str)
|
quantity = int(quantity_str)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return _render_sell_error(
|
return _render_sell_error(
|
||||||
request, db, locations, products, product_code, "Quantity must be a number"
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
product_code,
|
||||||
|
"Quantity must be a number",
|
||||||
|
quantity=quantity_str,
|
||||||
|
total_price_cents=total_price_str,
|
||||||
|
buyer=buyer,
|
||||||
|
notes=notes,
|
||||||
)
|
)
|
||||||
|
|
||||||
if quantity < 1:
|
if quantity < 1:
|
||||||
return _render_sell_error(
|
return _render_sell_error(
|
||||||
request, db, locations, products, product_code, "Quantity must be at least 1"
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
product_code,
|
||||||
|
"Quantity must be at least 1",
|
||||||
|
quantity=quantity_str,
|
||||||
|
total_price_cents=total_price_str,
|
||||||
|
buyer=buyer,
|
||||||
|
notes=notes,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate total_price_cents
|
# Validate total_price_cents
|
||||||
@@ -506,12 +572,30 @@ async def product_sold(request: Request, session):
|
|||||||
total_price_cents = int(total_price_str)
|
total_price_cents = int(total_price_str)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return _render_sell_error(
|
return _render_sell_error(
|
||||||
request, db, locations, products, product_code, "Total price must be a number"
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
product_code,
|
||||||
|
"Total price must be a number",
|
||||||
|
quantity=quantity_str,
|
||||||
|
total_price_cents=total_price_str,
|
||||||
|
buyer=buyer,
|
||||||
|
notes=notes,
|
||||||
)
|
)
|
||||||
|
|
||||||
if total_price_cents < 0:
|
if total_price_cents < 0:
|
||||||
return _render_sell_error(
|
return _render_sell_error(
|
||||||
request, db, locations, products, product_code, "Total price cannot be negative"
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
product_code,
|
||||||
|
"Total price cannot be negative",
|
||||||
|
quantity=quantity_str,
|
||||||
|
total_price_cents=total_price_str,
|
||||||
|
buyer=buyer,
|
||||||
|
notes=notes,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get timestamp - use provided or current (supports backdating)
|
# Get timestamp - use provided or current (supports backdating)
|
||||||
@@ -544,7 +628,18 @@ async def product_sold(request: Request, session):
|
|||||||
route="/actions/product-sold",
|
route="/actions/product-sold",
|
||||||
)
|
)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
return _render_sell_error(request, db, locations, products, product_code, str(e))
|
return _render_sell_error(
|
||||||
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
product_code,
|
||||||
|
str(e),
|
||||||
|
quantity=quantity_str,
|
||||||
|
total_price_cents=total_price_str,
|
||||||
|
buyer=buyer,
|
||||||
|
notes=notes,
|
||||||
|
)
|
||||||
|
|
||||||
# Add success toast with link to event
|
# Add success toast with link to event
|
||||||
add_toast(
|
add_toast(
|
||||||
@@ -573,8 +668,17 @@ async def product_sold(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _render_harvest_error(request, db, locations, products, selected_location_id, error_message):
|
def _render_harvest_error(
|
||||||
"""Render harvest form with error message.
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
selected_location_id,
|
||||||
|
error_message,
|
||||||
|
quantity: str | None = None,
|
||||||
|
notes: str | None = None,
|
||||||
|
):
|
||||||
|
"""Render harvest form with error message and preserved field values.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: The HTTP request.
|
request: The HTTP request.
|
||||||
@@ -583,6 +687,8 @@ def _render_harvest_error(request, db, locations, products, selected_location_id
|
|||||||
products: List of sellable products.
|
products: List of sellable products.
|
||||||
selected_location_id: Currently selected location.
|
selected_location_id: Currently selected location.
|
||||||
error_message: Error message to display.
|
error_message: Error message to display.
|
||||||
|
quantity: Quantity value to preserve.
|
||||||
|
notes: Notes value to preserve.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
HTMLResponse with 422 status.
|
HTMLResponse with 422 status.
|
||||||
@@ -600,6 +706,8 @@ def _render_harvest_error(request, db, locations, products, selected_location_id
|
|||||||
harvest_error=error_message,
|
harvest_error=error_message,
|
||||||
harvest_action=product_collected,
|
harvest_action=product_collected,
|
||||||
sell_action=product_sold,
|
sell_action=product_sold,
|
||||||
|
harvest_quantity=quantity,
|
||||||
|
harvest_notes=notes,
|
||||||
**display_data,
|
**display_data,
|
||||||
),
|
),
|
||||||
title="Eggs - AnimalTrack",
|
title="Eggs - AnimalTrack",
|
||||||
@@ -610,8 +718,19 @@ def _render_harvest_error(request, db, locations, products, selected_location_id
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _render_sell_error(request, db, locations, products, selected_product_code, error_message):
|
def _render_sell_error(
|
||||||
"""Render sell form with error message.
|
request,
|
||||||
|
db,
|
||||||
|
locations,
|
||||||
|
products,
|
||||||
|
selected_product_code,
|
||||||
|
error_message,
|
||||||
|
quantity: str | None = None,
|
||||||
|
total_price_cents: str | None = None,
|
||||||
|
buyer: str | None = None,
|
||||||
|
notes: str | None = None,
|
||||||
|
):
|
||||||
|
"""Render sell form with error message and preserved field values.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: The HTTP request.
|
request: The HTTP request.
|
||||||
@@ -620,6 +739,10 @@ def _render_sell_error(request, db, locations, products, selected_product_code,
|
|||||||
products: List of sellable products.
|
products: List of sellable products.
|
||||||
selected_product_code: Currently selected product code.
|
selected_product_code: Currently selected product code.
|
||||||
error_message: Error message to display.
|
error_message: Error message to display.
|
||||||
|
quantity: Quantity value to preserve.
|
||||||
|
total_price_cents: Total price value to preserve.
|
||||||
|
buyer: Buyer value to preserve.
|
||||||
|
notes: Notes value to preserve.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
HTMLResponse with 422 status.
|
HTMLResponse with 422 status.
|
||||||
@@ -637,6 +760,10 @@ def _render_sell_error(request, db, locations, products, selected_product_code,
|
|||||||
sell_error=error_message,
|
sell_error=error_message,
|
||||||
harvest_action=product_collected,
|
harvest_action=product_collected,
|
||||||
sell_action=product_sold,
|
sell_action=product_sold,
|
||||||
|
sell_quantity=quantity,
|
||||||
|
sell_total_price_cents=total_price_cents,
|
||||||
|
sell_buyer=buyer,
|
||||||
|
sell_notes=notes,
|
||||||
**display_data,
|
**display_data,
|
||||||
),
|
),
|
||||||
title="Eggs - AnimalTrack",
|
title="Eggs - AnimalTrack",
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ def eggs_page(
|
|||||||
cost_per_egg: float | None = None,
|
cost_per_egg: float | None = None,
|
||||||
sales_stats: dict | None = None,
|
sales_stats: dict | None = None,
|
||||||
location_names: dict[str, str] | None = None,
|
location_names: dict[str, str] | None = None,
|
||||||
|
# Field value preservation on errors
|
||||||
|
harvest_quantity: str | None = None,
|
||||||
|
harvest_notes: str | None = None,
|
||||||
|
sell_quantity: str | None = None,
|
||||||
|
sell_total_price_cents: str | None = None,
|
||||||
|
sell_buyer: str | None = None,
|
||||||
|
sell_notes: str | None = None,
|
||||||
):
|
):
|
||||||
"""Create the Eggs page with tabbed forms.
|
"""Create the Eggs page with tabbed forms.
|
||||||
|
|
||||||
@@ -56,6 +63,12 @@ def eggs_page(
|
|||||||
cost_per_egg: 30-day average cost per egg in EUR.
|
cost_per_egg: 30-day average cost per egg in EUR.
|
||||||
sales_stats: Dict with 'total_qty', 'total_cents', and 'avg_price_per_egg_cents'.
|
sales_stats: Dict with 'total_qty', 'total_cents', and 'avg_price_per_egg_cents'.
|
||||||
location_names: Dict mapping location_id to location name for display.
|
location_names: Dict mapping location_id to location name for display.
|
||||||
|
harvest_quantity: Preserved quantity value on error.
|
||||||
|
harvest_notes: Preserved notes value on error.
|
||||||
|
sell_quantity: Preserved quantity value on error.
|
||||||
|
sell_total_price_cents: Preserved total price value on error.
|
||||||
|
sell_buyer: Preserved buyer value on error.
|
||||||
|
sell_notes: Preserved notes value on error.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Page content with tabbed forms.
|
Page content with tabbed forms.
|
||||||
@@ -85,6 +98,8 @@ def eggs_page(
|
|||||||
eggs_per_day=eggs_per_day,
|
eggs_per_day=eggs_per_day,
|
||||||
cost_per_egg=cost_per_egg,
|
cost_per_egg=cost_per_egg,
|
||||||
location_names=location_names,
|
location_names=location_names,
|
||||||
|
default_quantity=harvest_quantity,
|
||||||
|
default_notes=harvest_notes,
|
||||||
),
|
),
|
||||||
cls="uk-active" if harvest_active else None,
|
cls="uk-active" if harvest_active else None,
|
||||||
),
|
),
|
||||||
@@ -96,6 +111,10 @@ def eggs_page(
|
|||||||
action=sell_action,
|
action=sell_action,
|
||||||
recent_events=sell_events,
|
recent_events=sell_events,
|
||||||
sales_stats=sales_stats,
|
sales_stats=sales_stats,
|
||||||
|
default_quantity=sell_quantity,
|
||||||
|
default_total_price_cents=sell_total_price_cents,
|
||||||
|
default_buyer=sell_buyer,
|
||||||
|
default_notes=sell_notes,
|
||||||
),
|
),
|
||||||
cls=None if harvest_active else "uk-active",
|
cls=None if harvest_active else "uk-active",
|
||||||
),
|
),
|
||||||
@@ -113,6 +132,8 @@ def harvest_form(
|
|||||||
eggs_per_day: float | None = None,
|
eggs_per_day: float | None = None,
|
||||||
cost_per_egg: float | None = None,
|
cost_per_egg: float | None = None,
|
||||||
location_names: dict[str, str] | None = None,
|
location_names: dict[str, str] | None = None,
|
||||||
|
default_quantity: str | None = None,
|
||||||
|
default_notes: str | None = None,
|
||||||
) -> Div:
|
) -> Div:
|
||||||
"""Create the Harvest form for egg collection.
|
"""Create the Harvest form for egg collection.
|
||||||
|
|
||||||
@@ -125,6 +146,8 @@ def harvest_form(
|
|||||||
eggs_per_day: 30-day average eggs per day.
|
eggs_per_day: 30-day average eggs per day.
|
||||||
cost_per_egg: 30-day average cost per egg in EUR.
|
cost_per_egg: 30-day average cost per egg in EUR.
|
||||||
location_names: Dict mapping location_id to location name for display.
|
location_names: Dict mapping location_id to location name for display.
|
||||||
|
default_quantity: Preserved quantity value on error.
|
||||||
|
default_notes: Preserved notes value on error.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Div containing form and recent events section.
|
Div containing form and recent events section.
|
||||||
@@ -193,6 +216,7 @@ def harvest_form(
|
|||||||
step="1",
|
step="1",
|
||||||
placeholder="Number of eggs",
|
placeholder="Number of eggs",
|
||||||
required=True,
|
required=True,
|
||||||
|
value=default_quantity or "",
|
||||||
),
|
),
|
||||||
# Optional notes
|
# Optional notes
|
||||||
LabelTextArea(
|
LabelTextArea(
|
||||||
@@ -200,6 +224,7 @@ def harvest_form(
|
|||||||
id="notes",
|
id="notes",
|
||||||
name="notes",
|
name="notes",
|
||||||
placeholder="Optional notes",
|
placeholder="Optional notes",
|
||||||
|
value=default_notes or "",
|
||||||
),
|
),
|
||||||
# Event datetime picker (for backdating)
|
# Event datetime picker (for backdating)
|
||||||
event_datetime_field("harvest_datetime"),
|
event_datetime_field("harvest_datetime"),
|
||||||
@@ -231,6 +256,10 @@ def sell_form(
|
|||||||
action: Callable[..., Any] | str = "/actions/product-sold",
|
action: Callable[..., Any] | str = "/actions/product-sold",
|
||||||
recent_events: list[tuple[Event, bool]] | None = None,
|
recent_events: list[tuple[Event, bool]] | None = None,
|
||||||
sales_stats: dict | None = None,
|
sales_stats: dict | None = None,
|
||||||
|
default_quantity: str | None = None,
|
||||||
|
default_total_price_cents: str | None = None,
|
||||||
|
default_buyer: str | None = None,
|
||||||
|
default_notes: str | None = None,
|
||||||
) -> Div:
|
) -> Div:
|
||||||
"""Create the Sell form for recording sales.
|
"""Create the Sell form for recording sales.
|
||||||
|
|
||||||
@@ -241,6 +270,10 @@ def sell_form(
|
|||||||
action: Route function or URL string for form submission.
|
action: Route function or URL string for form submission.
|
||||||
recent_events: Recent (Event, is_deleted) tuples, most recent first.
|
recent_events: Recent (Event, is_deleted) tuples, most recent first.
|
||||||
sales_stats: Dict with 'total_qty' and 'total_cents' for 30-day sales.
|
sales_stats: Dict with 'total_qty' and 'total_cents' for 30-day sales.
|
||||||
|
default_quantity: Preserved quantity value on error.
|
||||||
|
default_total_price_cents: Preserved total price value on error.
|
||||||
|
default_buyer: Preserved buyer value on error.
|
||||||
|
default_notes: Preserved notes value on error.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Div containing form and recent events section.
|
Div containing form and recent events section.
|
||||||
@@ -315,6 +348,7 @@ def sell_form(
|
|||||||
step="1",
|
step="1",
|
||||||
placeholder="Number of items sold",
|
placeholder="Number of items sold",
|
||||||
required=True,
|
required=True,
|
||||||
|
value=default_quantity or "",
|
||||||
),
|
),
|
||||||
# Total price in cents
|
# Total price in cents
|
||||||
LabelInput(
|
LabelInput(
|
||||||
@@ -326,6 +360,7 @@ def sell_form(
|
|||||||
step="1",
|
step="1",
|
||||||
placeholder="Total price in cents",
|
placeholder="Total price in cents",
|
||||||
required=True,
|
required=True,
|
||||||
|
value=default_total_price_cents or "",
|
||||||
),
|
),
|
||||||
# Optional buyer
|
# Optional buyer
|
||||||
LabelInput(
|
LabelInput(
|
||||||
@@ -334,6 +369,7 @@ def sell_form(
|
|||||||
name="buyer",
|
name="buyer",
|
||||||
type="text",
|
type="text",
|
||||||
placeholder="Optional buyer name",
|
placeholder="Optional buyer name",
|
||||||
|
value=default_buyer or "",
|
||||||
),
|
),
|
||||||
# Optional notes
|
# Optional notes
|
||||||
LabelTextArea(
|
LabelTextArea(
|
||||||
@@ -341,6 +377,7 @@ def sell_form(
|
|||||||
id="sell_notes",
|
id="sell_notes",
|
||||||
name="notes",
|
name="notes",
|
||||||
placeholder="Optional notes",
|
placeholder="Optional notes",
|
||||||
|
value=default_notes or "",
|
||||||
),
|
),
|
||||||
# Event datetime picker (for backdating)
|
# Event datetime picker (for backdating)
|
||||||
event_datetime_field("sell_datetime"),
|
event_datetime_field("sell_datetime"),
|
||||||
|
|||||||
Reference in New Issue
Block a user