{"id":34502716,"url":"https://github.com/ruslanways/lifejournal-v2","last_synced_at":"2026-04-09T08:04:18.560Z","repository":{"id":329335782,"uuid":"1118534421","full_name":"ruslanways/lifejournal-v2","owner":"ruslanways","description":"Instagram-like Django app with ASGI, Celery, Redis, Docker","archived":false,"fork":false,"pushed_at":"2025-12-19T15:21:06.000Z","size":52,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-21T22:22:29.381Z","etag":null,"topics":["asgi","celery","django","docker","postgresql","redis","rest-api","uvicorn"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ruslanways.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-17T22:49:20.000Z","updated_at":"2025-12-19T14:57:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ruslanways/lifejournal-v2","commit_stats":null,"previous_names":["ruslanways/lifejournal-v2"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ruslanways/lifejournal-v2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ruslanways%2Flifejournal-v2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ruslanways%2Flifejournal-v2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ruslanways%2Flifejournal-v2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ruslanways%2Flifejournal-v2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ruslanways","download_url":"https://codeload.github.com/ruslanways/lifejournal-v2/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ruslanways%2Flifejournal-v2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27992999,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-24T02:00:07.193Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asgi","celery","django","docker","postgresql","redis","rest-api","uvicorn"],"created_at":"2025-12-24T02:19:36.407Z","updated_at":"2026-04-09T08:04:18.554Z","avatar_url":"https://github.com/ruslanways.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LifeJournal v2\n\nInstagram-like Django application for sharing posts with images, titles, descriptions, and likes.\n\n**Tech Stack:** Django · Django REST Framework · Uvicorn (ASGI) · PostgreSQL · Redis · Celery · Whitenoise · S3\n\n---\n\n## Requirements\n\n- **Docker** + **Docker Compose** plugin (`docker compose version`)\n- **`uv`** (optional, for local tooling)\n\n---\n\n## Project Structure\n\n```\nconfig/          # Settings (base/dev/prod), URLs, ASGI configuration, Celery app\napps/\n  ├── users/     # Custom user model with bio, avatar, website fields\n  └── posts/     # Posts application (views, URLs, templates)\ntemplates/       # Project-level templates\n  ├── base.html           # Main base template with navigation and footer\n  └── account/            # Allauth template overrides\n      ├── base.html       # Allauth base template wrapper\n      ├── login.html      # Custom login page\n      ├── signup.html     # Custom signup page\n      └── ...             # Other allauth pages (logout, password reset, etc.)\nstatic/          # Source static files (CSS, JS, images)\n  └── css/\n      └── main.css        # Main stylesheet with modern color scheme\napps/posts/templates/posts/  # App-specific templates\n  └── home.html          # Home page template\ncompose.dev.yml  # Development stack configuration (uses dev target)\ncompose.prod.yml # Production stack configuration (uses prod target)\nDockerfile       # Multi-stage build with dev/prod targets, virtual environment in /opt/venv\n```\n\n---\n\n## Authentication System\n\n### Overview\n\nThe application uses **django-allauth** for authentication, providing:\n- Email-based registration with mandatory verification\n- Login with **both email and username**\n- Password reset via email\n- Email management (add, remove, verify multiple emails)\n- Ready for social authentication (Google, GitHub, etc.)\n\n### Custom User Model\n\n**Model:** `apps.users.User` (extends `AbstractUser`)\n\n**Additional Fields:**\n- `bio` - Text field (max 500 characters)\n- `avatar` - Image field (uploaded to `media/avatars/`)\n- `website` - URL field (max 200 characters)\n\n**Admin Panel:** Enhanced UserAdmin with custom fields visible at `/admin/`\n\n**Inspecting User Model Fields:**\n\nTo check all fields on your user model, use the Django shell:\n\n```bash\nuv run python manage.py shell\n```\n\nThen in the shell:\n```python\n\u003e\u003e\u003e from apps.users.models import User\n\u003e\u003e\u003e [f.name for f in User._meta.get_fields()]\n```\n\n### Custom Allauth Templates\n\nAll allauth pages use custom templates that inherit from the project's base template, ensuring consistent styling across the entire application:\n\n**Template Overrides** (`templates/account/`):\n- `base.html` - Wraps allauth content in styled card\n- `login.html` - Custom login page\n- `signup.html` - Custom signup page\n- `logout.html` - Logout confirmation\n- `password_reset.html` - Password reset request\n- `password_reset_done.html` - Reset email sent confirmation\n- `password_reset_from_key.html` - Set new password form\n- `password_reset_from_key_done.html` - Password changed confirmation\n- `password_change.html` - Change password (when logged in)\n- `email.html` - Email management\n- `email_confirm.html` - Email confirmation\n- `verified_email_required.html` - Email verification required\n- `account_inactive.html` - Inactive account notice\n- And other allauth pages\n\nAll templates extend `templates/account/base.html`, which extends `templates/base.html`, ensuring:\n- Consistent navigation and footer\n- Unified color scheme and styling\n- Responsive design across all pages\n\n### Authentication URLs\n\nAll authentication URLs are under `/accounts/`:\n\n#### User Registration \u0026 Login\n- `GET/POST /accounts/signup/` - User registration form\n- `GET/POST /accounts/login/` - Login (accepts username OR email)\n- `POST /accounts/logout/` - Logout\n\n#### Email Verification\n- `GET /accounts/confirm-email/\u003ckey\u003e/` - Verify email with key from email\n- `GET/POST /accounts/email/` - Change email address (one email per user)\n\n#### Password Management\n- `GET/POST /accounts/password/change/` - Change password (when logged in)\n- `GET/POST /accounts/password/set/` - Set password (for social accounts)\n- `GET/POST /accounts/password/reset/` - Request password reset via email\n- `GET /accounts/password/reset/done/` - Password reset email sent confirmation\n- `GET/POST /accounts/password/reset/key/\u003cuidb36\u003e-\u003ckey\u003e/` - Password reset form\n- `GET /accounts/password/reset/key/done/` - Password successfully reset\n\n#### Other\n- `GET /accounts/inactive/` - Account inactive notice\n\n### Email Verification Flow\n\n1. User submits registration form at `/accounts/signup/`\n2. Account created but **inactive** until email verified\n3. Verification email sent with unique link\n4. User clicks link → account activated\n5. User can now login at `/accounts/login/`\n\n**Development:** Emails appear in Docker container logs (console backend):\n```bash\ndocker compose -f compose.dev.yml logs -f web\n```\n\n**Production:** Configure SMTP settings in `config/settings/prod.py`\n\n### Testing Authentication\n\n#### Create Test User\n```bash\n# Via signup form\nOpen: http://localhost:8000/accounts/signup/\n\n# Via command line\ndocker compose -f compose.dev.yml exec web python manage.py createsuperuser\n```\n\n#### Login Options\nBoth methods work:\n- **Email:** `user@example.com` + password\n- **Username:** `username` + password\n\n\n### Settings Configuration\n\n**Key Settings** (`config/settings/base.py`):\n```python\nAUTH_USER_MODEL = \"users.User\"                      # Custom user model\nACCOUNT_LOGIN_METHODS = {'email', 'username'}       # Allow login with both\nACCOUNT_SIGNUP_FIELDS = ['email*', 'username*', 'password1*', 'password2*']  # Required fields (* = required)\nACCOUNT_EMAIL_VERIFICATION = \"mandatory\"            # Must verify email\nACCOUNT_UNIQUE_EMAIL = True                         # One email per user\nACCOUNT_MAX_EMAIL_ADDRESSES = 1                     # Limit to one email\n```\n\n### Future Enhancements\n\n**Phase 2:** REST API with JWT tokens\n- Add `dj-rest-auth` for API endpoints\n- JWT authentication for SPAs and mobile apps\n- Token refresh and rotation\n\n**Phase 3:** Two-Factor Authentication\n- TOTP (Google Authenticator, Authy)\n- Email-based 2FA\n- WebAuthn/Security keys\n\n---\n\n## Development\n\n### 1. Environment Setup\n\nCreate a `.env` file in the project root (gitignored).\n\n**Minimal example:**\n\n```bash\nDJANGO_SETTINGS_MODULE=config.settings.dev\nDEBUG=True\nSECRET_KEY=dev-insecure-change-me\nALLOWED_HOSTS=localhost,127.0.0.1\n\nDATABASE_URL=postgresql://app:app@db:5432/app\nREDIS_URL=redis://redis:6379/0\n```\n\n### 2. Start Development Stack\n\n```bash\ndocker compose -f compose.dev.yml up --build\n```\n\n**What this starts:**\n- **`web`** — Development container with dev dependencies (pytest, etc.), runs `migrate`, `collectstatic`, then starts Uvicorn with `--reload`\n- **`db`** — PostgreSQL 16 with healthcheck\n- **`redis`** — Redis with healthcheck\n- **`celery_worker`** — Celery worker for background tasks (with dev dependencies)\n- **`celery_beat`** — Celery beat scheduler for periodic tasks (with dev dependencies)\n\n**Note:** The development containers use the `dev` target from the Dockerfile, which includes all development dependencies (pytest, pytest-django, etc.) for running tests inside the container.\n\n**Access the application:**\n- Home page: http://localhost:8000/\n- Django Admin: http://localhost:8000/admin/\n- User Login: http://localhost:8000/accounts/login/\n- User Signup: http://localhost:8000/accounts/signup/\n\n### 3. Management Commands\n\nRun Django management commands inside the `web` container:\n\n```bash\n# Create a superuser\ndocker compose -f compose.dev.yml exec web python manage.py createsuperuser\n\n# Open Django shell\ndocker compose -f compose.dev.yml exec web python manage.py shell\n\n# Run any other management command\ndocker compose -f compose.dev.yml exec web python manage.py \u003ccommand\u003e\n```\n\n### 4. View Logs\n\nView logs for specific services:\n\n```bash\n# Web service logs\ndocker compose -f compose.dev.yml logs -f web\n\n# Celery worker logs\ndocker compose -f compose.dev.yml logs -f celery_worker\n\n# Celery beat logs\ndocker compose -f compose.dev.yml logs -f celery_beat\n\n# All services\ndocker compose -f compose.dev.yml logs -f\n```\n\n### 5. Reset Development Database\n\n⚠️ **Warning:** This deletes the PostgreSQL volume and all data.\n\n```bash\ndocker compose -f compose.dev.yml down -v\ndocker compose -f compose.dev.yml up --build\n```\n\n---\n\n## Testing\n\n### Test Structure\n\nThe project uses **pytest** with a well-organized test structure:\n\n```\napps/\n  └── users/\n      └── tests/                      # App-specific unit and integration tests\n          ├── conftest.py            # Registers factories with pytest-factoryboy\n          ├── test_login.py          # Login functionality tests\n          ├── test_signup.py         # User registration tests\n          ├── test_logout.py         # Logout functionality tests\n          ├── test_email_verification.py  # Email verification flow tests\n          ├── test_password_reset.py      # Password reset flow tests\n          └── test_protected_pages.py     # Protected page access tests\napps/\n  └── conftest.py                    # Makes global fixtures available to app tests\ntests/                               # End-to-end tests and global fixtures\n  └── conftest.py                    # Global fixtures (authenticated_client, verified_user, etc.)\napps/users/\n  └── factories.py                   # Factory Boy factories (UserFactory, EmailAddressFactory)\n```\n\n**Test Organization:**\n- **`apps/users/tests/`** - User app tests (authentication, registration, email verification, password reset, etc.)\n- **`tests/`** - End-to-end tests and tests that don't belong to any specific app (currently used for global fixtures)\n- **`apps/conftest.py`** - Makes global fixtures from `tests/conftest.py` available to app tests via `pytest_plugins`\n- **`tests/conftest.py`** - Global fixtures (`authenticated_client`, `verified_user`, `unverified_user`, `site`)\n- **`apps/users/tests/conftest.py`** - Registers factories with pytest-factoryboy, making them available as fixtures\n- **`apps/users/factories.py`** - Factory Boy factories for creating test data (`UserFactory`, `EmailAddressFactory`)\n\n**Current Test Coverage:**\n- **Authentication:** Login (username and email), logout, signup\n- **Email Verification:** Verification flow, unverified user handling, expired keys\n- **Password Reset:** Reset request, email sending, password change\n- **Protected Pages:** Login required behavior, authentication redirects\n\n### Testing Packages\n\nThe project uses the following testing packages (installed via `uv`):\n\n- **`pytest`** (\u003e=8.0.0) - Test framework\n- **`pytest-django`** (\u003e=4.8.0) - Django integration for pytest (provides `client`, `django_user_model`, `mailoutbox` fixtures)\n- **`pytest-factoryboy`** (\u003e=2.8.0) - Factory Boy integration for test fixtures (auto-creates fixtures from registered factories)\n- **`pytest-cov`** (\u003e=4.1.0) - Coverage reporting\n- **`pytest-xdist`** (\u003e=3.5.0) - Parallel test execution\n- **`factory-boy`** (via pytest-factoryboy) - Test data generation with factories\n\n**Factory Boy Integration:**\n- Factories are registered in `apps/users/tests/conftest.py` using `register()`\n- Registered factories automatically create fixtures with the factory name (e.g., `UserFactory` → `user` and `user_factory`)\n- Use factories directly in tests or via auto-generated fixtures\n\nInstall development dependencies:\n```bash\nuv sync --extra dev\n```\n\n### Running Tests\n\n#### Running Tests Locally\n\n```bash\n# Run all tests\npytest\n\n# Run tests for a specific app\npytest apps/users\n\n# Run a specific test file\npytest apps/users/tests/test_login.py\n\n# Run a specific test\npytest apps/users/tests/test_login.py::TestLogin::test_login_with_username\n\n# Run end-to-end tests\npytest tests/\n```\n\n#### Running Tests in Docker Container\n\nTests can also be run inside the development container, which ensures a consistent environment:\n\n```bash\n# Run all tests in the container\ndocker compose -f compose.dev.yml exec web pytest\n\n# Run tests for a specific app\ndocker compose -f compose.dev.yml exec web pytest apps/users\n\n# Run a specific test file\ndocker compose -f compose.dev.yml exec web pytest apps/users/tests/test_login.py\n\n# Run with verbose output\ndocker compose -f compose.dev.yml exec web pytest -v\n\n# Run with coverage\ndocker compose -f compose.dev.yml exec web pytest --cov=apps --cov=config --cov-report=term-missing\n```\n\n**Note:** The development container includes all test dependencies (pytest, pytest-django, etc.) via the `dev` Dockerfile target. Tests automatically use `config.settings.test` via the `--ds=config.settings.test` flag in `pyproject.toml`, ensuring consistent test configuration regardless of environment variables.\n\n#### Test Execution Options\n\n```bash\n# Verbose output\npytest -v\n\n# Show print statements\npytest -s\n\n# Stop on first failure\npytest -x\n\n# Run only failed tests from last run\npytest --lf\n\n# Run tests matching a pattern\npytest -k \"login\"\n\n# Run tests in parallel (faster)\npytest -n auto\n\n# Run tests with markers\npytest -m \"not slow\"        # Exclude slow tests\npytest -m integration        # Run only integration tests\n```\n\n#### Test Configuration\n\nTest configuration is in `pyproject.toml`:\n- **Test paths:** `apps/` (app-specific tests)\n- **Settings:** Uses `config.settings.test` (enforced via `--ds=config.settings.test` flag to override environment variables)\n- **Database:** Reuses database between runs (`--reuse-db`) for faster execution\n- **Cache:** Stored in `var/tests/pytest_cache/`\n\n**Test Settings** (`config/settings/test.py`):\n- **Email Backend:** `locmem.EmailBackend` (in-memory, emails available via `mailoutbox` fixture)\n- **Password Hashing:** `MD5PasswordHasher` (faster for tests)\n- **Cache:** `DummyCache` (no caching in tests)\n- **Database:** PostgreSQL (auto-detects Docker vs localhost connection)\n- **Email Verification:** `ACCOUNT_EMAIL_VERIFICATION = \"mandatory\"` (enforced in tests)\n- **Login Redirect:** `LOGIN_REDIRECT_URL = \"/\"` (redirects to home after login)\n\n**Important:** The `--ds=config.settings.test` flag ensures that pytest always uses test settings, even when `DJANGO_SETTINGS_MODULE` is set to a different value (e.g., in Docker containers). This guarantees consistent test behavior across local and containerized environments.\n\n### Coverage Reporting\n\n#### Basic Coverage\n\n```bash\n# Terminal report with missing lines\npytest --cov=apps --cov=config --cov-report=term-missing\n\n# HTML report (opens in browser)\npytest --cov=apps --cov=config --cov-report=html\n# Then open: var/tests/htmlcov/index.html\n\n# Terminal report only\npytest --cov=apps --cov=config --cov-report=term\n```\n\n#### Coverage Options\n\n```bash\n# Coverage for specific app\npytest apps/users --cov=apps.users --cov-report=term-missing\n\n# Coverage with parallel execution\npytest --cov=apps --cov=config --cov-report=term-missing -n auto\n\n# Coverage with minimum threshold (fails if below 80%)\npytest --cov=apps --cov=config --cov-report=term-missing --cov-fail-under=80\n\n# Combine multiple report formats\npytest --cov=apps --cov=config --cov-report=term-missing --cov-report=html\n```\n\n#### Coverage Configuration\n\nCoverage settings are configured in `pyproject.toml`:\n- **Source:** Measures coverage for `apps/` and `config/`\n- **Excludes:** Migrations, test files, settings, conftest files\n- **Output:** Coverage data stored in `var/tests/.coverage`\n- **HTML reports:** Generated in `var/tests/htmlcov/`\n\n### Test Fixtures\n\n#### Global Fixtures (`tests/conftest.py`)\n\nAvailable to all tests:\n- **`client`** - Django test client (provided automatically by pytest-django)\n- **`authenticated_client`** - Authenticated client as regular user (uses `user` fixture)\n- **`verified_user`** - User with verified email (created via `user_factory` and `email_address_factory`)\n- **`unverified_user`** - User with unverified email (created via `user_factory` and `email_address_factory`)\n- **`mailoutbox`** - Access sent emails in tests (provided by pytest-django when using locmem email backend)\n- **`site`** (autouse) - Ensures Site exists (required by allauth)\n\n#### Factory-Based Fixtures (`apps/users/tests/conftest.py`)\n\nThe following fixtures are automatically created by pytest-factoryboy from registered factories:\n- **`user`** - Standard test user (from `UserFactory`, creates: `User` with username, email, password, etc.)\n- **`user_factory`** - Factory for creating custom User instances\n- **`email_address`** - EmailAddress instance (from `EmailAddressFactory`)\n- **`email_address_factory`** - Factory for creating custom EmailAddress instances\n\n**Factories** (`apps/users/factories.py`):\n- **`UserFactory`** - Creates `User` instances with:\n  - Sequential usernames: `user0`, `user1`, etc.\n  - Sequential emails: `user0@example.com`, `user1@example.com`, etc.\n  - Default password: `testpass123` (stored in `DEFAULT_TEST_PASSWORD`)\n  - Faker-generated first_name, last_name, bio\n  - Default: `is_active=True`, `is_staff=False`, `is_superuser=False`\n- **`EmailAddressFactory`** - Creates `EmailAddress` instances with:\n  - Links to User via SubFactory\n  - Email matches user's email by default\n  - Default: `verified=False`, `primary=True`\n\n#### App-Specific Fixtures (`apps/*/tests/conftest.py`)\n\nApp-specific fixtures can be defined in each app's `tests/conftest.py` file. The `apps/users/tests/conftest.py` registers factories for use in tests.\n\n### Example Test\n\n```python\n# apps/users/tests/test_login.py\nimport pytest\nfrom django.urls import reverse\n\nfrom apps.users.factories import DEFAULT_TEST_PASSWORD\n\n@pytest.mark.django_db\nclass TestLogin:\n    \"\"\"Test user login flow.\"\"\"\n\n    def test_login_with_username(self, client, verified_user):\n        \"\"\"Test that login works with username.\"\"\"\n        url = reverse(\"account_login\")\n        data = {\n            \"login\": verified_user.username,\n            \"password\": DEFAULT_TEST_PASSWORD,\n        }\n\n        response = client.post(url, data, follow=True)\n        # Should redirect after successful login\n        assert response.status_code == 200\n        # Check user is authenticated\n        if response.context:\n            assert response.context[\"user\"].is_authenticated\n            assert response.context[\"user\"] == verified_user\n```\n\n**Key Testing Patterns:**\n- Use `@pytest.mark.django_db` decorator for tests that need database access\n- Use `reverse()` to get URLs by name (more maintainable than hardcoded paths)\n- Use `DEFAULT_TEST_PASSWORD` from factories for consistent test passwords\n- Use `follow=True` in `client.post()` to follow redirects and check final page\n- Access `mailoutbox` fixture to verify emails were sent in tests\n\n### Writing Tests\n\n#### Test File Naming\n\nTests are discovered by pytest using these patterns:\n- `test_*.py` - Files starting with `test_`\n- `*_tests.py` - Files ending with `_tests.py`\n- `tests.py` - Files named `tests.py`\n\n#### Test Organization\n\n- **Unit tests** - Test individual functions/methods\n- **Integration tests** - Test interactions between components\n- **End-to-end tests** - Test complete user workflows (in `tests/`)\n\n#### Using Markers\n\nMark tests with markers for selective execution:\n\n```python\nimport pytest\n\n@pytest.mark.slow\ndef test_slow_operation():\n    \"\"\"This test takes a long time.\"\"\"\n    pass\n\n@pytest.mark.integration\ndef test_api_integration():\n    \"\"\"Integration test.\"\"\"\n    pass\n```\n\nRun only non-slow tests:\n```bash\npytest -m \"not slow\"\n```\n\n### Test Database\n\n- Tests use a separate test database (configured in `config/settings/test.py`)\n- Database is reused between runs (`--reuse-db`) for faster execution\n- To force database recreation: `pytest --create-db`\n\n### Troubleshooting Tests\n\n#### Fixtures Not Found\n\nIf you see `fixture 'user' not found`:\n- Ensure `apps/users/tests/conftest.py` exists and registers `UserFactory` using `register(UserFactory)`\n- The `user` fixture is auto-generated by pytest-factoryboy from the registered factory\n\nIf you see `fixture 'authenticated_client' not found` or other global fixtures:\n- Ensure `apps/conftest.py` exists and imports from `tests.conftest` via `pytest_plugins = [\"tests.conftest\"]`\n- Check that `tests/conftest.py` contains the fixture definitions\n\n#### Import Errors\n\nIf tests can't import modules:\n- Ensure you're running tests from the project root\n- Check that `DJANGO_SETTINGS_MODULE` is set correctly (handled by pytest-django)\n\n#### Database Issues\n\nIf you encounter database-related errors:\n- Try recreating the test database: `pytest --create-db`\n- Check that PostgreSQL is running (if using external database)\n\n---\n\n## Production\n\n### Overview\n\n**Important:** The production compose configuration is intentionally \"clean\":\n- No development bind mounts\n- No `--reload` flag\n- Does **not** run migrations or collectstatic on startup\n- Uses the `prod` Dockerfile target (excludes dev dependencies for smaller images)\n\n**Recommended deployment approach:** Deploy on EC2 with RDS for PostgreSQL, use GitHub Actions for CI/CD, and store secrets in AWS Secrets Manager or use IAM roles.\n\n### 1. Environment Variables\n\nIn production, you must provide environment variables via your EC2 environment, secrets manager (recommended), or via a `.env` file present on the server.\n\n**Required variables:**\n\n```bash\nDJANGO_SETTINGS_MODULE=config.settings.prod\nDEBUG=False\nSECRET_KEY=\u003csecure-random-key\u003e\nALLOWED_HOSTS=yourdomain.com,www.yourdomain.com\n\n# External PostgreSQL (RDS)\n# Note: Production compose does NOT run PostgreSQL in Docker\nDATABASE_URL=postgresql://USER:PASSWORD@RDS_HOST:5432/DBNAME\n\nREDIS_URL=redis://redis:6379/0\n\nCSRF_TRUSTED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com\n\n# S3 media storage (if enabled in prod settings)\nAWS_STORAGE_BUCKET_NAME=your-bucket\nAWS_S3_REGION_NAME=eu-central-1\n```\n\n**Production notes:**\n\n- **Authentication:** Prefer **IAM roles** on EC2 instead of storing AWS access keys in environment variables\n- **Static files:** Served by **Whitenoise** from `STATIC_ROOT` inside the Docker image\n- **Media files:** Uploaded media is stored on **S3** in production (when `AWS_STORAGE_BUCKET_NAME` is set)\n- **Database:** `compose.prod.yml` expects PostgreSQL to be external (e.g., AWS RDS), but runs Redis in Docker\n\n### 2. Deploy Production Services\n\n```bash\ndocker compose -f compose.prod.yml up -d --build\n```\n\n**Production services:**\n- **`web`** — Production container (no dev dependencies), Uvicorn ASGI server with 2 workers, exposed internally on port 8000\n- **`celery_worker`** — Production container, Celery worker for background task processing\n- **`celery_beat`** — Production container, Celery beat for scheduled task execution\n- **`redis`** — Redis cache and Celery message broker\n\n**Note:** Production containers use the `prod` target from the Dockerfile, which excludes development dependencies (pytest, etc.) to create smaller, more secure production images.\n\n### 3. Run Database Migrations\n\nMigrations must be run explicitly in production (one-off command):\n\n```bash\ndocker compose -f compose.prod.yml run --rm web python manage.py migrate\n```\n\n### 4. Create Admin User\n\nCreate a superuser account (one-off command):\n\n```bash\ndocker compose -f compose.prod.yml run --rm web python manage.py createsuperuser\n```\n\n### 5. Static Files Handling\n\nStatic files are served from `STATIC_ROOT` via **Whitenoise**. \n\nYou have two options:\n1. Collect static files during image build (bake them into the image)\n2. Collect static files during deployment as a one-off command\n\nIn development, `collectstatic` is run automatically by the web service startup command.\n\nTo collect static files manually in production:\n\n```bash\ndocker compose -f compose.prod.yml run --rm web python manage.py collectstatic --noinput\n```\n\n---\n\n## Templates \u0026 Styling\n\n### Template Structure\n\nThe project uses a hierarchical template structure following Django best practices:\n\n**Project-level templates** (`templates/`):\n- `base.html` - Main base template with navigation, footer, and message display\n- `account/` - Allauth template overrides for consistent styling\n\n**App-level templates** (`apps/posts/templates/posts/`):\n- `home.html` - Home page template\n\n**Template Inheritance:**\n```\nAllauth pages → templates/account/base.html → templates/base.html\nApp pages → templates/base.html\n```\n\n### URL Routing\n\n**Main URLs** (`config/urls.py`):\n- `/` - Home page (includes `apps.posts.urls`)\n- `/admin/` - Django admin\n- `/accounts/` - Allauth authentication URLs\n\n**Posts App URLs** (`apps/posts/urls.py`):\n- `/` - Home page view\n\n### Static Files\n\n**Source Files** (`static/`):\n- CSS files in `static/css/`\n- JavaScript files (when added)\n- Images and other assets\n\n**Configuration:**\n- `STATICFILES_DIRS = [BASE_DIR / \"static\"]` - Source directory\n- `STATIC_ROOT = BASE_DIR / \"staticfiles\"` - Collected files directory\n- `STATIC_URL = \"/static/\"` - URL prefix\n\n**Development:**\n- Static files are automatically served by `django.contrib.staticfiles` in DEBUG mode\n- Files are served directly from `static/` directory\n\n**Production:**\n- Run `collectstatic` to copy all static files to `staticfiles/`\n- Files are served by Whitenoise from `staticfiles/` directory\n\n### Media Files\n\n**Configuration:**\n- `MEDIA_ROOT = BASE_DIR / \"media\"` - Local storage directory\n- `MEDIA_URL = \"/media/\"` - URL prefix\n- User avatars are uploaded to `media/avatars/`\n\n**Automatic Directory Creation:**\n- Media directories are automatically created on Django startup via `apps.users.apps.UsersConfig.ready()`\n- Ensures `media/` and `media/avatars/` exist before file uploads\n\n**Development:**\n- Media files are served by Django's static file serving (when `DEBUG=True`)\n- Files are stored locally in `media/` directory\n\n**Production:**\n- Media files are stored in S3 when `AWS_STORAGE_BUCKET_NAME` is configured\n- Configured in `config/settings/prod.py`\n\n### Styling\n\n**Color Scheme:**\nThe application uses a modern, clean color palette:\n- **Primary:** Indigo (`#6366F1`) for buttons and links\n- **Text:** Dark gray (`#1F2937`) for primary text, medium gray (`#6B7280`) for secondary\n- **Backgrounds:** White and light gray (`#F9FAFB`)\n- **Borders:** Neutral gray (`#E5E7EB`)\n- **Shadows:** Modern layered shadows for depth\n\n**CSS Variables:**\nAll colors are defined as CSS variables in `static/css/main.css` for easy customization.\n\n**Responsive Design:**\n- Mobile-friendly navigation\n- Responsive forms and cards\n- Breakpoints at 768px for tablet/mobile\n\n---\n\n## Technical Notes\n\n### Dockerfile Multi-Stage Build\n\nThe Dockerfile uses a multi-stage build with separate targets for development and production:\n\n- **`builder`** — Base stage that installs production dependencies\n- **`builder-dev`** — Extends `builder` to include development dependencies (pytest, etc.)\n- **`prod`** — Production runtime stage (excludes dev dependencies, collects static files)\n- **`dev`** — Development runtime stage (includes dev dependencies, no static collection)\n\n**Benefits:**\n- Smaller production images (no dev dependencies)\n- Development containers have all tools needed for testing\n- Clear separation between dev and prod environments\n- Both environments share the same base dependencies\n\nThe compose files specify which target to use:\n- `compose.dev.yml` uses `target: dev` for all services\n- `compose.prod.yml` uses `target: prod` for all services\n\n### Why `/opt/venv`?\n\nThe Python virtual environment is installed into `/opt/venv` (not `/app/.venv`) so that development bind mounts (`.:/app`) never hide the installed dependencies. This prevents the common \"No module named django\" error when mounting the local directory into the container.\n\n### Template Resolution Order\n\nDjango searches for templates in this order:\n1. `TEMPLATES['DIRS']` - Project-level templates (`templates/`)\n2. Each app's `templates/` directory (e.g., `apps/posts/templates/`)\n\nThis allows you to:\n- Override third-party templates (like allauth) in project-level `templates/`\n- Keep app-specific templates within each app\n- Maintain a clean separation of concerns\n\n### Static Files vs Media Files\n\n**Static Files:**\n- Files that are part of your codebase (CSS, JS, images bundled with code)\n- Collected by `collectstatic` into `staticfiles/`\n- Served by Whitenoise in production\n- Source files in `static/` directory\n\n**Media Files:**\n- User-uploaded content (avatars, post images, etc.)\n- Stored in `media/` directory (dev) or S3 (prod)\n- Not collected by `collectstatic`\n- Served separately via Django or S3\n\n### Celery Beat Schedule Files\n\nCelery Beat writes runtime state files under `var/celery/` (gitignored). This includes files like `celerybeat-schedule*` which track the schedule execution state. These files persist between container restarts when using volumes.\n\n---\n\n## Troubleshooting\n\n### \"Connection refused\" to PostgreSQL during startup\n\n**Problem:** PostgreSQL may take a moment to become ready after the container starts.\n\n**Solution:** The `compose.dev.yml` file includes healthchecks and dependency conditions (`depends_on` with `condition: service_healthy`) to reduce race conditions. If you still experience issues, wait a few seconds and try again, or check the database logs:\n\n```bash\ndocker compose -f compose.dev.yml logs db\n```\n\n### \"No module named django\" inside container\n\n**Problem:** The virtual environment is being hidden by a bind mount.\n\n**Solution:** This repository uses `/opt/venv` instead of `/app/.venv` to avoid this class of issue. If you're still experiencing this problem, ensure you haven't modified the Dockerfile or volume mounts in a way that hides the `/opt/venv` directory.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fruslanways%2Flifejournal-v2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fruslanways%2Flifejournal-v2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fruslanways%2Flifejournal-v2/lists"}