django-simple-icons 16.26.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_simple_icons-16.26.0/LICENSE +21 -0
- django_simple_icons-16.26.0/MANIFEST.in +1 -0
- django_simple_icons-16.26.0/PKG-INFO +220 -0
- django_simple_icons-16.26.0/README.md +189 -0
- django_simple_icons-16.26.0/pyproject.toml +136 -0
- django_simple_icons-16.26.0/setup.cfg +4 -0
- django_simple_icons-16.26.0/src/django_simple_icons/__init__.py +121 -0
- django_simple_icons-16.26.0/src/django_simple_icons/jinja.py +16 -0
- django_simple_icons-16.26.0/src/django_simple_icons/py.typed +0 -0
- django_simple_icons-16.26.0/src/django_simple_icons/simple-icons.zip +0 -0
- django_simple_icons-16.26.0/src/django_simple_icons/templatetags/__init__.py +0 -0
- django_simple_icons-16.26.0/src/django_simple_icons/templatetags/simple_icons.py +22 -0
- django_simple_icons-16.26.0/src/django_simple_icons.egg-info/PKG-INFO +220 -0
- django_simple_icons-16.26.0/src/django_simple_icons.egg-info/SOURCES.txt +18 -0
- django_simple_icons-16.26.0/src/django_simple_icons.egg-info/dependency_links.txt +1 -0
- django_simple_icons-16.26.0/src/django_simple_icons.egg-info/requires.txt +6 -0
- django_simple_icons-16.26.0/src/django_simple_icons.egg-info/top_level.txt +1 -0
- django_simple_icons-16.26.0/tests/test_core.py +131 -0
- django_simple_icons-16.26.0/tests/test_django.py +155 -0
- django_simple_icons-16.26.0/tests/test_jinja.py +100 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 M4p4
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include src/django_simple_icons/simple-icons.zip
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-simple-icons
|
|
3
|
+
Version: 16.26.0
|
|
4
|
+
Summary: Use Simple Icons brand icons in your Django and Jinja templates.
|
|
5
|
+
Author-email: M4p4 <homejr@protonmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Changelog, https://github.com/M4p4/django-simple-icons/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Repository, https://github.com/M4p4/django-simple-icons
|
|
9
|
+
Keywords: django,icons,jinja,simple-icons,svg
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 4.2
|
|
13
|
+
Classifier: Framework :: Django :: 5.2
|
|
14
|
+
Classifier: Framework :: Django :: 6.0
|
|
15
|
+
Classifier: Framework :: Django :: 6.1
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Provides-Extra: django
|
|
27
|
+
Requires-Dist: django>=4.2; extra == "django"
|
|
28
|
+
Provides-Extra: jinja
|
|
29
|
+
Requires-Dist: jinja2>=2.8; extra == "jinja"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# django-simple-icons
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/django-simple-icons/)
|
|
35
|
+
[](https://pypi.org/project/django-simple-icons/)
|
|
36
|
+
[](https://github.com/M4p4/django-simple-icons/actions/workflows/main.yml)
|
|
37
|
+
|
|
38
|
+
Use [Simple Icons](https://simpleicons.org/), 3,449 SVG brand icons, in your Django and
|
|
39
|
+
Jinja templates. The icons ship inside the package, so there is no CDN to call and no
|
|
40
|
+
static files to collect. Each icon renders as inline SVG that follows the surrounding
|
|
41
|
+
text color.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
Install with the extra matching your template engine:
|
|
46
|
+
|
|
47
|
+
```console
|
|
48
|
+
python -m pip install 'django-simple-icons[django]'
|
|
49
|
+
python -m pip install 'django-simple-icons[jinja]'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The package itself has no runtime dependencies and needs Python 3.12 or later.
|
|
53
|
+
|
|
54
|
+
## Django
|
|
55
|
+
|
|
56
|
+
Add the app to `INSTALLED_APPS` so the template tag library can be loaded:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
INSTALLED_APPS = [
|
|
60
|
+
...,
|
|
61
|
+
"django_simple_icons",
|
|
62
|
+
]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then load the library and render an icon by its slug:
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
{% load simple_icons %}
|
|
69
|
+
|
|
70
|
+
{% simple_icon "django" %}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
That produces a 24x24 inline SVG which inherits the current text color and is hidden
|
|
74
|
+
from screen readers:
|
|
75
|
+
|
|
76
|
+
```html
|
|
77
|
+
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" aria-hidden="true"><path d="M11.146 0h3.924…"/></svg>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Sizing
|
|
81
|
+
|
|
82
|
+
`size` sets both `width` and `height` and defaults to 24:
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
{% simple_icon "django" size=48 %}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Pass `size=None` to omit both attributes, which is what you want when CSS classes
|
|
89
|
+
control the size:
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
{% simple_icon "django" size=None class="h-6 w-6" %}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Color
|
|
96
|
+
|
|
97
|
+
Icons use `fill="currentColor"` by default, so they follow the surrounding text. Pass
|
|
98
|
+
`color="brand"` to use the brand's official color, or any CSS color to set it directly:
|
|
99
|
+
|
|
100
|
+
```html
|
|
101
|
+
{% simple_icon "django" color="brand" %}
|
|
102
|
+
{% simple_icon "django" color="#ff6600" %}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Extra attributes
|
|
106
|
+
|
|
107
|
+
Any other keyword argument becomes an attribute on the `<svg>` element, with
|
|
108
|
+
underscores converted to dashes:
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
{% simple_icon "django" class="h-6 w-6 text-green-700" data_controller="icon" %}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" aria-hidden="true" class="h-6 w-6 text-green-700" data-controller="icon"><path d="M11.146 0h3.924…"/></svg>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Values are escaped, so `data_test="a < 2"` renders as `data-test="a < 2"`.
|
|
119
|
+
|
|
120
|
+
### Accessibility
|
|
121
|
+
|
|
122
|
+
Icons are decorative by default, so the upstream `<title>` is removed and
|
|
123
|
+
`aria-hidden="true"` is set. An icon next to a text label needs nothing more.
|
|
124
|
+
|
|
125
|
+
For a standalone icon that carries meaning, pass `title=True` to keep the brand name as
|
|
126
|
+
the accessible name, or pass a string to replace it:
|
|
127
|
+
|
|
128
|
+
```html
|
|
129
|
+
{% simple_icon "django" title=True %}
|
|
130
|
+
{% simple_icon "django" title="Django framework" %}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Either form keeps `role="img"` and drops `aria-hidden`:
|
|
134
|
+
|
|
135
|
+
```html
|
|
136
|
+
<svg role="img" viewBox="0 0 24 24" width="24" height="24" fill="currentColor"><title>Django framework</title><path d="M11.146 0h3.924…"/></svg>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Jinja
|
|
140
|
+
|
|
141
|
+
Register the function as a global on your environment:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from django_simple_icons.jinja import simple_icon
|
|
145
|
+
|
|
146
|
+
environment.globals["simple_icon"] = simple_icon
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Call it with the same arguments as the Django tag:
|
|
150
|
+
|
|
151
|
+
```jinja
|
|
152
|
+
{{ simple_icon("django") }}
|
|
153
|
+
{{ simple_icon("django", size=48, color="brand") }}
|
|
154
|
+
{{ simple_icon("django", title="Django framework") }}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The result is a `markupsafe.Markup`, so it renders unescaped under autoescaping.
|
|
158
|
+
|
|
159
|
+
Because `class` is a reserved word in Python, spell it `class_` when calling the
|
|
160
|
+
function directly. The trailing underscore is stripped:
|
|
161
|
+
|
|
162
|
+
```jinja
|
|
163
|
+
{{ simple_icon("django", class_="h-6 w-6") }}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Arguments
|
|
167
|
+
|
|
168
|
+
| Argument | Default | Description |
|
|
169
|
+
| --------- | ---------------- | ---------------------------------------------------------- |
|
|
170
|
+
| `name` | required | The icon slug, such as `"django"` |
|
|
171
|
+
| `size` | `24` | Sets `width` and `height`; `None` omits both |
|
|
172
|
+
| `color` | `"currentColor"` | Any CSS color, or `"brand"` for the brand's official color |
|
|
173
|
+
| `title` | `False` | `True` keeps the brand name, a string replaces it |
|
|
174
|
+
| `**attrs` | none | Extra attributes; underscores become dashes |
|
|
175
|
+
|
|
176
|
+
## Brand colors
|
|
177
|
+
|
|
178
|
+
`get_brand_color` returns a brand's official color, which is useful outside a template:
|
|
179
|
+
|
|
180
|
+
```pycon
|
|
181
|
+
>>> from django_simple_icons import get_brand_color
|
|
182
|
+
>>> get_brand_color("django")
|
|
183
|
+
'#092E20'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Finding icon slugs
|
|
187
|
+
|
|
188
|
+
Slugs match the names on [simpleicons.org](https://simpleicons.org/), where you can
|
|
189
|
+
search the full set. An unknown slug raises `IconDoesNotExist` with suggestions:
|
|
190
|
+
|
|
191
|
+
```pycon
|
|
192
|
+
>>> from django_simple_icons import get_brand_color
|
|
193
|
+
>>> get_brand_color("djngo")
|
|
194
|
+
Traceback (most recent call last):
|
|
195
|
+
...
|
|
196
|
+
django_simple_icons.IconDoesNotExist: Icon 'djngo' does not exist. Did you mean: django, deno, fandango? Browse the available icons at https://simpleicons.org/
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Supported versions
|
|
200
|
+
|
|
201
|
+
- Python 3.12, 3.13 and 3.14
|
|
202
|
+
- Django 5.2, 6.0 and 6.1 are tested. Django is declared as `>=4.2` because the code
|
|
203
|
+
uses nothing newer, but only the tested versions are supported.
|
|
204
|
+
- Jinja2 2.8 and later
|
|
205
|
+
|
|
206
|
+
## Versioning
|
|
207
|
+
|
|
208
|
+
The package version mirrors the bundled Simple Icons release, so version 16.26.0
|
|
209
|
+
contains Simple Icons 16.26.0. A fix to this package that ships no new icons appends a
|
|
210
|
+
fourth segment, such as 16.26.0.1.
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
The code in this package is MIT licensed. The icons come from Simple Icons and are
|
|
215
|
+
[CC0-1.0](https://github.com/simple-icons/simple-icons/blob/master/LICENSE.md).
|
|
216
|
+
|
|
217
|
+
Brand icons and names are trademarks of their respective owners. Using an icon does not
|
|
218
|
+
imply endorsement, and some brands restrict how their marks may be used. Read the
|
|
219
|
+
[Simple Icons legal disclaimer](https://github.com/simple-icons/simple-icons/blob/master/DISCLAIMER.md)
|
|
220
|
+
before shipping an icon.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# django-simple-icons
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/django-simple-icons/)
|
|
4
|
+
[](https://pypi.org/project/django-simple-icons/)
|
|
5
|
+
[](https://github.com/M4p4/django-simple-icons/actions/workflows/main.yml)
|
|
6
|
+
|
|
7
|
+
Use [Simple Icons](https://simpleicons.org/), 3,449 SVG brand icons, in your Django and
|
|
8
|
+
Jinja templates. The icons ship inside the package, so there is no CDN to call and no
|
|
9
|
+
static files to collect. Each icon renders as inline SVG that follows the surrounding
|
|
10
|
+
text color.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Install with the extra matching your template engine:
|
|
15
|
+
|
|
16
|
+
```console
|
|
17
|
+
python -m pip install 'django-simple-icons[django]'
|
|
18
|
+
python -m pip install 'django-simple-icons[jinja]'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The package itself has no runtime dependencies and needs Python 3.12 or later.
|
|
22
|
+
|
|
23
|
+
## Django
|
|
24
|
+
|
|
25
|
+
Add the app to `INSTALLED_APPS` so the template tag library can be loaded:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
INSTALLED_APPS = [
|
|
29
|
+
...,
|
|
30
|
+
"django_simple_icons",
|
|
31
|
+
]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then load the library and render an icon by its slug:
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
{% load simple_icons %}
|
|
38
|
+
|
|
39
|
+
{% simple_icon "django" %}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That produces a 24x24 inline SVG which inherits the current text color and is hidden
|
|
43
|
+
from screen readers:
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" aria-hidden="true"><path d="M11.146 0h3.924…"/></svg>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Sizing
|
|
50
|
+
|
|
51
|
+
`size` sets both `width` and `height` and defaults to 24:
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
{% simple_icon "django" size=48 %}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Pass `size=None` to omit both attributes, which is what you want when CSS classes
|
|
58
|
+
control the size:
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
{% simple_icon "django" size=None class="h-6 w-6" %}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Color
|
|
65
|
+
|
|
66
|
+
Icons use `fill="currentColor"` by default, so they follow the surrounding text. Pass
|
|
67
|
+
`color="brand"` to use the brand's official color, or any CSS color to set it directly:
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
{% simple_icon "django" color="brand" %}
|
|
71
|
+
{% simple_icon "django" color="#ff6600" %}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Extra attributes
|
|
75
|
+
|
|
76
|
+
Any other keyword argument becomes an attribute on the `<svg>` element, with
|
|
77
|
+
underscores converted to dashes:
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
{% simple_icon "django" class="h-6 w-6 text-green-700" data_controller="icon" %}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" aria-hidden="true" class="h-6 w-6 text-green-700" data-controller="icon"><path d="M11.146 0h3.924…"/></svg>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Values are escaped, so `data_test="a < 2"` renders as `data-test="a < 2"`.
|
|
88
|
+
|
|
89
|
+
### Accessibility
|
|
90
|
+
|
|
91
|
+
Icons are decorative by default, so the upstream `<title>` is removed and
|
|
92
|
+
`aria-hidden="true"` is set. An icon next to a text label needs nothing more.
|
|
93
|
+
|
|
94
|
+
For a standalone icon that carries meaning, pass `title=True` to keep the brand name as
|
|
95
|
+
the accessible name, or pass a string to replace it:
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
{% simple_icon "django" title=True %}
|
|
99
|
+
{% simple_icon "django" title="Django framework" %}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Either form keeps `role="img"` and drops `aria-hidden`:
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<svg role="img" viewBox="0 0 24 24" width="24" height="24" fill="currentColor"><title>Django framework</title><path d="M11.146 0h3.924…"/></svg>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Jinja
|
|
109
|
+
|
|
110
|
+
Register the function as a global on your environment:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from django_simple_icons.jinja import simple_icon
|
|
114
|
+
|
|
115
|
+
environment.globals["simple_icon"] = simple_icon
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Call it with the same arguments as the Django tag:
|
|
119
|
+
|
|
120
|
+
```jinja
|
|
121
|
+
{{ simple_icon("django") }}
|
|
122
|
+
{{ simple_icon("django", size=48, color="brand") }}
|
|
123
|
+
{{ simple_icon("django", title="Django framework") }}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The result is a `markupsafe.Markup`, so it renders unescaped under autoescaping.
|
|
127
|
+
|
|
128
|
+
Because `class` is a reserved word in Python, spell it `class_` when calling the
|
|
129
|
+
function directly. The trailing underscore is stripped:
|
|
130
|
+
|
|
131
|
+
```jinja
|
|
132
|
+
{{ simple_icon("django", class_="h-6 w-6") }}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Arguments
|
|
136
|
+
|
|
137
|
+
| Argument | Default | Description |
|
|
138
|
+
| --------- | ---------------- | ---------------------------------------------------------- |
|
|
139
|
+
| `name` | required | The icon slug, such as `"django"` |
|
|
140
|
+
| `size` | `24` | Sets `width` and `height`; `None` omits both |
|
|
141
|
+
| `color` | `"currentColor"` | Any CSS color, or `"brand"` for the brand's official color |
|
|
142
|
+
| `title` | `False` | `True` keeps the brand name, a string replaces it |
|
|
143
|
+
| `**attrs` | none | Extra attributes; underscores become dashes |
|
|
144
|
+
|
|
145
|
+
## Brand colors
|
|
146
|
+
|
|
147
|
+
`get_brand_color` returns a brand's official color, which is useful outside a template:
|
|
148
|
+
|
|
149
|
+
```pycon
|
|
150
|
+
>>> from django_simple_icons import get_brand_color
|
|
151
|
+
>>> get_brand_color("django")
|
|
152
|
+
'#092E20'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Finding icon slugs
|
|
156
|
+
|
|
157
|
+
Slugs match the names on [simpleicons.org](https://simpleicons.org/), where you can
|
|
158
|
+
search the full set. An unknown slug raises `IconDoesNotExist` with suggestions:
|
|
159
|
+
|
|
160
|
+
```pycon
|
|
161
|
+
>>> from django_simple_icons import get_brand_color
|
|
162
|
+
>>> get_brand_color("djngo")
|
|
163
|
+
Traceback (most recent call last):
|
|
164
|
+
...
|
|
165
|
+
django_simple_icons.IconDoesNotExist: Icon 'djngo' does not exist. Did you mean: django, deno, fandango? Browse the available icons at https://simpleicons.org/
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Supported versions
|
|
169
|
+
|
|
170
|
+
- Python 3.12, 3.13 and 3.14
|
|
171
|
+
- Django 5.2, 6.0 and 6.1 are tested. Django is declared as `>=4.2` because the code
|
|
172
|
+
uses nothing newer, but only the tested versions are supported.
|
|
173
|
+
- Jinja2 2.8 and later
|
|
174
|
+
|
|
175
|
+
## Versioning
|
|
176
|
+
|
|
177
|
+
The package version mirrors the bundled Simple Icons release, so version 16.26.0
|
|
178
|
+
contains Simple Icons 16.26.0. A fix to this package that ships no new icons appends a
|
|
179
|
+
fourth segment, such as 16.26.0.1.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
The code in this package is MIT licensed. The icons come from Simple Icons and are
|
|
184
|
+
[CC0-1.0](https://github.com/simple-icons/simple-icons/blob/master/LICENSE.md).
|
|
185
|
+
|
|
186
|
+
Brand icons and names are trademarks of their respective owners. Using an icon does not
|
|
187
|
+
imply endorsement, and some brands restrict how their marks may be used. Read the
|
|
188
|
+
[Simple Icons legal disclaimer](https://github.com/simple-icons/simple-icons/blob/master/DISCLAIMER.md)
|
|
189
|
+
before shipping an icon.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = [ "setuptools>=77" ]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-simple-icons"
|
|
7
|
+
version = "16.26.0"
|
|
8
|
+
description = "Use Simple Icons brand icons in your Django and Jinja templates."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
keywords = [ "django", "icons", "jinja", "simple-icons", "svg" ]
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = [ "LICENSE" ]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "M4p4", email = "homejr@protonmail.com" },
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.12"
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 5 - Production/Stable",
|
|
19
|
+
"Framework :: Django",
|
|
20
|
+
"Framework :: Django :: 4.2",
|
|
21
|
+
"Framework :: Django :: 5.2",
|
|
22
|
+
"Framework :: Django :: 6.0",
|
|
23
|
+
"Framework :: Django :: 6.1",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
29
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
30
|
+
"Typing :: Typed",
|
|
31
|
+
]
|
|
32
|
+
optional-dependencies.django = [ "django>=4.2" ]
|
|
33
|
+
optional-dependencies.jinja = [ "jinja2>=2.8" ]
|
|
34
|
+
urls.Changelog = "https://github.com/M4p4/django-simple-icons/blob/main/CHANGELOG.md"
|
|
35
|
+
urls.Repository = "https://github.com/M4p4/django-simple-icons"
|
|
36
|
+
|
|
37
|
+
[dependency-groups]
|
|
38
|
+
test = [
|
|
39
|
+
"coverage[toml]",
|
|
40
|
+
"jinja2",
|
|
41
|
+
"pytest",
|
|
42
|
+
"pytest-randomly",
|
|
43
|
+
]
|
|
44
|
+
django52 = [ "django>=5.2,<5.3" ]
|
|
45
|
+
django60 = [ "django>=6,<6.1" ]
|
|
46
|
+
# Django 6.1 is still in beta; the specifier opts uv into prereleases and will
|
|
47
|
+
# resolve to the final release once it lands.
|
|
48
|
+
django61 = [ "django>=6.1b1,<6.2" ]
|
|
49
|
+
|
|
50
|
+
[tool.setuptools]
|
|
51
|
+
packages.find.where = [ "src" ]
|
|
52
|
+
package-data.django_simple_icons = [ "py.typed", "simple-icons.zip" ]
|
|
53
|
+
|
|
54
|
+
[tool.uv]
|
|
55
|
+
# uv only auto-installs the `dev` group, so name the groups a bare `uv run`
|
|
56
|
+
# needs. The full version matrix is exercised by tox, not by the default env.
|
|
57
|
+
default-groups = [ "test", "django60" ]
|
|
58
|
+
conflicts = [
|
|
59
|
+
[
|
|
60
|
+
{ group = "django52" },
|
|
61
|
+
{ group = "django60" },
|
|
62
|
+
{ group = "django61" },
|
|
63
|
+
],
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.ruff]
|
|
67
|
+
lint.select = [
|
|
68
|
+
# flake8-bugbear
|
|
69
|
+
"B",
|
|
70
|
+
# flake8-comprehensions
|
|
71
|
+
"C4",
|
|
72
|
+
# pycodestyle
|
|
73
|
+
"E",
|
|
74
|
+
# Pyflakes errors
|
|
75
|
+
"F",
|
|
76
|
+
# isort
|
|
77
|
+
"I",
|
|
78
|
+
# flake8-simplify
|
|
79
|
+
"SIM",
|
|
80
|
+
# flake8-tidy-imports
|
|
81
|
+
"TID",
|
|
82
|
+
# pyupgrade
|
|
83
|
+
"UP",
|
|
84
|
+
# Pyflakes warnings
|
|
85
|
+
"W",
|
|
86
|
+
]
|
|
87
|
+
lint.ignore = [
|
|
88
|
+
# flake8-bugbear opinionated rules
|
|
89
|
+
"B9",
|
|
90
|
+
# line-too-long
|
|
91
|
+
"E501",
|
|
92
|
+
# suppressible-exception
|
|
93
|
+
"SIM105",
|
|
94
|
+
# if-else-block-instead-of-if-exp
|
|
95
|
+
"SIM108",
|
|
96
|
+
]
|
|
97
|
+
lint.extend-safe-fixes = [
|
|
98
|
+
# non-pep585-annotation
|
|
99
|
+
"UP006",
|
|
100
|
+
]
|
|
101
|
+
lint.isort.required-imports = [ "from __future__ import annotations" ]
|
|
102
|
+
|
|
103
|
+
[tool.pyproject-fmt]
|
|
104
|
+
max_supported_python = "3.14"
|
|
105
|
+
|
|
106
|
+
[tool.mypy]
|
|
107
|
+
mypy_path = "src/"
|
|
108
|
+
namespace_packages = false
|
|
109
|
+
warn_unreachable = true
|
|
110
|
+
enable_error_code = [
|
|
111
|
+
"ignore-without-code",
|
|
112
|
+
"redundant-expr",
|
|
113
|
+
"truthy-bool",
|
|
114
|
+
]
|
|
115
|
+
strict = true
|
|
116
|
+
overrides = [ { module = "tests.*", allow_untyped_defs = true, allow_untyped_calls = true } ]
|
|
117
|
+
|
|
118
|
+
[tool.pytest]
|
|
119
|
+
ini_options.testpaths = [ "tests" ]
|
|
120
|
+
ini_options.addopts = "-ra --strict-config --strict-markers"
|
|
121
|
+
|
|
122
|
+
[tool.coverage]
|
|
123
|
+
run.branch = true
|
|
124
|
+
run.data_file = ".coverage/cov"
|
|
125
|
+
run.parallel = true
|
|
126
|
+
run.source = [
|
|
127
|
+
"django_simple_icons",
|
|
128
|
+
"tests",
|
|
129
|
+
]
|
|
130
|
+
paths.source = [
|
|
131
|
+
"src",
|
|
132
|
+
".tox/**/site-packages",
|
|
133
|
+
]
|
|
134
|
+
report.show_missing = true
|
|
135
|
+
report.skip_covered = true
|
|
136
|
+
report.skip_empty = true
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Iterator
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from difflib import get_close_matches
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
from importlib.resources import files
|
|
10
|
+
from typing import Any
|
|
11
|
+
from xml.etree import ElementTree
|
|
12
|
+
from zipfile import ZipFile
|
|
13
|
+
|
|
14
|
+
__all__ = ["IconDoesNotExist", "get_brand_color"]
|
|
15
|
+
|
|
16
|
+
_ICONS_URL = "https://simpleicons.org/"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IconDoesNotExist(Exception):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@contextmanager
|
|
24
|
+
def _zip_file() -> Iterator[ZipFile]:
|
|
25
|
+
resource = files("django_simple_icons").joinpath("simple-icons.zip")
|
|
26
|
+
with resource.open("rb") as file, ZipFile(file) as archive:
|
|
27
|
+
yield archive
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@lru_cache(maxsize=1)
|
|
31
|
+
def _load_metadata() -> dict[str, dict[str, str]]:
|
|
32
|
+
with _zip_file() as archive:
|
|
33
|
+
data: dict[str, dict[str, str]] = json.loads(archive.read("data.json"))
|
|
34
|
+
return data
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _unknown_icon(name: str) -> IconDoesNotExist:
|
|
38
|
+
matches = get_close_matches(name, _load_metadata(), n=3)
|
|
39
|
+
suggestion = f" Did you mean: {', '.join(matches)}?" if matches else ""
|
|
40
|
+
return IconDoesNotExist(
|
|
41
|
+
f"Icon {name!r} does not exist.{suggestion}"
|
|
42
|
+
f" Browse the available icons at {_ICONS_URL}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@lru_cache(maxsize=128)
|
|
47
|
+
def _load_icon(name: str) -> ElementTree.Element:
|
|
48
|
+
with _zip_file() as archive:
|
|
49
|
+
try:
|
|
50
|
+
svg_bytes = archive.read(f"icons/{name}.svg")
|
|
51
|
+
except KeyError:
|
|
52
|
+
raise _unknown_icon(name) from None
|
|
53
|
+
|
|
54
|
+
svg = ElementTree.fromstring(svg_bytes.decode())
|
|
55
|
+
# Parsing resolves the xmlns declaration into '{ns}tag' names; undo that so
|
|
56
|
+
# the icon serializes as plain inline SVG.
|
|
57
|
+
for element in svg.iter():
|
|
58
|
+
_, _, element.tag = element.tag.rpartition("}")
|
|
59
|
+
return svg
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_brand_color(name: str) -> str:
|
|
63
|
+
"""Return the official brand color of ``name`` as ``"#RRGGBB"``."""
|
|
64
|
+
try:
|
|
65
|
+
hex_ = _load_metadata()[name]["hex"]
|
|
66
|
+
except KeyError:
|
|
67
|
+
raise _unknown_icon(name) from None
|
|
68
|
+
return f"#{hex_}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _plain_str(value: Any) -> Any:
|
|
72
|
+
"""Demote a ``str`` subclass to ``str``, leaving anything else untouched.
|
|
73
|
+
|
|
74
|
+
``markupsafe.Markup`` overrides ``replace()`` to escape its arguments, so
|
|
75
|
+
ElementTree's serializer would turn its own ``<`` into ``&lt;``.
|
|
76
|
+
Non-strings pass through so ElementTree still rejects what it cannot
|
|
77
|
+
serialize instead of silently emitting ``"None"``.
|
|
78
|
+
"""
|
|
79
|
+
return str(value) if isinstance(value, str) else value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _render_icon(
|
|
83
|
+
name: str,
|
|
84
|
+
*,
|
|
85
|
+
size: int | None,
|
|
86
|
+
color: str,
|
|
87
|
+
title: str | bool,
|
|
88
|
+
attrs: dict[str, Any],
|
|
89
|
+
) -> str:
|
|
90
|
+
color = _plain_str(color)
|
|
91
|
+
title = _plain_str(title)
|
|
92
|
+
|
|
93
|
+
svg = deepcopy(_load_icon(name))
|
|
94
|
+
|
|
95
|
+
if size is not None:
|
|
96
|
+
svg.set("width", str(size))
|
|
97
|
+
svg.set("height", str(size))
|
|
98
|
+
|
|
99
|
+
if color == "brand":
|
|
100
|
+
svg.set("fill", get_brand_color(name))
|
|
101
|
+
else:
|
|
102
|
+
svg.set("fill", color)
|
|
103
|
+
|
|
104
|
+
title_element = svg.find("title")
|
|
105
|
+
assert title_element is not None # every Simple Icon ships a <title>
|
|
106
|
+
if title is False:
|
|
107
|
+
# Decorative by default: no accessible name, hidden from screen readers.
|
|
108
|
+
svg.remove(title_element)
|
|
109
|
+
svg.attrib.pop("role", None)
|
|
110
|
+
svg.set("aria-hidden", "true")
|
|
111
|
+
elif title is not True:
|
|
112
|
+
title_element.text = title
|
|
113
|
+
|
|
114
|
+
for key, value in attrs.items():
|
|
115
|
+
# removesuffix: `class` and `for` are reserved words, so the Python and
|
|
116
|
+
# Jinja surfaces have to spell them `class_` / `for_`.
|
|
117
|
+
svg.set(key.removesuffix("_").replace("_", "-"), str(_plain_str(value)))
|
|
118
|
+
|
|
119
|
+
# Tags carry no namespace after _load_icon(), so no xmlns is serialized —
|
|
120
|
+
# inline SVG inherits it from the surrounding HTML document anyway.
|
|
121
|
+
return ElementTree.tostring(svg, encoding="unicode")
|