Skip to content
Merged
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

All notable changes to this project will be documented in this file.

## [0.8.2]

### Added

- **URL reversing for Bolt routes** - Include `django_bolt.urls` in your `ROOT_URLCONF` (`path("", include("django_bolt.urls"))`) and Django's `reverse()`, `reverse_lazy()`, and the `{% url %}` template tag resolve Bolt route names. Entries are reverse-only — Bolt still serves the paths in Rust and the registered views never run; path converters, `args`/`kwargs`, `query`, and `fragment` come from Django's own resolver.
- **`name=` on every route decorator** - `@api.get/post/put/patch/delete/head/options`, `@api.websocket`, `@api.view`, `@api.viewset`, and `@action` accept an explicit reverse name. Unnamed routes derive a name verbatim from the Python identifier (function name, or class name for class-based views); viewsets name each route `{base}-{action}` (e.g. `user-list`, `user-partial_update`).
- **Opt-in reverse namespaces** - `BoltAPI(namespace="...")` mirrors Django's `app_name`; namespaced routes reverse as `namespace:name` and resolve only under that namespace.

### Documentation

- **Routing docs** - New "URL names and reversing" section in `routing.md` covering wiring, naming, derived names, namespaces, viewset/`@action` naming, and collision rules; `class-based-views.md` documents the `@action` `name=` parameter and cross-links it.

## [0.8.1]

### Added
Expand Down
5 changes: 5 additions & 0 deletions docs/src/topics/class-based-views.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ class ArticleViewSet(ViewSet):
| `methods` | yes | List of HTTP methods: `["GET"]`, `["POST"]`, etc. |
| `detail` | yes | `True` for instance actions (`/{pk}/action`), `False` for collection actions (`/action`) |
| `path` | no | Custom URL path (defaults to function name) |
| `name` | no | URL-reverse suffix; combined with the viewset base as `{base}-{name}` (see below) |
| `auth` | no | List of authentication backends (overrides class-level `auth`) |
| `guards` | no | List of permission guards (overrides class-level `guards`) |
| `response_model` | no | Response model for serialization |
Expand All @@ -281,6 +282,10 @@ async def some_method_name(self, request, pk: int):
return {"action": "custom-action-name", "article_id": pk}
```

### URL reversing

`view()`, `viewset()`, and `@action` all accept `name=` for URL reversing. A viewset names each route `{base}-{action}` (e.g. `user-list`, `user-recent`). See [URL names and reversing](routing.md#url-names-and-reversing) for the full reference.

### Actions with query parameters

```python
Expand Down
94 changes: 94 additions & 0 deletions docs/src/topics/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,100 @@ async def get_user(user_id: int):
return {"user_id": user_id}
```

## URL names and reversing

Bolt routes live in Rust's router, not Django's URLconf, so Django can't see them by default. Wire them up once and Django's native `reverse()`, `reverse_lazy()`, and the `{% url %}` template tag resolve Bolt route names like any other view.

### Wiring it up

Include `django_bolt.urls` in your project's `ROOT_URLCONF`:

```python
# urls.py
from django.urls import include, path

urlpatterns = [
path("", include("django_bolt.urls")),
# ... your other patterns
]
```

This contributes a **reverse-only** entry for every named Bolt route. The registered views never run (Bolt still serves these paths in Rust) — they exist purely so Django can reverse the names.

### Naming a route

Pass `name=` to any route decorator:

```python
@api.get("/missions/{mission_id}", name="mission-detail")
async def get_mission(mission_id: int):
return {"id": mission_id}
```

```python
from django.urls import reverse

reverse("mission-detail", kwargs={"mission_id": 42}) # "/missions/42"
```

```html
{% url "mission-detail" mission_id=42 %}
```

Path converters and catch-alls come from Django's own resolver, so `args`/`kwargs`, `query`, and `fragment` all work. A Bolt `{name:path}` catch-all reverses through Django's `<path:name>` converter (accepts slashes); other `{name:type}` hints are untyped on reverse, matching the router.

### Derived names

If you omit `name=`, the name is the **verbatim Python identifier** — the function name for routes, the class name for class-based views — with no transformation:

```python
@api.get("/y")
async def get_mission(): # name == "get_mission"
return {}
```

Reversing against a derived name that no longer exists raises Django's usual `NoReverseMatch`, so renames surface immediately. Name a route explicitly whenever you intend to reverse it.

### Namespaces

Namespaces are **opt-in**, like Django's `app_name`. Pass `namespace=` to a `BoltAPI` and its routes reverse as `namespace:name`:

```python
api = BoltAPI(namespace="missions")

@api.get("/missions/{mission_id}", name="detail")
async def get_mission(mission_id: int):
return {}

reverse("missions:detail", kwargs={"mission_id": 42}) # "/missions/42"
```

A namespaced route reverses **only** under its namespace; the bare name won't resolve.

### Class-based views and viewsets

`view()`, `viewset()`, and `@action` all accept `name=`. A viewset names each route `{base}-{action}`:

```python
@api.viewset("/users", name="user")
class UserViewSet(ViewSet):
async def list(self, request): ... # name == "user-list"
async def partial_update(self, request): ... # name == "user-partial_update"

@action(["GET"], detail=False)
async def recent(self, request): ... # name == "user-recent"
```

Without `name=`, the base falls back to the verbatim class name (`UserViewSet-list`), so set `name=` on viewsets you intend to reverse.

### Collisions

Names are resolved when the urlpatterns are built:

- Several methods on one path share a name and are deduped (no error).
- An explicit `name=` wins over a derived name with the same key.
- Two **explicit** names mapping to different paths raise `ImproperlyConfigured`.

## Sync handlers

While async handlers are recommended, you can also use synchronous functions:
Expand Down
Loading