Django 6 + DRF resource server against the Gooyal accounts OAuth2 service, matching the Winsoo ecosystem's conventions. Covers locations, stores, catalog, cart, checkout/orders (with the multi-store-cart split and the status stepper), reviews, and notifications, plus a demo-data seed command. Payment integration (wallet debits, online gateway, seller payouts) is intentionally left out here — see feature/payment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Stack
Django 6 + Django REST Framework on Python 3.13, with GeoDjango (PostGIS) for location/coverage data. Part of the Winsoo ecosystem (~/Projects/Winsoo/) — same conventions as campaign, wallet, advertising, promotions, settlement.
Winofy is a hyperlocal multi-vendor marketplace serving two clients: the customer app (اپ مشتری) and the seller panel (پنل فروشنده).
Local setup
python3.13 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then set SECRET_KEY and DB_USER (postgres role with CREATEDB/superuser or ownership of the DB below)
createdb winofy_dev && psql -d winofy_dev -c "CREATE EXTENSION postgis;"
python manage.py migrate
python manage.py runserver
On macOS, uncomment GDAL_LIBRARY_PATH / GEOS_LIBRARY_PATH in .env (Homebrew paths) — GDAL isn't on the default library search path there.
Common commands
python manage.py runserver
python manage.py startapp <name> # then move it under apps/ and fix apps.py's `name`
python manage.py makemigrations
python manage.py migrate
python manage.py test
python manage.py shell
Settings
Single file: config/settings.py. Config comes from .env via django-environ (env(...)) with a few OAuth2 vars still read through python-decouple's config() — matching the mixed style already in use across the ecosystem. LOGGING is built in config/other_settings/logging.py and imported at the very bottom of config/settings.py (it reads BASE_DIR back off django.conf.settings, which only works once BASE_DIR has already been assigned earlier in the same module — don't move that import up).
Key .env variables (see .env.example):
SECRET_KEY— requiredDB_NAME/DB_USER/DB_PASSWORD/DB_HOST/DB_PORT— Postgres+PostGIS connectionREDIS_URL— used for caching outbound OAuth2 client-credentials tokensALLOWED_HOSTS,CORS_ALLOWED_ORIGINS,CSRF_TRUSTED_ORIGINS— comma-separated listsOAUTH2_PROVIDER_*— Gooyal accounts OAuth2 resource-server + this app's own client credentials (see below)DEFAULT_COMMISSION_PERCENT— platform commission on delivered orders (overridable perStore)
Authentication — Gooyal accounts, resource-server pattern
This service does not implement login/OTP/password endpoints. Every Winsoo service is an OAuth2 resource server: client apps authenticate directly against the central Gooyal accounts service's /oauth2/token/ and pass the resulting bearer token to every microservice, including this one. apps/gooyal_oauth2 (copied verbatim from the ecosystem) introspects incoming tokens against Gooyal's /introspect/ endpoint and lazily creates a local shadow apps.users.User row (UUID PK = the Gooyal user's UUID) — see apps/gooyal_oauth2/validators.py::OAuth2Validator.
For calls this service makes outward to other Gooyal services (currently just accounts), it authenticates itself via its own client_credentials grant, cached in Redis — see utils/accounts_client.py.
No payment integration on this branch. Wallet debits, online-gateway charges, seller payouts/withdrawals, and the
apps.paymentsapp all live onfeature/payment, not here.Order.commission_amount/seller_payout_amountare still computed at checkout time (pure numbers, no external call), but nothing actually moves money yet —OrderGroup.payment_statusjust stayspendingregardless of the chosenpayment_method. Seefeature/paymentforutils/wallet_client.py,utils/ipg_client.py, and the vendoredutils/clients/gooyal_wallet_client/SDK.
App layout
Domain apps live under apps/; apps.py's name must be the dotted path including the apps. prefix (e.g. apps.stores) to match INSTALLED_APPS. Each app follows: models.py, admin.py, serializers.py, views.py (GenericViewSet + explicit actions, not ModelViewSet), urls.py (DefaultRouter), tests/. All domain models inherit utils.models.BaseModel (UUID PK + created_at/updated_at).
apps/orders is intentionally the biggest app — Cart/CartItem and Notification live there too rather than in their own apps. Cart is just pre-order state (it's cleared into Orders by services.checkout()), and every Notification.type this app fires (new_order, order_cancelled, settlement_done, new_review) is order-triggered, so both would've been single-model apps whose only real dependency was orders anyway. reviews stays separate since it touches orders/stores/catalog about equally and doesn't belong to any one of them. payments (wallet + ipg integration) lives on feature/payment only, for the same "earns its own boundary" reasoning — see above.
Seller-panel-only endpoints are gated with apps.core.permissions.IsStoreOwner (checks request.user.store exists — one seller owns exactly one Store).
Errors
utils.exceptions.exception_handler (wired as REST_FRAMEWORK['EXCEPTION_HANDLER']) plus utils.exceptions.ErrorMiddleware for non-DRF 500s. User-facing error strings are in Persian; code/comments stay in English. Wraps every response as {success, status_code, status_message, details}.