- HTML 66.1%
- Python 26.8%
- JavaScript 3.8%
- CSS 3.1%
- Makefile 0.1%
- Other 0.1%
|
All checks were successful
Deploy / env (push) Successful in 4s
Deploy / pre-commit (push) Successful in 1m13s
Deploy / js-tests (push) Successful in 12s
Deploy / build-static (push) Successful in 20s
Deploy / security (push) Successful in 20s
Deploy / audit (push) Successful in 13s
Deploy / tests (push) Successful in 38s
Deploy / e2e (push) Successful in 1m22s
Deploy / migrations (push) Successful in 29s
Deploy / deploy (push) Successful in 2m1s
Reviewed-on: #6 |
||
|---|---|---|
| .forgejo | ||
| apps | ||
| config | ||
| locale | ||
| scripts | ||
| static | ||
| templates | ||
| tests | ||
| .djlintrc | ||
| .dockerignore | ||
| .env.ci | ||
| .env.example | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| .stylelintrc.json | ||
| docker-compose.prod.yaml | ||
| docker-compose.test.yaml | ||
| Dockerfile | ||
| eslint.config.js | ||
| lighthouserc.js | ||
| Makefile | ||
| manage.py | ||
| pyproject.toml | ||
| README.es.md | ||
| README.md | ||
| README.ru.md | ||
| uv.lock | ||
SA Shop
Language / Язык / Idioma: English | Русский | Español
A full-stack, multilingual e-commerce application built with Django. SA Shop covers the entire purchase flow — browsing a catalog, a session-based cart, checkout with a shipping address, online payments via Stripe and YooKassa, webhook-driven order confirmation, asynchronous email and PDF invoices, plus accounts with email verification and product reviews. Provider keys and view-layer tunables live in editable database singletons, so an administrator can reconfigure the running site without a redeploy.
Table of Contents
- Features
- Tech Stack
- Getting Started
- Environment Variables
- Make Commands
- Project Structure
- Runtime Settings (Database Singletons)
- Internationalization
- Testing
- CI/CD
- License
Features
- Product catalog — hierarchical, self-referential categories, product detail pages, discounts, and per-product stock quantities: an "in stock" manager hides products with zero quantity, and stock is reserved (decremented) atomically at checkout to prevent overselling
- Search — search products by title, browse by category
- Session cart — add, update, and remove items asynchronously (a shared
fetchhelper with in-place DOM updates, no full page reload); stale/unavailable items are pruned automatically - Checkout & orders — shipping address capture, order creation, order history, and per-order detail pages
- Payments — two interchangeable gateways: Stripe Checkout and YooKassa, with payment confirmation handled through signed/IP-verified webhooks
- Async side effects — order-confirmation emails are sent through Celery with automatic retry/back-off; PDF invoices are generated with WeasyPrint from the admin
- Accounts — registration with email verification, login/logout, a profile dashboard, account deletion, and the full password-reset flow
- Reviews & ratings — one review per user per product, 1–5 star ratings
- Full multilingual support — UI strings and catalog content (category names, product titles/descriptions) translated into English, Russian, and Spanish, served from locale-prefixed URLs
- Admin-configurable runtime settings — Stripe / YooKassa / email credentials and view tunables are stored as editable DB singletons; no redeploy needed to rotate keys
- Production hardening — a
Content-Security-Policyheader on every response, plus HSTS, secure cookies, SSL redirect, and a strict referrer policy enabled automatically whenDEBUG=False - Asset pipeline —
django-compressorfor CSS bundling anddjango-google-fontsfor self-hosted fonts
Tech Stack
| Layer | Technology |
|---|---|
| Framework | Django 6.0 |
| Language | Python 3.12+ |
| Database | PostgreSQL (psycopg 3) |
| Cache / sessions | Redis (optional; falls back to in-memory cache + DB sessions) |
| Task queue | Celery + Redis broker, django-celery-results, Flower |
| Payments | Stripe, YooKassa |
| Frontend | Django Templates + HTMX (server-rendered, progressive enhancement) |
| Translations | django-modeltranslation + gettext (.po/.mo) |
| PDF generation | WeasyPrint |
| Asset pipeline | django-compressor, django-google-fonts |
| App server | Gunicorn |
| Dependency manager | uv |
| Testing | pytest, pytest-django, Playwright, VCR.py, factory-boy, node --test |
| Lint / format | Black, isort, Ruff, flake8, djLint, stylelint, ESLint (via pre-commit) |
| Containerization | Docker + docker-compose |
| CI/CD | Forgejo Actions |
Getting Started
Prerequisites
- Python 3.12+
- uv (dependency & virtualenv manager)
- PostgreSQL 17 (or Docker, to run it in a container)
- Redis (optional — used for caching, sessions, and as the Celery broker)
- Node.js 22 (only for JS tests and front-end linting)
- WeasyPrint system libraries for PDF rendering (
pango,cairo,gdk-pixbuf,gobject-introspection); see theDockerfilefor the exact apt packages
Installation
git clone https://git.smekhov-alex.com/SmehAlex/sa_shop
cd sa_shop
uv sync
Configuration
Copy the example environment file and fill in the values (see Environment Variables):
cp .env.example .env
Database, translations, and run
uv run python scripts/compile_po.py # compile .po catalogs into .mo
uv run python manage.py migrate # apply migrations
uv run python manage.py runserver # or: make runserver
The site mounts under the path prefix defined by BASE_URL (e.g. /shop/...). With BASE_URL=shop, the product list is at http://127.0.0.1:8000/shop/shop/.
Docker
A production-style image is defined in Dockerfile (installs system deps, syncs with uv, compiles translations). docker-compose.prod.yaml wires the shop (Gunicorn) and celery (worker + beat) services, attaching to the shared redis on the sa_server_net network.
Environment Variables
Configuration is read from .env via django-environ. See .env.example.
| Variable | Purpose |
|---|---|
DEBUG |
Debug mode; production hardening is enabled when False |
BASE_URL |
Path prefix the whole app is mounted under (also used to build static URLs) |
CSRF_TRUSTED |
Comma-separated list of trusted origins for CSRF |
DJANGO_SECRET_KEY |
Django secret key |
DJANGO_ALLOWED_HOSTS |
Comma-separated allowed hosts |
CELERY_BROKER_URL |
Broker URL for Celery (typically Redis) |
PG_NAME / PG_USER / PG_PASSWORD / PG_HOST / PG_PORT |
PostgreSQL connection |
REDIS_URL |
Optional. If set, enables Redis cache + cached-DB sessions; otherwise in-memory cache + DB sessions |
DJANGO_SUPERUSER_* |
Username / email / password used by make superuser |
TEST_DB_* |
Test database connection (used by the pytest config and CI) |
SOURCE_CODE_URL |
Optional. "View source" link shown in the UI |
DB_CONN_MAX_AGE |
Optional. Persistent DB connection lifetime (default 60) |
COMPRESS_ENABLED / COMPRESS_OFFLINE |
Optional. django-compressor toggles |
TRUST_X_FORWARDED_FOR |
Optional. Trust X-Forwarded-For when behind a proxy (required for the YooKassa webhook IP allow-list) |
TRUSTED_PROXY_COUNT |
Optional. Number of trusted proxies in front of the app; selects which X-Forwarded-For entry is treated as the real client IP (default 1) |
LOG_LEVEL |
Optional. Root / django / apps log level (default INFO) |
Note: Stripe, YooKassa, and SMTP credentials are not environment variables — they live in editable database singletons configured from the Django admin (see Runtime Settings).
Make Commands
| Command | Description |
|---|---|
make runserver |
Start the Django development server |
make shell |
Open the Django shell |
make superuser |
Create a superuser from the DJANGO_SUPERUSER_* env vars |
make collect-static |
Collect static files |
make test |
Spin up the test Postgres (Docker), run the pytest suite, tear it down |
make test-js |
Run the JS test suite with node --test |
make test-e2e |
Install Chromium and run the Playwright end-to-end suite |
make venv-sync |
uv sync the virtual environment |
make clean-static / clean-cache / clean-media |
Remove build/cache/media artifacts |
Project Structure
sa_shop/
├── apps/
│ ├── settings/ # Admin-editable runtime config (DB singletons)
│ │ ├── singleton.py # SingletonModel/Manager/Admin base classes
│ │ ├── models.py # Stripe / YooKassa / Email / Views settings
│ │ └── email_backend.py # SMTP backend driven by EmailSettings
│ ├── shop/ # Catalog: Category, Product, search, listings
│ │ ├── translation.py # modeltranslation registration (name/title/description)
│ │ ├── context_processors.py# Cached category tree for the navbar
│ │ └── templatetags/
│ ├── cart/ # Session-based cart (HTMX add/update/delete)
│ ├── account/ # Registration, email verification, profile, password reset
│ ├── payment/ # Checkout, orders, Stripe/YooKassa, PDF invoices
│ │ ├── webhook/ # stripe_webhook.py, yookassa_webhook.py
│ │ └── tasks.py # Celery order-confirmation email
│ └── recommend/ # Product reviews & ratings
├── config/
│ ├── settings/ # Split settings package
│ │ ├── django/ # base, apps, auth, middlewares, templates
│ │ ├── cache.py # Redis-or-locmem cache + session backend
│ │ ├── databases.py / celery.py / email.py / logging.py / modeltranslation.py / site.py
│ │ └── __init__.py # Aggregates all settings modules
│ ├── i18n_urls.py # BASE_URL-aware locale URL prefixing
│ ├── middleware.py # PrefixedLocaleMiddleware
│ ├── urls.py / celery.py / wsgi.py / asgi.py
├── templates/ # Server-rendered templates (base, shop, cart, account, payment…)
├── static/ # CSS / JS / icons / favicon
├── locale/{en,es,ru}/LC_MESSAGES/django.po # Hand-curated translation catalogs
├── scripts/compile_po.py # Custom .po → .mo compiler (no makemessages)
├── tests/ # pytest suites + e2e/ (Playwright) + js/ (node --test)
├── .forgejo/ # Forgejo Actions workflows + composite actions
├── Dockerfile / docker-compose.prod.yaml / docker-compose.test.yaml
├── Makefile
└── pyproject.toml / uv.lock
Runtime Settings (Database Singletons)
Rather than reading payment and email credentials from environment variables at import time, SA Shop stores them in editable database singletons under the apps.settings app. Each is a one-row table backed by SingletonModel / SingletonManager (Model.objects.get() does a get_or_create), editable from the Django admin:
| Singleton | Holds |
|---|---|
StripeSettings |
publishable key, secret key, API version, webhook secret |
YookassaSettings |
shop ID, secret key, USD→RUB rate, test mode, trusted webhook CIDR networks |
EmailSettings |
SMTP host/port/user/password, TLS, from address, verification-link page domain |
ViewsSettings |
default review rating, orders/products per page, CSV export chunk size, category cache TTL, stale-order expiry hours, max item quantity per cart line |
Why it matters:
- An administrator can rotate API keys or change SMTP details without a redeploy.
- Credentials are read at call time, not at module import — there are no
settings.STRIPE_*reads scattered through the codebase. - Each singleton row is created on first access with code defaults (
Model.objects.get()does aget_or_create), then configured entirely from the admin — provider and email credentials never touch.env.
SMTP transport is wired through EMAIL_BACKEND = apps.settings.email_backend.EmailSettingsBackend, which reads EmailSettings per send. Because django-email-verification reads EMAIL_FROM_ADDRESS / EMAIL_PAGE_DOMAIN directly from django.conf.settings at send time, register_user_view applies those values from EmailSettings immediately before dispatching the verification email.
Internationalization
The application is fully localized into English, Russian, and Spanish (LANGUAGE_CODE = "en"), covering both interface strings and catalog content.
Two translation layers:
- Static strings —
gettext/gettext_lazycatalogs inlocale/{en,es,ru}/LC_MESSAGES/django.po. These are hand-curated (the sourcemsgids are mostly the original Russian strings) and compiled to.moby a custom script,scripts/compile_po.py— there is nomakemessagesstep. - Model content —
django-modeltranslationadds per-language columns forCategory.nameandProduct.title/Product.description(registered inapps/shop/translation.py), with English as the default and fallback language.
Locale-prefixed URLs: a custom PrefixedLocaleMiddleware and base_i18n_patterns helper (config/i18n_urls.py) serve each language under a URL prefix beneath BASE_URL, while leaving the default language unprefixed (prefix_default_language=False).
Adding or changing a translated string:
- Wrap the string in
gettext/gettext_lazy. - Append a matching
msgid/msgstrblock to all three.pofiles. - Run
uv run python scripts/compile_po.pyto rebuild the.mofiles (thecompile-popre-commit hook does this automatically on.pochanges).
Testing
Tests run against a dedicated PostgreSQL instance defined in docker-compose.test.yaml; the pytest config uses the tests.config settings module (separate TEST_DB_*, eager Celery, dummy cache, locmem email).
make test # start test DB → run pytest → tear down
make test-e2e # install Chromium → run Playwright e2e suite
make test-js # run node --test JS suite
Test layers:
- Unit & view tests (
tests/apps/) — models, forms, views, context processors, admin, Celery tasks - Integration tests (
@pytest.mark.integration) — Stripe / YooKassa calls replayed from VCR.py cassettes (tests/apps/payment/cassettes/) - Snapshot tests (
tests/snapshot/) — rendered HTML and email templates frozen per language (en/ru/es) - End-to-end (
tests/e2e/) — full browser flows with Playwright (auth, cart, search, shipping, i18n); excluded from the default pytest run - JavaScript (
tests/js/) — front-end behavior via the built-in Node test runner
Fixtures and factory-boy factories live in tests/fixtures/ and tests/factories/.
CI/CD
Continuous integration runs on Forgejo Actions. Each workflow reads test DB credentials from .env.ci, and most spin up a postgres:17 service container. Reusable steps are factored into composite actions under .forgejo/actions/.
| Workflow | Trigger | What it does |
|---|---|---|
tests.yml |
pull request | Sync deps, compile translations, run the full pytest suite, plus a security job (manage.py check --deploy) |
audit.yml |
pull request / manual | Audit the locked production dependencies for known vulnerabilities (pip-audit over the exported lock); accepted findings without an upstream fix are listed in .forgejo/pip-audit-ignore.txt |
migrations.yml |
pull request | makemigrations --check (no missing migrations) + apply to a fresh DB |
e2e.yml |
pull request / manual | Install Chromium with deps, run the Playwright e2e suite |
js-tests.yml |
pull request / manual | Run the node --test JS suite |
lighthouse.yml |
pull request / manual | Boot the app with seeded demo data, run Lighthouse CI against thresholds |
pre-commit.yml |
pull request | Run all pre-commit hooks (Black, isort, Ruff, flake8, djLint, stylelint, ESLint, compile-po) over the whole tree |
deploy.yml |
push to main / manual |
Re-run the full PR check suite (pre-commit, js, tests, e2e, migrations, build-static, security, audit), then build the image, migrate, collect & compress static, and restart the prod containers |
License
This project is open-source. Source code is available at git.smekhov-alex.com/SmehAlex/sa_shop.