• HTML 66.1%
  • Python 26.8%
  • JavaScript 3.8%
  • CSS 3.1%
  • Makefile 0.1%
  • Other 0.1%
Find a file
Smekhov Aleksandr 4d18996ea6
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
Fix and extract audit
Reviewed-on: #6
2026-08-27 15:12:14 +00:00
.forgejo Update audit. 2026-08-27 18:00:48 +03:00
apps Squash migrations. 2026-06-04 02:03:26 +03:00
config Small improvement. 2026-06-04 01:37:40 +03:00
locale Small improvement. 2026-06-04 01:37:40 +03:00
scripts Python code refactoring. 2026-04-23 23:58:54 +03:00
static Fix CSS. 2026-08-16 17:32:24 +03:00
templates Fix CSS. 2026-08-16 17:32:24 +03:00
tests Fix tests. 2026-08-16 18:01:55 +03:00
.djlintrc Compress CSS. 2026-04-19 17:25:28 +03:00
.dockerignore Add deploy workflow. 2026-05-27 01:16:05 +03:00
.env.ci Update env files 2026-05-27 00:04:16 +03:00
.env.example Small improvement. 2026-06-04 01:37:40 +03:00
.gitignore Some improvements. 2026-05-28 01:54:44 +03:00
.pre-commit-config.yaml Refactor tests dir. 2026-04-20 21:04:32 +03:00
.stylelintrc.json HTML + CSS refactoring. 2026-04-20 20:48:07 +03:00
docker-compose.prod.yaml Fix deploy #2. 2026-08-16 17:02:57 +03:00
docker-compose.test.yaml Add tests. 2026-04-17 23:40:41 +03:00
Dockerfile Deploy fixes. 2026-08-16 16:38:54 +03:00
eslint.config.js Add js tests. 2026-04-20 20:59:06 +03:00
lighthouserc.js Fix lighthouse, refactoring HTML and CSS. 2026-04-20 20:00:39 +03:00
Makefile Some improvements. 2026-05-28 01:54:44 +03:00
manage.py Add more ruff rules. 2026-04-03 01:37:34 +03:00
pyproject.toml Fix audit. 2026-08-27 17:31:52 +03:00
README.es.md Fix audit. 2026-08-27 17:31:52 +03:00
README.md Fix audit. 2026-08-27 17:31:52 +03:00
README.ru.md Fix audit. 2026-08-27 17:31:52 +03:00
uv.lock Fix audit. 2026-08-27 17:31:52 +03:00

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

  • 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 fetch helper 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, 15 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-Policy header on every response, plus HSTS, secure cookies, SSL redirect, and a strict referrer policy enabled automatically when DEBUG=False
  • Asset pipelinedjango-compressor for CSS bundling and django-google-fonts for 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 the Dockerfile for 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 a get_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:

  1. Static stringsgettext / gettext_lazy catalogs in locale/{en,es,ru}/LC_MESSAGES/django.po. These are hand-curated (the source msgids are mostly the original Russian strings) and compiled to .mo by a custom script, scripts/compile_po.py — there is no makemessages step.
  2. Model contentdjango-modeltranslation adds per-language columns for Category.name and Product.title / Product.description (registered in apps/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:

  1. Wrap the string in gettext / gettext_lazy.
  2. Append a matching msgid / msgstr block to all three .po files.
  3. Run uv run python scripts/compile_po.py to rebuild the .mo files (the compile-po pre-commit hook does this automatically on .po changes).

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.