Fix entry edit always failing with 'Input should be None'
Two bugs: 1. Python 3.12 name collision in schemas.py: `date: Optional[date] = None` caused get_type_hints() to resolve the `date` type annotation to NoneType (Optional[None]) because the field name shadowed the datetime.date import. All *Update schemas were rejecting any PUT with a valid date. Fixed by aliasing the import: `from datetime import date as Date`. 2. FastAPI validation errors return detail as a list of objects, not a string. Passing that list to new Error() produced "Error: [object Object]". Fixed in api.js to map the detail array to msg strings before throwing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from datetime import date, datetime
|
||||
from datetime import date as Date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -56,18 +56,18 @@ class UserOut(BaseModel):
|
||||
# ── Egg Collections ───────────────────────────────────────────────────────────
|
||||
|
||||
class EggCollectionCreate(BaseModel):
|
||||
date: date
|
||||
date: Date
|
||||
eggs: int = Field(ge=0)
|
||||
notes: Optional[str] = None
|
||||
|
||||
class EggCollectionUpdate(BaseModel):
|
||||
date: Optional[date] = None
|
||||
date: Optional[Date] = None
|
||||
eggs: Optional[int] = Field(default=None, ge=0)
|
||||
notes: Optional[str] = None
|
||||
|
||||
class EggCollectionOut(BaseModel):
|
||||
id: int
|
||||
date: date
|
||||
date: Date
|
||||
eggs: int
|
||||
notes: Optional[str]
|
||||
created_at: datetime
|
||||
@@ -78,18 +78,18 @@ class EggCollectionOut(BaseModel):
|
||||
# ── Flock History ─────────────────────────────────────────────────────────────
|
||||
|
||||
class FlockHistoryCreate(BaseModel):
|
||||
date: date
|
||||
date: Date
|
||||
chicken_count: int = Field(ge=0)
|
||||
notes: Optional[str] = None
|
||||
|
||||
class FlockHistoryUpdate(BaseModel):
|
||||
date: Optional[date] = None
|
||||
date: Optional[Date] = None
|
||||
chicken_count: Optional[int] = Field(default=None, ge=0)
|
||||
notes: Optional[str] = None
|
||||
|
||||
class FlockHistoryOut(BaseModel):
|
||||
id: int
|
||||
date: date
|
||||
date: Date
|
||||
chicken_count: int
|
||||
notes: Optional[str]
|
||||
created_at: datetime
|
||||
@@ -100,20 +100,20 @@ class FlockHistoryOut(BaseModel):
|
||||
# ── Feed Purchases ────────────────────────────────────────────────────────────
|
||||
|
||||
class FeedPurchaseCreate(BaseModel):
|
||||
date: date
|
||||
date: Date
|
||||
bags: Decimal = Field(gt=0, decimal_places=2)
|
||||
price_per_bag: Decimal = Field(gt=0, decimal_places=2)
|
||||
notes: Optional[str] = None
|
||||
|
||||
class FeedPurchaseUpdate(BaseModel):
|
||||
date: Optional[date] = None
|
||||
date: Optional[Date] = None
|
||||
bags: Optional[Decimal] = Field(default=None, gt=0, decimal_places=2)
|
||||
price_per_bag: Optional[Decimal] = Field(default=None, gt=0, decimal_places=2)
|
||||
notes: Optional[str] = None
|
||||
|
||||
class FeedPurchaseOut(BaseModel):
|
||||
id: int
|
||||
date: date
|
||||
date: Date
|
||||
bags: Decimal
|
||||
price_per_bag: Decimal
|
||||
notes: Optional[str]
|
||||
@@ -125,18 +125,18 @@ class FeedPurchaseOut(BaseModel):
|
||||
# ── Other Purchases ───────────────────────────────────────────────────────────
|
||||
|
||||
class OtherPurchaseCreate(BaseModel):
|
||||
date: date
|
||||
date: Date
|
||||
total: Decimal = Field(gt=0, decimal_places=2)
|
||||
notes: Optional[str] = None
|
||||
|
||||
class OtherPurchaseUpdate(BaseModel):
|
||||
date: Optional[date] = None
|
||||
date: Optional[Date] = None
|
||||
total: Optional[Decimal] = Field(default=None, gt=0, decimal_places=2)
|
||||
notes: Optional[str] = None
|
||||
|
||||
class OtherPurchaseOut(BaseModel):
|
||||
id: int
|
||||
date: date
|
||||
date: Date
|
||||
total: Decimal
|
||||
notes: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
@@ -13,7 +13,10 @@ const API = {
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ detail: res.statusText }));
|
||||
throw new Error(err.detail || `Request failed (${res.status})`);
|
||||
const detail = Array.isArray(err.detail)
|
||||
? err.detail.map(e => e.msg).join(', ')
|
||||
: err.detail;
|
||||
throw new Error(detail || `Request failed (${res.status})`);
|
||||
}
|
||||
if (res.status === 204) return null; // DELETE returns No Content
|
||||
return res.json();
|
||||
|
||||
Reference in New Issue
Block a user