Metadata-Version: 2.4
Name: django-var-cms
Version: 1.0.1
Summary: A modern, role-based Django CMS registry with rich media previews
Requires-Python: >=3.11
Requires-Dist: django>=5.0
Requires-Dist: pillow>=10.0
Requires-Dist: whitenoise>=6.6
Provides-Extra: dev
Requires-Dist: django-debug-toolbar>=4.0; extra == 'dev'
Provides-Extra: geo
Requires-Dist: django[gis]>=5.0; extra == 'geo'
Provides-Extra: pdf
Requires-Dist: pdf2image>=1.16; extra == 'pdf'
Provides-Extra: tailwind
Requires-Dist: django-tailwind-cli>=2.0; extra == 'tailwind'
Description-Content-Type: text/markdown

# django-var-cms

A modern, fully-customisable CMS registry for Django.  
Think `django-admin` — but yours to control.

---

## Quick Start

```bash
uv add django pillow whitenoise
uv run python manage.py migrate
uv run python manage.py seed_demo
uv run python manage.py runserver
# → http://127.0.0.1:8000/var-cms/
```

### Demo accounts

| Username | Password | Role      | Permissions                        |
|----------|----------|-----------|------------------------------------|
| admin    | admin    | superuser | Full access                        |
| editor   | editor   | editor    | Add + Edit (no delete)             |
| author   | author   | author    | Add + limited field edits          |
| viewer   | viewer   | viewer    | List + View only                   |
| alice    | alice    | viewer    | + delete override via UserPermission |

---

## Registration

Create `var_cms_admin.py` in any Django app — auto-discovered on startup.

```python
from var_cms.registry    import var_cms_site, VarCMSModelAdmin
from var_cms.permissions import RolePermission, UserPermission

class ArticleAdmin(VarCMSModelAdmin):
    # ── List view ─────────────────────────────────────────────────────
    list_display  = ["title", "category__name", "author", "status", "created_at"]
    list_filter   = ["status", "category", "is_featured"]
    search_fields = ["title", "body", "author"]
    list_per_page = 25
    ordering      = ["-created_at"]

    # ── Form ──────────────────────────────────────────────────────────
    readonly_fields  = ["created_at", "updated_at", "view_count"]
    exclude_fields   = ["internal_notes"]

    # ── Role permissions ──────────────────────────────────────────────
    permissions = [
        RolePermission("superuser", add=True,  list=True, view=True, edit=True,  delete=True),
        RolePermission("editor",    add=True,  list=True, view=True, edit=True,  delete=False),
        RolePermission("author",    add=True,  list=True, view=True, edit=True,  delete=False),
        RolePermission("viewer",    add=False, list=True, view=True, edit=False, delete=False),
        UserPermission("alice",     add=True,  list=True, view=True, edit=True,  delete=True),
    ]

    # ── Per-role editable fields ──────────────────────────────────────
    role_editable_fields = {
        "superuser": "__all__",                          # everything
        "editor":    ["title", "body", "status", "category"],
        "author":    ["title", "body", "status"],        # can't touch slug/category
        "*":         [],                                  # all others: read-only
    }

var_cms_site.register(Article, ArticleAdmin)
```

---

## Permissions

### RolePermission — matched by Django group name or "superuser"

```python
RolePermission("editor", add=True, list=True, view=True, edit=True, delete=False)
```

### UserPermission — highest priority, matched by username

```python
UserPermission("alice", add=True, list=True, view=True, edit=True, delete=True)
```

### Overriding permission logic

```python
class ArticleAdmin(VarCMSModelAdmin):
    def has_permission(self, request, action, obj=None):
        if action == "delete" and obj and obj.is_published:
            return False  # nobody can delete published articles
        return super().has_permission(request, action, obj)
```

---

## Media Features

### Image preview + Cropper
- Click any image thumbnail to open the preview modal
- Crop with free/fixed aspect ratio (1:1, 16:9, 4:3, etc.)
- Rotate and flip
- Export as JPEG, PNG, or WebP
- Cropped file saved to `MEDIA_ROOT/crops/`

### Video / Audio player
- Native `<video>` and `<audio>` controls in the modal

### PDF viewer
- Inline `<iframe>` PDF preview

### File conversion (API)
- Images:  JPEG ↔ PNG ↔ WebP ↔ BMP ↔ TIFF
- Audio:   MP3 ↔ WAV ↔ OGG ↔ FLAC ↔ AAC  (requires `ffmpeg`)
- Video:   MP4 ↔ WebM ↔ AVI ↔ MOV        (requires `ffmpeg`)
- PDF→PNG: page-by-page                   (requires `uv add pdf2image`)

```bash
# Install ffmpeg (Linux)
sudo apt install ffmpeg

# PDF support
uv add pdf2image
```

---

## Hooks

```python
class ArticleAdmin(VarCMSModelAdmin):

    def get_queryset(self, request):
        return super().get_queryset(request).filter(site=request.site)

    def save_model(self, request, obj, form, change):
        if not change:
            obj.created_by = request.user
        obj.save()

    def delete_model(self, request, obj):
        obj.is_deleted = True   # soft delete
        obj.save()
```

---

## Supported Field Types

| Type | List | Filter | Form |
|------|------|--------|------|
| CharField / SlugField | ✓ | text | text input |
| TextField | ✓ truncated | text | textarea |
| IntegerField / Decimal | ✓ | min/max | number |
| BooleanField | ✓ icon | yes/no | checkbox |
| DateField / DateTimeField | ✓ | date range | date picker |
| ForeignKey | ✓ | select | select |
| choices | ✓ | select | select |
| ImageField | ✓ thumbnail + crop modal | — | file |
| FileField | ✓ preview link + modal | — | file |
| PointField / PolygonField (GIS) | ✓ geo badge | — | WKT input |

---

## URL Structure

```
/var-cms/                          → Dashboard
/var-cms/{app}/{model}/            → List
/var-cms/{app}/{model}/add/        → Add form
/var-cms/{app}/{model}/{pk}/       → Edit form
/var-cms/{app}/{model}/{pk}/view/  → Detail view (read-only)
/var-cms/{app}/{model}/{pk}/delete/→ Delete confirm
/var-cms/api/media/crop/           → POST: crop image
/var-cms/api/media/convert/        → POST: convert file format
```
