- Connections dashboard with add/remove flow and background health checks - VK OAuth connection with profile info and health monitoring - Sync configuration page with master toggle and filter summary - Catalog browser with store/group/product tables, filter management, CSV export - Alembic migrations for all new tables - run/read_config.py for shell sync script DB integration - CHANGELOG.md updated for v1.8.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
12 KiB
Catalog Browser with Filter Management & CSV Export
Context
Users need to browse their Evotor catalog (stores, groups, products) in a table view, manage sync whitelist/blacklist rules inline, and export data to CSV.
This feature replaces the separate /sync/stores, /sync/groups, /sync/products pages from the sync-configuration plan. The catalog browser becomes the unified place for both viewing data and managing filter rules.
Data is cached in DB with a refresh mechanism — not fetched live on every page load.
Data Model
Catalog Cache Tables
tablename: "cached_stores"
- id (Integer, PK)
- user_id (Integer, FK users.id CASCADE)
- evotor_id (String 255) # Evotor UUID
- name (String 255)
- address (String 500, nullable)
- fetched_at (DateTime) # when this snapshot was taken
UniqueConstraint: (user_id, evotor_id)
Index: user_id
tablename: "cached_groups"
- id (Integer, PK)
- user_id (Integer, FK users.id CASCADE)
- evotor_id (String 255) # Evotor UUID
- store_evotor_id (String 255) # parent store UUID
- name (String 255)
- fetched_at (DateTime)
UniqueConstraint: (user_id, evotor_id)
Index: (user_id, store_evotor_id)
tablename: "cached_products"
- id (Integer, PK)
- user_id (Integer, FK users.id CASCADE)
- evotor_id (String 255) # Evotor UUID
- store_evotor_id (String 255) # parent store UUID
- group_evotor_id (String 255, nullable) # parent group UUID
- name (String 255)
- price (Numeric(12,2), nullable)
- quantity (Numeric(12,3), nullable)
- measure_name (String 20, nullable)
- article_number (String 100, nullable)
- allow_to_sell (Boolean, nullable)
- fetched_at (DateTime)
UniqueConstraint: (user_id, evotor_id)
Index: (user_id, store_evotor_id, group_evotor_id)
SyncFilter (from sync-configuration plan, unchanged)
tablename: "sync_filters"
- sync_config_id, entity_type, entity_id, entity_name, filter_mode, parent_entity_id
The catalog browser reads from cache tables for display and from sync_filters for the current filter state of each entity.
Cache Refresh
web/evotor_api.py gets a new function:
async def refresh_catalog_cache(user_id: int, access_token: str, db: Session):
"""Fetch all stores, groups, products from Evotor API and upsert into cache tables."""
Triggered by:
- Manual "Обновить" button on the catalog page
- Background job (optional, can reuse health_checker interval or separate setting)
- First visit to catalog if cache is empty
Plan
1. New Models — web/models.py
Add CachedStore, CachedGroup, CachedProduct models as described above.
2. Alembic Migration
Create cached_stores, cached_groups, cached_products tables.
3. Evotor API Helper — web/evotor_api.py
Extend with:
async def fetch_stores(access_token: str) -> list[dict]
async def fetch_groups(access_token: str, store_id: str) -> list[dict]
async def fetch_products(access_token: str, store_id: str) -> list[dict]
async def refresh_catalog_cache(user_id: int, access_token: str, db: Session)
refresh_catalog_cache does:
- Fetch all stores
- For each store, fetch groups and products
- Upsert into cache tables (delete old rows for user, insert fresh)
- Update
fetched_attimestamps
4. Catalog Route — web/routes/catalog.py (new)
GET /catalog — Stores table. Requires auth + Evotor connection.
- Reads
cached_storesfor user - If cache is empty, triggers refresh
- Shows table with columns: Название, Адрес, Статус фильтра, Действия
- Each row shows the store's current
SyncFilterstate (included/excluded/no rule) - Link to drill into groups for each store
- "Обновить каталог" button, "Экспорт CSV" button, back link
GET /catalog/groups?store_id=UUID — Groups table for a store.
- Reads
cached_groupsfiltered bystore_evotor_id - Table columns: Название, Статус фильтра, Кол-во товаров, Действия
- Each row shows group's
SyncFilterstate - Link to drill into products for each group
- "Экспорт CSV" button, back to stores
GET /catalog/products?store_id=UUID&group_id=UUID — Products table for a group.
- Reads
cached_productsfiltered bystore_evotor_idandgroup_evotor_id - Table columns: Название, Артикул, Цена, Кол-во, Ед. изм., В продаже, Статус фильтра, Действия
- Each row shows product's
SyncFilterstate - "Экспорт CSV" button, back to groups
GET /catalog/products?store_id=UUID — All products for a store (no group filter).
- Same table, but shows all products in the store with a "Группа" column added
POST /catalog/filter — Toggle filter for an entity.
- Body:
entity_type,entity_id,entity_name,filter_mode(include/exclude/none),parent_entity_id - Creates, updates, or deletes the
SyncFilterrow - Redirects back to the referring page
POST /catalog/refresh — Manual cache refresh.
- Calls
refresh_catalog_cache() - Redirects back to
/catalog
GET /catalog/export?type=stores|groups|products&store_id=UUID&group_id=UUID — CSV export.
- Reads from cache tables
- Returns
StreamingResponsewithtext/csvcontent type andContent-Disposition: attachment - Filename:
{type}_{date}.csv
5. Templates
web/templates/catalog_stores.html — Stores table:
┌──────────────────────────────────────────────────────────────┐
│ Каталог [Обновить] [Экспорт CSV] │
│ Последнее обновление: 06.03.2026 14:30 │
├──────────────────────────────────────────────────────────────┤
│ │
│ Магазины │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Название │ Адрес │ Фильтр │ │ │
│ ├──────────────────────────────────────────────────────┤ │
│ │ Чайная │ ул. Мира, 1 │ ✓ Вкл │ [→][▼] │ │
│ │ Склад │ — │ ✗ Выкл │ [→][▼] │ │
│ │ Точка 2 │ ул. Мира, 5 │ — Нет │ [→][▼] │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ [→] = перейти к группам │
│ [▼] = dropdown: Включить / Исключить / Убрать правило │
│ │
└──────────────────────────────────────────────────────────────┘
Filter status column shows:
✓ Включено(green badge) — entity has an "include" rule✗ Исключено(red badge) — entity has an "exclude" rule— Нет правила(grey badge) — no filter rule (follows default behavior)
Actions column per row:
- Link icon → drill into children (groups for stores, products for groups)
- Dropdown button with filter actions: "Включить в синхронизацию" / "Исключить из синхронизации" / "Убрать правило". Each is a small POST form to
/catalog/filter.
web/templates/catalog_groups.html — Groups table:
- Breadcrumb: Каталог > {Store name} > Группы
- Same table pattern, columns: Название, Кол-во товаров, Фильтр, Действия
- Drill-down link to products per group
web/templates/catalog_products.html — Products table:
- Breadcrumb: Каталог > {Store name} > {Group name} > Товары
- Columns: Название, Артикул, Цена, Кол-во, Ед. изм., В продаже, Фильтр, Действия
- "В продаже" column: green check / red cross based on
allow_to_sell
All tables use Bootstrap table styling (table table-striped table-hover) with responsive wrapper.
6. CSV Export Format
Stores CSV:
Название,Адрес,ID,Фильтр
Чайная,"ул. Мира, 1",uuid-123,Включено
Groups CSV:
Магазин,Название,ID,Фильтр
Чайная,Белый чай,uuid-456,Включено
Products CSV:
Магазин,Группа,Название,Артикул,Цена,Количество,Ед. измерения,В продаже,ID,Фильтр
Чайная,Белый чай,Бай Му Дань,1005,350.00,180.0,г,Да,uuid-789,Включено
UTF-8 with BOM (\ufeff) for Excel compatibility. Delimiter: comma.
7. Update Sync Configuration Plan
The /sync page links to /catalog instead of separate filter pages:
- "Настроить фильтры" button →
/catalog - Filter summary on
/syncreads fromSyncFiltertable (unchanged) - Remove
/sync/stores,/sync/groups,/sync/productsroutes from sync-configuration plan — replaced by catalog browser
8. Navbar / Navigation
Add "Каталог" link to navbar for logged-in users. Order: Подключения → Каталог → Синхронизация → Личный кабинет → Выход.
9. Register Route — web/main.py
from web.routes import catalog
app.include_router(catalog.router)
Files Summary
| File | Action |
|---|---|
web/models.py |
Modify — add CachedStore, CachedGroup, CachedProduct |
web/evotor_api.py |
Create — API fetch + cache refresh functions |
web/routes/catalog.py |
Create — catalog routes (tables, filter toggle, refresh, CSV export) |
web/templates/catalog_stores.html |
Create — stores table |
web/templates/catalog_groups.html |
Create — groups table |
web/templates/catalog_products.html |
Create — products table |
web/templates/base.html |
Modify — add "Каталог" nav link |
web/main.py |
Modify — register catalog router |
docs/plans/sync-configuration.md |
Update — remove /sync/stores,groups,products; link to /catalog |
| Alembic migration | Create — cache tables |
Verification
- Run
alembic upgrade head - Visit
/catalogwithout Evotor connection → warning to connect first - Connect Evotor, visit
/catalog→ triggers first cache refresh, shows stores table - Click store → shows groups table with group names from Evotor
- Click group → shows products table with full product details
- Toggle filter on a product → badge changes,
SyncFilterrow created in DB - Go to
/sync→ filter summary reflects the change - Click "Экспорт CSV" on products page → downloads CSV, opens correctly in Excel
- Click "Обновить каталог" → re-fetches from Evotor API, updates cache
- Verify breadcrumb navigation works correctly through the hierarchy