Metadata-Version: 2.4
Name: portfolio-package
Version: 0.1.0
Summary: Portfolio site package for the Core boilerplate (projects, skills, education, certifications, services, social links). Depends on core-package for auth, RBAC, and the shared User model — it does not define its own.
Author-email: puzzllium <puzzllium@gmail.com>
Maintainer-email: puzzllium <puzzllium@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/puzzelchannel/portfolio-package
Project-URL: Issues, https://github.com/puzzelchannel/portfolio-package/issues
Classifier: Framework :: Django
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: core-package
Requires-Dist: blog-package
Requires-Dist: django-ninja>=1.7.0
Requires-Dist: django-unfold>=0.81.0
Requires-Dist: Pillow>=10.0
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-django>=4.14; extra == "dev"
Requires-Dist: pytest-asyncio>=1.4; extra == "dev"
Dynamic: license-file

# portfolio-package

A portfolio site package for the [Core](../../Core) boilerplate: projects
(with an image gallery), skills, education, certifications, services, social
links, and a `Profile` (the About/hero content — headline, tagline, bio,
stat-box numbers). It depends on [core-package](https://pypi.org/project/core-package/) for auth, RBAC,
and the shared `User` model — **it never defines its own user/account
model**; every record here is owned by a Core `User` via
`settings.AUTH_USER_MODEL`. A user's *name* and *profile picture* live on
Core's own `User` model (`first_name`/`last_name`/`avatar`), not here.

Also depends on `blog-package` — this site's About/portfolio pages include a
blog section, so installing `portfolio` installs `blog` too (see "Installing
into a Core-based project" below).

`Project.title`/`summary`/`description` and `Profile.headline`/`tagline`/
`bio` are translatable via `apps.core.models.TranslatableModel` — a single
`translations` JSON field per row (`{"fa": {"title": "...", ...}, "de":
{...}}`), not a `title_en`/`title_fa` column per language. Add a language by
calling `PUT /projects/{id}/translations/{language}` or
`PUT /profile/me/translations/{language}`, no migration required.

See Core's `AGENT.md` §10 ("Package architecture") for the full contract this
package follows.

## Installation

```bash
pip install portfolio-package
```

Requires Python 3.10+ and Django 5.2 (via core-package). This also installs
`blog-package`.

## Local development

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e /path/to/Core/Core   # core-package, editable
pip install -e ".[dev]"
pytest
```

## Installing into a Core-based project

1. Install it into the host repo's venv:
   ```bash
   pip install portfolio-package
   ```
   This also pulls in `blog-package`. You still need to do steps 2-4 for
   **both** `portfolio` and `blog` — Core's "no auto-discovery, explicit
   wiring" convention (§10) means the pip install is automatic, but
   `INSTALLED_APPS`/router-mounting/`migrate` are not; see `blog`'s own
   README for its exact `INSTALLED_APPS`/router lines. For local development
   of either package, install it editable instead
   (`pip install -e ../Packages/portfolio-package`).
2. Add it to `INSTALLED_APPS` in the host's `config/settings/base.py`, under
   the `# site packages` comment (after the local apps block):
   ```python
   # site packages
   "blog",
   "portfolio",
   ```
3. Mount its router in the host's `config/api.py`:
   ```python
   from blog.api import router as blog_router
   from portfolio.api import router as portfolio_router
   ...
   api.add_router("/blog", blog_router)
   api.add_router("/portfolio", portfolio_router)
   ```
   Since both are wildcard-free routers, order relative to the other
   `add_router` calls doesn't matter.
4. Migrate:
   ```bash
   python manage.py migrate
   ```
5. Optional — add a nav entry under `UNFOLD["SIDEBAR"]["navigation"]` in the
   host's `config/settings/base.py` for its admin models (`Project`, `Profile`,
   `Skill`, etc., and `blog`'s `Post`, `Comment`, etc.).

## Endpoints

Mounted at whatever prefix the host chooses (`/portfolio` in the example
above):

- `GET /projects` — paginated list of published projects (public), optional
  `?user_id=` and `?lang=`
- `GET /projects/by-slug/{slug}` — published project detail (public),
  optional `?lang=`
- `GET /profile?user_id=` — a user's About/hero content (public), optional
  `?lang=`
- `GET /skills`, `/services`, `/education`, `/certifications`,
  `/social-links` — public read-only lists, optionally filtered by
  `?user_id=`
- `POST /projects`, `GET /projects/{project_id}`,
  `PATCH /projects/{project_id}`, `DELETE /projects/{project_id}` —
  admin-only (`require_role("admin")`, from Core)
- `PUT /projects/{project_id}/translations/{language}` — admin-only; sets
  the translated `title`/`summary`/`description` for that language
- `PATCH /profile/me` — admin-only; create/update the caller's own `Profile`
- `PUT /profile/me/translations/{language}` — admin-only; sets the
  translated `headline`/`tagline`/`bio` for that language

Skills/education/certifications/services/social-links/project images are
managed through the Django admin (no write API for them yet —
deliberately, to avoid building endpoints nothing needs yet).
