diff --git a/alembic/versions/0fec09fb6ae6_manufacturers_url_nullable.py b/alembic/versions/0fec09fb6ae6_manufacturers_url_nullable.py new file mode 100644 index 0000000..4989e18 --- /dev/null +++ b/alembic/versions/0fec09fb6ae6_manufacturers_url_nullable.py @@ -0,0 +1,37 @@ +"""manufacturers_url_nullable + +Revision ID: 0fec09fb6ae6 +Revises: 9a1c2f6e0b3d +Create Date: 2026-09-04 23:05:18.990806 +""" + +from collections.abc import Sequence + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "0fec09fb6ae6" +down_revision: str | None = "9a1c2f6e0b3d" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + with op.batch_alter_table("manufacturers", schema=None) as batch_op: + batch_op.alter_column( + "url", + existing_type=sa.String(), + nullable=True, + ) + + +def downgrade() -> None: + op.execute("UPDATE manufacturers SET url = '' WHERE url IS NULL") + with op.batch_alter_table("manufacturers", schema=None) as batch_op: + batch_op.alter_column( + "url", + existing_type=sa.String(), + nullable=False, + ) diff --git a/app/crud/manufacturer.py b/app/crud/manufacturer.py index 118b382..a87b2e9 100644 --- a/app/crud/manufacturer.py +++ b/app/crud/manufacturer.py @@ -26,7 +26,7 @@ async def create( db: AsyncSession, *, name: str, - url: str, + url: str | None = None, logo_url: str | None = None, ) -> Manufacturer: m = Manufacturer(name=name, url=url, logo_url=logo_url) diff --git a/app/models/manufacturer.py b/app/models/manufacturer.py index 23f29eb..0637435 100644 --- a/app/models/manufacturer.py +++ b/app/models/manufacturer.py @@ -8,5 +8,5 @@ class Manufacturer(Base): id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(nullable=False) - url: Mapped[str] = mapped_column(nullable=True) + url: Mapped[str | None] = mapped_column(nullable=True) logo_url: Mapped[str | None] = mapped_column(nullable=True, default=None) diff --git a/app/schemas/manufacturer.py b/app/schemas/manufacturer.py index 6ec84a8..e820e18 100644 --- a/app/schemas/manufacturer.py +++ b/app/schemas/manufacturer.py @@ -1,4 +1,10 @@ -from pydantic import BaseModel, ConfigDict, HttpUrl +from pydantic import BaseModel, ConfigDict, HttpUrl, field_validator + + +def _blank_to_none(value: str | None) -> str | None: + if isinstance(value, str) and not value.strip(): + return None + return value class ManufacturerBase(BaseModel): @@ -6,6 +12,10 @@ class ManufacturerBase(BaseModel): url: HttpUrl | None = None logo_url: HttpUrl | None = None + _normalize_url = field_validator("url", "logo_url", mode="before")( + _blank_to_none + ) + class ManufacturerCreate(ManufacturerBase): pass @@ -16,6 +26,10 @@ class ManufacturerUpdate(BaseModel): url: HttpUrl | None = None logo_url: HttpUrl | None = None + _normalize_url = field_validator("url", "logo_url", mode="before")( + _blank_to_none + ) + class ManufacturerRead(ManufacturerBase): model_config = ConfigDict(from_attributes=True) diff --git a/app/web/admin.py b/app/web/admin.py index fc082b0..3750a10 100644 --- a/app/web/admin.py +++ b/app/web/admin.py @@ -728,7 +728,7 @@ async def create_manufacturer( created = await manufacturer_crud.create( db, name=name, - url=url, + url=url.strip() or None, logo_url=logo_url.strip() or None, ) await audit_crud.log( @@ -789,7 +789,11 @@ async def update_manufacturer( m = await manufacturer_crud.get(db, manufacturer_id) if m is None: raise HTTPException(status_code=404, detail="Manufacturer not found") - changes = {"name": name, "url": url, "logo_url": logo_url.strip() or None} + changes = { + "name": name, + "url": url.strip() or None, + "logo_url": logo_url.strip() or None, + } await manufacturer_crud.update(db, m, changes) await audit_crud.log( db,