Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
# Changelog

## 0.6.0 : 2026-06-23

### Upgrades
- Django 6 and Python 3.14
- Dependency bumps (django-filter, argon2-cffi, black 25)
- Standard PostgreSQL `PG*` environment variables (`PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`)

### Security & bug fixes
- Fixed `BasicBackend` (base64 decode of credentials)
- Added authorization to `UserDetail` (self-or-permission) and corrected the `UserChecker` logic
- `default_permissions` now include `view`/`change` so `core.view_user` exists
- `order_by` allow-list (`Model.ORDERING_FIELDS`) to prevent ordering injection
- Token expiry computed at creation via `TokenManager`
- Added CSRF middleware (API views are `csrf_exempt`) and production security settings (secure cookies, SSL redirect, `ALLOWED_HOSTS` from env)
- `SECRET_KEY` now required (raises `ImproperlyConfigured` if missing)

### Deployment fixes
- `supervisor.conf` uses the project's WSGI module (was hard-coded)
- Aligned `supervisord` config path between Dockerfile and entrypoint
- Fixed `docker-compose.yml` database credentials, volumes and healthcheck

### Documentation
- Markdown-based proposal (IP) system under `docs/proposals/`
- `/ip` skill for quick proposal capture

### Testing
- Django `unittest` test suite covering API views, services and database behaviour (soft-delete, token expiry, managers, ordering)

## 0.5.0 : 2024-14-10

- Remove request from exceptions
Expand Down
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ Simple quickstart for [Django](https://www.djangoproject.com/)-based projects cr
- Dependency management using [poetry](https://python-poetry.org/)
- Multi-environment settings
- E-mail testing using [django-imap-backend](https://github.com/Sibyx/django-imap-backend) in `development` environment
- CRON job management

### Bundled dependencies

- [django_api_forms](https://github.com/Sibyx/django_api_forms): Request validation
- [python-dotenv](https://github.com/theskumar/python-dotenv): `.env` handling
- [porcupine-python](https://github.com/zurek11/porcupine-python): Response serialisation
- [pydantic](https://github.com/pydantic/pydantic): Response serialisation
- [django-imap-backend](https://github.com/Sibyx/django-imap-backend): Custom e-mail backend for simplified testing
- [django-celery-beat](https://github.com/celery/django-celery-beat): CRON jobs

## Usage

Expand All @@ -40,13 +38,18 @@ cookiecutter gh:backbonesk/django-project-template
## Next steps

1. Check `pyproject.toml` and change the `authors` list
2. `cd {{ directory_name }}`
3. `python -m venv venv`
4. `poetry install && poetry update`
5. Remove stuff you don't need (template is feature rich on purpose, it's easier to delete than create)
6. Call `python manage.py makemigrations` and then `python manage.py migrate`
7. You are supposed to create superuser using `python manage.py createsuperuser`
8. When the project is set up, you can call `{baseurl}/status` to check if everything is up and running
9. Take a coffee and celebrate life, you saved a plenty of time!
2. `cd <directory_name>`
3. `poetry install && poetry update`
4. Remove stuff you don't need (template is feature rich on purpose, it's easier to delete than create)
5. Copy `.env.example` to `.env` and fill it in — in particular set a `SECRET_KEY`
(generate one at <https://djecrety.ir/>) and the `PG*` database variables.
**`SECRET_KEY` is required: every `manage.py` command fails without it.**
6. Create the PostgreSQL database matching `PGDATABASE` (e.g. `createdb <name>`)
7. Call `python manage.py makemigrations && python manage.py migrate`
8. Create a superuser: `python manage.py createsuperuser` (prompts for email, name, surname, password)
9. Start the server with `make run` (or `python manage.py runserver 0.0.0.0:8000`), then call
`curl http://localhost:8000/api/v1/status` to check everything is up and running
10. (optional) Run the test suite with `make test`
11. Take a coffee and celebrate life, you saved a plenty of time!
---
Made with ❤️ and ☕️ BACKBONE s.r.o. (c) 2024
Made with ❤️ and ☕️ BACKBONE, s.r.o. (c) 2026
37 changes: 37 additions & 0 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env python
"""
Cookiecutter post-generation hook.

Runs in the generated project directory after rendering. It creates a ``.env``
from ``.env.example`` with a freshly generated ``SECRET_KEY`` so the project is
runnable immediately (every ``manage.py`` command requires ``SECRET_KEY``).
"""
import secrets
from pathlib import Path

# Avoid characters that are awkward in .env values: quotes, '#', '$', whitespace.
SECRET_KEY_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@%^&*(-_=+)"


def generate_secret_key(length: int = 50) -> str:
return "".join(secrets.choice(SECRET_KEY_CHARS) for _ in range(length))


def main() -> None:
example = Path(".env.example")
env = Path(".env")

if not example.exists() or env.exists():
return

content = example.read_text()
# Replace the bare ``SECRET_KEY=`` line with a generated value.
content = content.replace("SECRET_KEY=\n", f"SECRET_KEY={generate_secret_key()}\n", 1)
env.write_text(content)

print("Created .env with a generated SECRET_KEY.")
print("Next: configure the PG* database variables in .env, then run `make migrations`.")


if __name__ == "__main__":
main()
93 changes: 93 additions & 0 deletions {{cookiecutter.directory_name}}/.claude/skills/ip/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
name: ip
description: Use when quickly capturing an intellectual property idea - searches for related proposals, extends existing or creates new
user-invocable: true
argument-hint: "[NNN] <idea description>"
---

# ip — Quick proposal capture

Quickly save an Intellectual Property (IP) proposal to `docs/proposals/posts/` following the
project's proposal system (see `CLAUDE.md` for the full guidelines).

## Argument Parsing

Args format: `[NNN] <description>`

- **`/ip 002 description...`** — NNN provided → go directly to that proposal number (extend if exists, create with that number if not)
- **`/ip description...`** — no number → search by keywords, then extend or create new

Parse logic:
1. Split args on first space
2. If first token matches `^\d{3}$` → `target_number = first token`, `description = rest`
3. Otherwise → `target_number = nil`, `description = all args`

## Paths

All paths are relative to the **current working directory** (the project root). Do not hardcode absolute paths.

- Proposals dir: `docs/proposals/posts/`
- Proposal file: `docs/proposals/posts/IP-{NNN}-{slug}.md` (one file per proposal)
- Template: `docs/proposals/.template.md`
- Index: `docs/proposals/index.md`

## Author

Derive the author from git at runtime; do not hardcode a username. This value is used for
both the frontmatter `author:` field and the Changelog column:

```sh
git config user.name || git config user.email || echo "author"
```

## Workflow

### Step 1: Resolve Target

**If `target_number` provided:**
- Run: `ls docs/proposals/posts/ | grep -iE "^IP-0*{target_number}-"` to find the matching file
- If found → go to **2A (Extend)**
- If not found → go to **2B (Create)** using `target_number` as the IP number

**If no `target_number`:**
- Check description for explicit "create new" / "new proposal" intent → skip search, go directly to **2B (Create)**
- Otherwise: run `ls docs/proposals/posts/` and grep filenames + file content for keywords from description
- If matches found → pick the best match and go to **2A (Extend)** automatically (no confirmation needed)
- If no matches → go to **2B (Create)**

### 2A. Extend Existing Proposal

1. Read the matched `IP-{NNN}-{slug}.md` file
2. Locate and update relevant sections:
- Add to **Implementation Plan** (append new phase/steps as checkboxes)
- Add supporting details to **Problem Statement**, **Proposed Solution**, or other relevant sections
3. Update **Changelog**: `| {today} | {author} | [brief change description] |`
4. Update **Status** if appropriate
5. Use the Edit tool to modify the file
6. Update `docs/proposals/index.md` tracking table if status changed
7. Confirm: `✓ Extended IP-{NNN}: [title]`

### 2B. Create New Proposal

1. Determine IP number:
- If `target_number` provided → use it
- Otherwise: `ls docs/proposals/posts/ | grep -oiE '^IP-[0-9]+' | grep -oE '[0-9]+' | sort -n | tail -1` → increment by 1 (start at 1 if none)
2. Format as zero-padded 3-digit: e.g. `002`
3. Derive slug from description (lowercase, hyphens, max 40 chars)
4. Read template: `docs/proposals/.template.md`
5. Create `docs/proposals/posts/IP-{NNN}-{slug}.md` with:
- Updated frontmatter (`date: {today}`, `author: {author}`, tags)
- Title: `# IP-{NNN}: [Full Title]`
- All template sections filled in
- **Review Questions section** (required for AI-created proposals)
- **Changelog**: `| {today} | {author} | Initial draft |`
6. Update `docs/proposals/index.md` tracking table (add row: IP number linking to the file, title, status, last updated)
7. Confirm: `✓ Created IP-{NNN}: [title]`

## Key Rules

- No time estimates in proposals
- AI-created proposals MUST include a Review Questions section
- Always update the Changelog with `YYYY-MM-DD`, author, change description
- Always update `docs/proposals/index.md`
- Derive the author from git config (see **Author** above)
26 changes: 21 additions & 5 deletions {{cookiecutter.directory_name}}/.env.example
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME={{cookiecutter.project_name}}
DATABASE_USER=postgres
DATABASE_PASSWORD=admin
PGHOST=localhost
PGPORT=5432
PGDATABASE={{cookiecutter.project_name}}
PGUSER=postgres
PGPASSWORD=admin

REDIS_DB=0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a toto nechces dat prec tiez ?


LOG_LEVEL=INFO

SENTRY_DSN=''

INSTANCE_NAME={{cookiecutter.project_name}}

# Comma-separated hostnames served in production, e.g. api.example.com,www.example.com
ALLOWED_HOSTS=''

# SECURITY WARNING: keep this secret and use a unique value per environment.
# Generate using: https://djecrety.ir/
SECRET_KEY=

EMAIL_IMAP_HOST=''

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

za mna by bolo pre uplnost fajn tuto pridat comment, ze tie EMAIL_IMAP sa tykaju iba cisto toho naseho django imap backendu, a pridat sem example aj pre klasicky smtp backend (kedze tie tu uplne chybaju, mozno okrem EMAIL_SENDER_NAME, ak ten sa tam pouziva)

EMAIL_IMAP_USER=''
EMAIL_IMAP_PASSWORD=''
EMAIL_IMAP_MAILBOX=''
EMAIL_IMAP_SSL='true'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EMAIL_IMAP_SSL==true nastavit port na fixnu hodnotu (i.e. 993), a ak je false, tak na fixnu default hodnotu (143), alebo odkial je identifikovane, na akom porte ten imap pocuva, kedze ziadne EMAIL_IMAP_PORT tu nemame ?

EMAIL_SENDER_NAME=''

# SMTP (used by non-development environments)
EMAIL_HOST=''
EMAIL_PORT=''
EMAIL_HOST_USER=''
EMAIL_HOST_PASSWORD=''
EMAIL_USE_TLS='false'
17 changes: 17 additions & 0 deletions {{cookiecutter.directory_name}}/.flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[flake8]
max-line-length = 119
Comment thread
Serbel97 marked this conversation as resolved.
# E203 / W503 conflict with Black's formatting.
extend-ignore = E203, W503
# Re-export packages legitimately import names they don't use locally.
per-file-ignores =
__init__.py:F401
exclude =
.git,
__pycache__,
migrations,
venv,
.venv,
static,
media,
private,
conf
Loading