Metadata-Version: 2.4
Name: core-package
Version: 0.2.0
Summary: Reusable Django/Ninja apps (session-JWT auth, users, RBAC, audit logging, base models, optional Socket.IO) to install into any Django project; the foundation for the blog/portfolio/company/ecommerce site packages.
Author-email: puzzllium <puzzllium@gmail.com>
License-Expression: MIT
Classifier: Framework :: Django
Classifier: Framework :: Django :: 5.2
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django<5.3,>=5.2
Requires-Dist: django-ninja<2,>=1.4
Requires-Dist: asgiref>=3.8
Requires-Dist: email-validator<3,>=2.2
Requires-Dist: argon2-cffi>=23
Requires-Dist: PyJWT<3,>=2.8
Requires-Dist: django-unfold>=0.60
Requires-Dist: Pillow>=10
Requires-Dist: requests<3,>=2.31
Provides-Extra: realtime
Requires-Dist: python-socketio<6,>=5.11; extra == "realtime"
Requires-Dist: python-engineio<5,>=4.9; extra == "realtime"
Provides-Extra: static
Requires-Dist: whitenoise<7,>=6.7; extra == "static"
Provides-Extra: postgres
Requires-Dist: psycopg[binary]<4,>=3.2; extra == "postgres"
Provides-Extra: redis
Requires-Dist: django-redis>=5.4; extra == "redis"
Requires-Dist: redis<9,>=5; extra == "redis"
Provides-Extra: server
Requires-Dist: uvicorn[standard]>=0.30; extra == "server"
Provides-Extra: all
Requires-Dist: core-package[postgres,realtime,redis,server,static]; extra == "all"
Provides-Extra: dev
Requires-Dist: core-package[realtime,static]; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-django>=4.9; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Dynamic: license-file

# core-package

**Author:** puzzllium · [puzzllium@gmail.com](mailto:puzzllium@gmail.com) · [LinkedIn](https://www.linkedin.com/in/puzzllium)

Reusable Django/Ninja apps — session-JWT auth, users, RBAC, audit logging,
base models and optional Socket.IO — that you install into **your own** Django
project. It's the foundation for the site packages (blog, portfolio, company,
ecommerce, ...). It is a package, not a project: there are no settings, urls,
or `manage.py` in it.

## Install

```bash
pip install core-package                 # without Socket.IO
pip install "core-package[realtime]"     # with Socket.IO
pip install "core-package[all]"          # realtime, static, postgres, redis, server
```

Requires Python 3.10+ and Django 5.2.

## Creating a project

core-package is installed into a normal Django project. The steps below build
one from scratch (here `Core/myproject`, next to the `Packages/` folder).

### 1. Folder and virtualenv

```bash
mkdir myproject && cd myproject
python -m venv .venv
source .venv/bin/activate
```

### 2. Install core-package

```bash
pip install "core-package[server]"             # without Socket.IO
pip install "core-package[realtime,server]"    # with Socket.IO
```

`server` installs uvicorn. While working from the local repos, install it
editable instead: `pip install -e "../Packages/core-package[realtime,server]"`.

### 3. Create the Django project

```bash
django-admin startproject config .
```

### 4. `config/settings.py`

```python
from apps.core.defaults import CORE_APPS, CORE_MIDDLEWARE, PASSWORD_HASHERS  # noqa: F401

INSTALLED_APPS = [
    "unfold",  # must come before django.contrib.admin
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    *CORE_APPS,          # ninja, apps.core, apps.events, apps.users, apps.roles
    # "apps.contacts",   # optional: contact form
    # "apps.realtime",   # optional: Socket.IO (needs core-package[realtime])
]

MIDDLEWARE = [
    # ... Django's default middleware, unchanged ...
    *CORE_MIDDLEWARE,    # JSON request log
]

AUTH_USER_MODEL = "users.User"

MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
```

### 5. `config/urls.py`

```python
from django.contrib import admin
from django.urls import path

from apps.core.api import create_api

api = create_api(title="My Site API")

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", api.urls),
]
```

`create_api()` adds session-JWT auth, the shared error format, `GET /health`
and the core routers (`/auth`, `/roles`, `/events`, plus `/contacts` if that app
is installed). Any NinjaAPI keyword (`title`, `version`, `docs_url`, ...) is
passed through.

### 6. Socket.IO (optional)

Skip this step if you don't want sockets; nothing else depends on it.
Otherwise install `core-package[realtime]`, uncomment `"apps.realtime"` in
`INSTALLED_APPS`, and replace `config/asgi.py` with:

```python
import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
django_asgi_app = get_asgi_application()

from apps.realtime.asgi import with_socketio  # noqa: E402

application = with_socketio(django_asgi_app)
```

### 7. Database and admin user

```bash
python manage.py migrate
python manage.py seed_roles        # default "admin" and "user" roles
python manage.py createsuperuser
```

### 8. Run

```bash
uvicorn config.asgi:application --reload   # required for Socket.IO
python manage.py runserver                 # fine without Socket.IO
```

- API docs: `http://localhost:8000/api/docs`
- Admin: `http://localhost:8000/admin/`

### 9. Add site packages (e.g. a company portfolio site)

Site packages are installed the same way and mounted on the API. For a
company site, install `company-package` (it pulls in `portfolio-package` and
`blog-package`), then:

```python
# settings.py: after *CORE_APPS, in this order
    *CORE_APPS,
    "blog",
    "portfolio",
    "company",
```

```python
# urls.py
from blog.api import router as blog_router
from company.api import router as company_router
from portfolio.api import router as portfolio_router

api = create_api(title="My Site API")
api.add_router("/blog", blog_router)
api.add_router("/portfolio", portfolio_router)
api.add_router("/company", company_router)
```

```bash
python manage.py migrate
```

### 10. Frontend on another origin (CORS)

CORS is the host project's job, not the package's:

```bash
pip install django-cors-headers
```

```python
INSTALLED_APPS += ["corsheaders"]
# put "corsheaders.middleware.CorsMiddleware" before CommonMiddleware in MIDDLEWARE
CORS_ALLOWED_ORIGINS = ["http://localhost:3000"]
CSRF_TRUSTED_ORIGINS = ["http://localhost:3000"]
CORS_ALLOW_CREDENTIALS = True   # auth uses the session cookie
```

## Settings

All optional — the package falls back to these defaults
(`apps/core/conf.py`):

| Setting | Default |
| --- | --- |
| `JWT_SIGNING_KEY` | `SECRET_KEY` |
| `JWT_ACCESS_TOKEN_LIFETIME_MINUTES` | `60` |
| `REDIS_URL` | `""` (Socket.IO uses the in-process manager) |
| `CORS_ALLOWED_ORIGINS` | `[]` (Socket.IO allowed origins) |
| `KAVENEGAR_API_KEY` | `""` (OTP codes are logged instead of sent) |
| `KAVENEGAR_OTP_TEMPLATE` | `"verify"` |

For the JSON audit log file, set `LOGGING = build_logging_config(BASE_DIR / "logs")`
from `apps.core.logging`.

## What's inside

| App | Required | Purpose |
| --- | --- | --- |
| `apps.core` | yes | Session-bound JWT auth, permissions, middleware, pagination, throttling, exceptions, audit logging, SMS helper, `create_api()` |
| `apps.users` | yes | User model and user/auth API |
| `apps.roles` | yes | Role-based access control (RBAC) |
| `apps.events` | yes | Audit events model, API and admin |
| `apps.contacts` | no | Contact messages model, API and admin |
| `apps.realtime` | no | Socket.IO server with session/JWT authentication |

## Development

```bash
pip install -e ".[dev]"
pytest          # runs against testproject/ (not shipped in the wheel)
```

## Dependencies

Version ranges are used (this is a library), so it won't conflict with the host project.

**Required**

| Package | Used for |
| --- | --- |
| Django `>=5.2,<5.3` | Web framework |
| django-ninja `>=1.4,<2` | REST API layer |
| asgiref `>=3.8` | Async support |
| email-validator `>=2.2,<3` | Email validation |
| argon2-cffi `>=23` | Password hashing |
| PyJWT `>=2.8,<3` | JWT tokens |
| django-unfold `>=0.60` | Admin theme |
| Pillow `>=10` | Image handling |
| requests `>=2.31,<3` | SMS (Kavenegar) HTTP client |

**Optional extras**

| Extra | Installs | For |
| --- | --- | --- |
| `realtime` | `python-socketio`, `python-engineio` | `apps.realtime` |
| `static` | `whitenoise` | `apps.core.middleware.AsyncWhiteNoiseMiddleware` |
| `postgres` | `psycopg[binary]` | PostgreSQL |
| `redis` | `django-redis`, `redis` | Cache/sessions, multi-worker Socket.IO |
| `server` | `uvicorn[standard]` | ASGI server |
| `all` | all of the above | |

## License

MIT. See [LICENSE](LICENSE).
