admin & data: correct admin action redirect, add SQLite retry, seed_test_data command

This commit is contained in:
2026-08-26 03:22:20 +03:30
parent f322d27e4f
commit 05ab6ffc36
7 changed files with 601 additions and 10 deletions

67
CLAUDE.md Normal file
View File

@@ -0,0 +1,67 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project
Django + DRF backend for Tadrisino, an educational app (Persian/Farsi). Codebase and API responses are in Persian; comments in source are Persian too.
## Commands
```bash
source venv/bin/activate
python manage.py runserver
python manage.py migrate
python manage.py test # all tests
python manage.py test accounts # single app
python manage.py test accounts.tests.SomeTestCase.test_something # single test
```
Local dev requires `DJANGO_DEBUG=1` (enables Swagger auth via `DEV_MASTER_OTP`/`devOtp` in responses). `DEBUG` defaults to `False`. Production also requires `DJANGO_SECRET_KEY`, `DJANGO_ALLOWED_HOSTS` (comma-separated), and `CACHE_URL` (Redis — required whenever `DEBUG=False` since rate limiting needs a shared cache; raises `ImproperlyConfigured` otherwise).
No pytest; uses Django's built-in test runner. No linter/formatter configured.
Docker (`docker compose up --build`) adds a local Loki+Grafana Alloy+Grafana log stack; not a production setup (still `runserver`, not gunicorn). See comments in `docker-compose.yml`, `docker/alloy/config.alloy`, and `LOG_DIR` in `config/settings.py`.
API docs: Swagger `/api/docs/`, Redoc `/api/redoc/`, admin `/admin/`.
## Architecture
**Apps** (each owns its own models, one responsibility):
| app | responsibility | key models |
|---|---|---|
| `accounts` | identity, sessions, OTP auth | `User`, `Profile`, `Session`, `OTPCode` |
| `content` | lessons/curriculum | `Grade`, `Unit`, `Node`, `Lesson`, `Question`, `Choice`, `NodeProgress` |
| `progress` | stats, leaderboard, streaks | `UserStats`, `LeaderboardEntry`, `StreakRecord`, `UserHearts` |
| `ai_tutor` | AI assistant chat | `Conversation`, `Message`, `MessageFeedback` |
| `subscription` | plans | `Plan`, `Subscription` |
| `payment` | orders/transactions (incl. Cafe Bazaar IAP) | `Order`, `Transaction` |
| `discount` | discount codes/offers | `DiscountCode`, `Redemption`, `Offer`, `UserOffer` |
| `notifications` | notifications | `Notification` |
| `affiliate` | referral program | `AffiliateAccount`, `Conversion` |
| `support` | tickets/bug reports | `BugReport`, `Ticket`, `TicketMessage` |
| `family` | parental control | `FamilyLink`, `ParentalSettings`, `WeeklyReportLog` |
| `statuslog` | generic cross-app status-change log | `StatusChangeLog` |
| `campaigns` | messaging campaign storage only, no send logic yet | `MessageCampaign` |
| `config` | project settings, root urls, middleware | — |
**Auth is custom, not Django's.** `accounts.User` is deliberately *not* `AUTH_USER_MODEL` — it's an independent business model. Auth is a stateless signed bearer token (`django.core.signing`, no JWT library) issued/verified in `accounts/authentication.py` (`TadrisinoTokenAuthentication`, header `Authorization: Bearer <token>`), backed by `accounts.Session`. Missing/invalid header → anonymous request, not a hard failure; each view's permission class decides if auth is required.
**Apps are decoupled by convention**: instead of real FKs across app boundaries, code uses loose refs (`user_id`, `object_id` + `label`) so any app can be split out later. Don't add cross-app FKs — follow the existing loose-reference pattern.
**Throttling**: DRF `ScopedRateThrottle` with per-endpoint `throttle_scope` strings, rates centralized in `REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']` in `config/settings.py`. Guest users are throttled by IP (`NUM_PROXIES` setting controls how many XFF hops are trusted — must match real proxy count in prod or IP spoofing/throttle-bypass is possible). Adding a new sensitive endpoint means adding both a `throttle_scope` on the view and a matching rate entry in settings.
**`POST /api/ai/ask`** currently returns a stub; real model integration point is `generate_tutor_answer` in `ai_tutor/views.py`.
**Structured logging**: `config.middleware.APIRequestLoggingMiddleware` (last in `MIDDLEWARE`, closest to the view) logs every `/api/` request as JSON (method/path/status/duration/user, no body/headers/secrets) via `config/log_formatters.py`.
**SQLite** is current DB (`OPTIONS.timeout=20` to tolerate concurrent-write locks, e.g. campaigns tests); README notes Postgres migration is still pending.
**SMS** goes through Kavenegar (`KAVENEGAR_API_KEY`/`KAVENEGAR_SENDER`); failures return explicit 503, not a fake success.
## Known gaps (per README pre-deploy checklist)
- Still on SQLite, not Postgres.
- Throttle rates need periodic review against real traffic.
- `DEV_MASTER_OTP` (`0000`) must stay gated behind `DEBUG` — never enabled in prod.