Skip to content

Commit d8b046e

Browse files
author
Martijn Jacobs
committed
Initial release
0 parents  commit d8b046e

File tree

13 files changed

+745
-0
lines changed

13 files changed

+745
-0
lines changed

.github/workflows/ci.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: CI
2+
on: [push, pull_request]
3+
4+
jobs:
5+
run-linting:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- name: Checkout the repository
9+
uses: actions/checkout@v3
10+
- name: Setup Python 3.13
11+
uses: actions/setup-python@v4
12+
with:
13+
python-version: '3.13'
14+
- name: Bootstrap the project and install all dependencies
15+
run: make
16+
- name: Run all linting
17+
run: make lint
18+
19+
run-tests:
20+
needs: run-linting
21+
runs-on: ubuntu-latest
22+
strategy:
23+
fail-fast: true
24+
matrix:
25+
python-version: ["3.10", "3.11", "3.12", "3.13"]
26+
steps:
27+
- name: Checkout the repository
28+
uses: actions/checkout@v4
29+
- name: Setup Python 3.x
30+
uses: actions/setup-python@v4
31+
with:
32+
python-version: ${{ matrix.python-version }}
33+
- name: Bootstrap the project and install all dependencies
34+
run: make
35+
- name: Show all versions
36+
run: pip list
37+
- name: Run the testsuite
38+
run: make cov
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Publish to pypi test
2+
on: workflow_dispatch
3+
4+
jobs:
5+
build:
6+
name: Build distribution
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
- name: Set up Python
11+
uses: actions/setup-python@v4
12+
with:
13+
python-version: "3.x"
14+
- name: Install build
15+
run: pip install build
16+
- name: Build the distribution
17+
run: make dist
18+
- name: Store the distribution packages
19+
uses: actions/upload-artifact@v4
20+
with:
21+
name: dist
22+
path: dist/
23+
24+
publish-to-pypi-test:
25+
name: Publish distribution to pypi test
26+
needs:
27+
- build
28+
runs-on: ubuntu-latest
29+
environment:
30+
name: pypi
31+
url: https://test.pypi.org/p/crumbles
32+
permissions:
33+
id-token: write # IMPORTANT: mandatory for trusted publishing
34+
steps:
35+
- name: Download the distribution
36+
uses: actions/download-artifact@v4
37+
with:
38+
name: dist
39+
path: dist/
40+
- name: Publish distribution to pypi test
41+
uses: pypa/gh-action-pypi-publish@release/v1
42+
with:
43+
repository-url: https://test.pypi.org/legacy/
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Publish to pypi
2+
on: workflow_dispatch
3+
4+
jobs:
5+
build:
6+
name: Build distribution
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
- name: Set up Python
11+
uses: actions/setup-python@v4
12+
with:
13+
python-version: "3.x"
14+
- name: Install build
15+
run: pip install build
16+
- name: Build the distribution
17+
run: make dist
18+
- name: Store the distribution packages
19+
uses: actions/upload-artifact@v4
20+
with:
21+
name: dist
22+
path: dist/
23+
24+
publish-to-pypi:
25+
name: Publish distribution to pypi
26+
needs:
27+
- build
28+
runs-on: ubuntu-latest
29+
environment:
30+
name: pypi
31+
url: https://pypi.org/p/crumbles
32+
permissions:
33+
id-token: write # IMPORTANT: mandatory for trusted publishing
34+
steps:
35+
- name: Download the distribution
36+
uses: actions/download-artifact@v4
37+
with:
38+
name: dist
39+
path: dist/
40+
- name: Publish distribution to pypi test
41+
uses: pypa/gh-action-pypi-publish@release/v1

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# OSX
2+
.DS_Store
3+
4+
# Python
5+
__pycache__/
6+
*.egg-info/
7+
*.pyc
8+
.ruff_cache/
9+
build/
10+
dist/
11+
.coverage

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2025 Maerteijn, https://www.maerteijn.nl
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

Makefile

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
.PHONY: clean versions install lint test cov format dist publish-release-pypi-test publish_release-pypi
2+
3+
default: clean install
4+
5+
clean:
6+
find . -name '__pycache__' -exec rm -rf {} +
7+
rm -Rf build/
8+
9+
versions:
10+
pip-compile --extra dev --no-emit-index-url --upgrade -o requirements-dev.txt pyproject.toml
11+
12+
install:
13+
pip install -r requirements-dev.txt -e .[dist]
14+
15+
lint:
16+
ruff check src tests
17+
mypy .
18+
19+
test:
20+
pytest
21+
22+
cov:
23+
pytest --cov=crumbles --cov-report=html --cov-report=term
24+
25+
format:
26+
ruff check src tests --fix
27+
ruff format
28+
29+
dist:
30+
pyproject-build .
31+
32+
publish-release-pypi-test: dist
33+
twine upload --repository testpypi dist/*
34+
35+
publish_release-pypi: build_release
36+
twine upload dist/*
37+

README.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# crumbles
2+
A simple library for class based view breadcrumbs for any python web framework.
3+
4+
[![CI](https://github.com/maerteijn/crumbles/actions/workflows/ci.yml/badge.svg)](https://github.com/maerteijn/crumbles/actions/workflows/ci.yml)
5+
![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)
6+
![PyPI version](https://badge.fury.io/py/crumbles.svg?dummy=unused)
7+
![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)
8+
![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg)
9+
![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)
10+
![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)
11+
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
12+
[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
13+
![No Dependencies](https://img.shields.io/badge/no%20dependencies-orange)
14+
15+
## Usage
16+
17+
First define a specific mixin for your framework. For example Django:
18+
```python
19+
from django.urls import reverse
20+
21+
22+
class DjangoCrumblesViewMixin(CrumblesViewMixin):
23+
def url_resolve(self, *args, **kwargs):
24+
return reverse(*args, **kwargs)
25+
```
26+
27+
28+
Use the Mixin in your View classes and define the breadcrumbs:
29+
```python
30+
class MyListView(View, DjangoCrumblesViewMixin):
31+
crumbles = (
32+
CrumbleDefinition(url_name="my-list-view", title="Home"),
33+
)
34+
```
35+
36+
Add a default breadcrumb to the mixin like so:
37+
```python
38+
class DjangoCrumblesViewMixin(CrumblesViewMixin):
39+
def __init__(self):
40+
self.crumbles = (
41+
CrumbleDefinition(url_name="index", title="Home"),
42+
) + type(self).crumbles
43+
44+
def url_resolve(self, *args, **kwargs):
45+
return reverse(*args, **kwargs)
46+
47+
```
48+
49+
50+
51+
You can reuse "parent" crumbles for detail pages and use a callable for
52+
url resolve arguments or for defining the title:
53+
```python
54+
from operator import attrgetter, methodcaller
55+
56+
57+
class MyDetailView(View, DjangoCrumblesViewMixin):
58+
crumbles = MyListView.crumbles + (
59+
CrumbleDefinition(
60+
url_name="my-detail-view",
61+
url_resolve_kwargs={"pk": attrgetter("pk")},
62+
title=methodcaller("__str__"),
63+
),
64+
)
65+
66+
```
67+
68+
The attributes context will be retrieved from `instance.object`. If this is not available it
69+
will default to the View class instance itself. You can set a custom context variable
70+
(including dotted syntakt) by using `crumbles_context_attribute`:
71+
72+
```python
73+
class MyDetailView(View, DjangoCrumblesViewMixin):
74+
crumbles_context_attribute = "my_object.parent"
75+
...
76+
```
77+
78+
Rendering the breadcrumb should be done in a template. An example for Django (with bootstrap):
79+
```jinja2
80+
<nav aria-label="breadcrumb">
81+
<ol class="breadcrumb">
82+
{% for item in view.resolve_crumbles %}
83+
{% if forloop.last %}
84+
<li class="breadcrumb-item active" aria-current="page">{{ item.title }}</li>
85+
{% else %}
86+
<li class="breadcrumb-item">
87+
{% if item.url %}
88+
<a href="{{ item.url }}">{{ item.title}}</a>
89+
{% else %}
90+
{{ item.title }}
91+
{% endif %}
92+
</li>
93+
{% endif %}
94+
{% endfor %}
95+
</ol>
96+
</nav>
97+
```
98+
99+
## Development setup
100+
101+
### First clone this repository
102+
```
103+
git clone https://github.com/maerteijn/crumbles.git
104+
```
105+
106+
### Install the python project
107+
```bash
108+
pyenv virtualenv crumbles # or your alternative to create a venv
109+
pyenv activate crumbles
110+
make install
111+
```
112+
113+
### Linting
114+
`ruff` and `mypy` are installed and configured
115+
```bash
116+
make lint
117+
```
118+
119+
### Formatting
120+
121+
`ruff` are configured
122+
```bash
123+
make format
124+
```
125+
126+
### Test
127+
128+
Pytest with coverage is default enabled
129+
```bash
130+
make cov
131+
```

0 commit comments

Comments
 (0)