django-easy-icons 0.2.3__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_easy_icons-0.2.3/LICENSE +21 -0
- django_easy_icons-0.2.3/PKG-INFO +290 -0
- django_easy_icons-0.2.3/README.md +272 -0
- django_easy_icons-0.2.3/pyproject.toml +103 -0
- django_easy_icons-0.2.3/src/easy_icons/__init__.py +29 -0
- django_easy_icons-0.2.3/src/easy_icons/apps.py +9 -0
- django_easy_icons-0.2.3/src/easy_icons/base.py +110 -0
- django_easy_icons-0.2.3/src/easy_icons/exceptions.py +34 -0
- django_easy_icons-0.2.3/src/easy_icons/renderers.py +114 -0
- django_easy_icons-0.2.3/src/easy_icons/templatetags/__init__.py +0 -0
- django_easy_icons-0.2.3/src/easy_icons/templatetags/easy_icons.py +53 -0
- django_easy_icons-0.2.3/src/easy_icons/utils.py +122 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Sam
|
|
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,290 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: django-easy-icons
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Easy, flexible icons for Django
|
|
5
|
+
Home-page: https://github.com/SamuelJennings/django-easy-icons
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Sam
|
|
8
|
+
Author-email: samuel.scott.jennings@gmail.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Dist: django
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Django Easy Icons
|
|
19
|
+
|
|
20
|
+
Easy, flexible icons for Django templates with support for multiple rendering backends.
|
|
21
|
+
|
|
22
|
+
## Overview
|
|
23
|
+
|
|
24
|
+
Django Easy Icons provides a simple, consistent way to include icons in your Django templates. It supports multiple icon sources including SVG files, font icon libraries (like Font Awesome), and SVG sprite sheets.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **Multiple Renderers**: Support for SVG files, font icons, and sprite sheets
|
|
29
|
+
- **Template Integration**: Simple `{% icon %}` template tag
|
|
30
|
+
- **Flexible Configuration**: Configure multiple icon sets with different renderers
|
|
31
|
+
- **Attribute Merging**: Easily add classes and attributes to icons
|
|
32
|
+
- **Caching**: Built-in renderer caching for performance
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install django-easy-icons
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Add `easy_icons` to your `INSTALLED_APPS`:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
INSTALLED_APPS = [
|
|
44
|
+
# ... other apps
|
|
45
|
+
'easy_icons',
|
|
46
|
+
]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### 1. Configure Icon Renderers
|
|
52
|
+
|
|
53
|
+
Add configuration to your Django settings:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
EASY_ICONS = {
|
|
57
|
+
"default": {
|
|
58
|
+
"renderer": "easy_icons.renderers.SvgRenderer",
|
|
59
|
+
"config": {
|
|
60
|
+
"svg_dir": "icons", # Template directory for SVG files
|
|
61
|
+
"default_attrs": {
|
|
62
|
+
"height": "1em",
|
|
63
|
+
"fill": "currentColor"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"icons": {
|
|
67
|
+
"home": "home.svg",
|
|
68
|
+
"user": "user.svg",
|
|
69
|
+
"settings": "cog.svg"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. Use in Templates
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
{% load easy_icons %}
|
|
79
|
+
|
|
80
|
+
<!-- Basic usage -->
|
|
81
|
+
{% icon "home" %}
|
|
82
|
+
|
|
83
|
+
<!-- With additional attributes -->
|
|
84
|
+
{% icon "user" class="nav-icon" height="2em" %}
|
|
85
|
+
|
|
86
|
+
<!-- Using specific renderer -->
|
|
87
|
+
{% icon "heart" renderer="fontawesome" %}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. Use in Python Code
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from easy_icons import icon
|
|
94
|
+
|
|
95
|
+
# Basic usage
|
|
96
|
+
home_icon = icon("home")
|
|
97
|
+
|
|
98
|
+
# With attributes
|
|
99
|
+
user_icon = icon("user", **{"class": "large", "data-role": "button"})
|
|
100
|
+
|
|
101
|
+
# Using specific renderer
|
|
102
|
+
fa_icon = icon("heart", renderer="fontawesome")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Renderers
|
|
106
|
+
|
|
107
|
+
### SVG Renderer
|
|
108
|
+
|
|
109
|
+
Renders icons from SVG template files:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
EASY_ICONS = {
|
|
113
|
+
"svg": {
|
|
114
|
+
"renderer": "easy_icons.renderers.SvgRenderer",
|
|
115
|
+
"config": {
|
|
116
|
+
"svg_dir": "icons",
|
|
117
|
+
"default_attrs": {"class": "svg-icon"}
|
|
118
|
+
},
|
|
119
|
+
"icons": {
|
|
120
|
+
"home": "house.svg",
|
|
121
|
+
"user": "person.svg"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Provider Renderer
|
|
128
|
+
|
|
129
|
+
For font icon libraries like Font Awesome:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
EASY_ICONS = {
|
|
133
|
+
"fontawesome": {
|
|
134
|
+
"renderer": "easy_icons.renderers.ProviderRenderer",
|
|
135
|
+
"config": {
|
|
136
|
+
"tag": "i"
|
|
137
|
+
},
|
|
138
|
+
"icons": {
|
|
139
|
+
"home": "fas fa-home",
|
|
140
|
+
"user": "fas fa-user",
|
|
141
|
+
"heart": "fas fa-heart"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Sprites Renderer
|
|
148
|
+
|
|
149
|
+
For SVG sprite sheets:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
EASY_ICONS = {
|
|
153
|
+
"sprites": {
|
|
154
|
+
"renderer": "easy_icons.renderers.SpritesRenderer",
|
|
155
|
+
"config": {
|
|
156
|
+
"sprite_url": "/static/icons.svg"
|
|
157
|
+
},
|
|
158
|
+
"icons": {
|
|
159
|
+
"logo": "brand-logo",
|
|
160
|
+
"menu": "hamburger-menu"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Configuration
|
|
167
|
+
|
|
168
|
+
### Multiple Renderers
|
|
169
|
+
|
|
170
|
+
You can configure multiple renderers and choose which one to use:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
EASY_ICONS = {
|
|
174
|
+
"default": {
|
|
175
|
+
"renderer": "easy_icons.renderers.SvgRenderer",
|
|
176
|
+
"config": {"svg_dir": "icons"},
|
|
177
|
+
"icons": {"home": "home.svg"}
|
|
178
|
+
},
|
|
179
|
+
"fontawesome": {
|
|
180
|
+
"renderer": "easy_icons.renderers.ProviderRenderer",
|
|
181
|
+
"config": {"tag": "i"},
|
|
182
|
+
"icons": {"heart": "fas fa-heart"}
|
|
183
|
+
},
|
|
184
|
+
"sprites": {
|
|
185
|
+
"renderer": "easy_icons.renderers.SpritesRenderer",
|
|
186
|
+
"config": {"sprite_url": "/static/icons.svg"},
|
|
187
|
+
"icons": {"logo": "brand"}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Use in templates:
|
|
193
|
+
|
|
194
|
+
```html
|
|
195
|
+
{% icon "home" %} <!-- Uses default renderer -->
|
|
196
|
+
{% icon "heart" renderer="fontawesome" %}
|
|
197
|
+
{% icon "logo" renderer="sprites" %}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Default Attributes
|
|
201
|
+
|
|
202
|
+
Configure default attributes that will be applied to all icons from a renderer:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
"config": {
|
|
206
|
+
"default_attrs": {
|
|
207
|
+
"class": "icon",
|
|
208
|
+
"aria-hidden": "true",
|
|
209
|
+
"height": "1em"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Note**: Template tag attributes completely override default attributes - they don't merge.
|
|
215
|
+
|
|
216
|
+
### Attribute Overriding
|
|
217
|
+
|
|
218
|
+
Template tag attributes override default attributes:
|
|
219
|
+
|
|
220
|
+
- Provided attributes completely replace defaults: `height="1em"` + `height="2em"` = `height="2em"`
|
|
221
|
+
- This includes class attributes: `class="icon"` + `class="large"` = `class="large"`
|
|
222
|
+
|
|
223
|
+
## Advanced Usage
|
|
224
|
+
|
|
225
|
+
### Custom Renderers
|
|
226
|
+
|
|
227
|
+
Create custom renderers by extending `BaseRenderer`:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from easy_icons.base import BaseRenderer
|
|
231
|
+
from django.utils.safestring import SafeString
|
|
232
|
+
|
|
233
|
+
class CustomRenderer(BaseRenderer):
|
|
234
|
+
def render(self, name: str, **kwargs) -> SafeString:
|
|
235
|
+
resolved_name = self.get_icon(name)
|
|
236
|
+
attrs = self.build_attrs(**kwargs)
|
|
237
|
+
html = f'<custom-icon {attrs}>{resolved_name}</custom-icon>'
|
|
238
|
+
return self.safe_return(html)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Disable Default Attributes
|
|
242
|
+
|
|
243
|
+
Use `use_defaults=False` to ignore default attributes:
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
{% icon "home" use_defaults=False class="only-this-class" %}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
icon("home", use_defaults=False, **{"class": "only-this-class"})
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Testing
|
|
254
|
+
|
|
255
|
+
Run the test suite:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Install dependencies
|
|
259
|
+
poetry install
|
|
260
|
+
|
|
261
|
+
# Run tests
|
|
262
|
+
poetry run pytest
|
|
263
|
+
|
|
264
|
+
# Run tests with coverage
|
|
265
|
+
poetry run pytest --cov=easy_icons --cov-report=html
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Contributing
|
|
269
|
+
|
|
270
|
+
1. Fork the repository
|
|
271
|
+
2. Create a feature branch
|
|
272
|
+
3. Make your changes
|
|
273
|
+
4. Add tests for new functionality
|
|
274
|
+
5. Run the test suite
|
|
275
|
+
6. Submit a pull request
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
This project is licensed under the MIT License. See the LICENSE file for details.
|
|
280
|
+
|
|
281
|
+
## Changelog
|
|
282
|
+
|
|
283
|
+
### 0.1.0
|
|
284
|
+
|
|
285
|
+
- Initial release
|
|
286
|
+
- SVG, Provider, and Sprites renderers
|
|
287
|
+
- Template tag support
|
|
288
|
+
- Configuration system
|
|
289
|
+
- Comprehensive test suite
|
|
290
|
+
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# Django Easy Icons
|
|
2
|
+
|
|
3
|
+
Easy, flexible icons for Django templates with support for multiple rendering backends.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Django Easy Icons provides a simple, consistent way to include icons in your Django templates. It supports multiple icon sources including SVG files, font icon libraries (like Font Awesome), and SVG sprite sheets.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Multiple Renderers**: Support for SVG files, font icons, and sprite sheets
|
|
12
|
+
- **Template Integration**: Simple `{% icon %}` template tag
|
|
13
|
+
- **Flexible Configuration**: Configure multiple icon sets with different renderers
|
|
14
|
+
- **Attribute Merging**: Easily add classes and attributes to icons
|
|
15
|
+
- **Caching**: Built-in renderer caching for performance
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install django-easy-icons
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Add `easy_icons` to your `INSTALLED_APPS`:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
INSTALLED_APPS = [
|
|
27
|
+
# ... other apps
|
|
28
|
+
'easy_icons',
|
|
29
|
+
]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### 1. Configure Icon Renderers
|
|
35
|
+
|
|
36
|
+
Add configuration to your Django settings:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
EASY_ICONS = {
|
|
40
|
+
"default": {
|
|
41
|
+
"renderer": "easy_icons.renderers.SvgRenderer",
|
|
42
|
+
"config": {
|
|
43
|
+
"svg_dir": "icons", # Template directory for SVG files
|
|
44
|
+
"default_attrs": {
|
|
45
|
+
"height": "1em",
|
|
46
|
+
"fill": "currentColor"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"icons": {
|
|
50
|
+
"home": "home.svg",
|
|
51
|
+
"user": "user.svg",
|
|
52
|
+
"settings": "cog.svg"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Use in Templates
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
{% load easy_icons %}
|
|
62
|
+
|
|
63
|
+
<!-- Basic usage -->
|
|
64
|
+
{% icon "home" %}
|
|
65
|
+
|
|
66
|
+
<!-- With additional attributes -->
|
|
67
|
+
{% icon "user" class="nav-icon" height="2em" %}
|
|
68
|
+
|
|
69
|
+
<!-- Using specific renderer -->
|
|
70
|
+
{% icon "heart" renderer="fontawesome" %}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. Use in Python Code
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from easy_icons import icon
|
|
77
|
+
|
|
78
|
+
# Basic usage
|
|
79
|
+
home_icon = icon("home")
|
|
80
|
+
|
|
81
|
+
# With attributes
|
|
82
|
+
user_icon = icon("user", **{"class": "large", "data-role": "button"})
|
|
83
|
+
|
|
84
|
+
# Using specific renderer
|
|
85
|
+
fa_icon = icon("heart", renderer="fontawesome")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Renderers
|
|
89
|
+
|
|
90
|
+
### SVG Renderer
|
|
91
|
+
|
|
92
|
+
Renders icons from SVG template files:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
EASY_ICONS = {
|
|
96
|
+
"svg": {
|
|
97
|
+
"renderer": "easy_icons.renderers.SvgRenderer",
|
|
98
|
+
"config": {
|
|
99
|
+
"svg_dir": "icons",
|
|
100
|
+
"default_attrs": {"class": "svg-icon"}
|
|
101
|
+
},
|
|
102
|
+
"icons": {
|
|
103
|
+
"home": "house.svg",
|
|
104
|
+
"user": "person.svg"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Provider Renderer
|
|
111
|
+
|
|
112
|
+
For font icon libraries like Font Awesome:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
EASY_ICONS = {
|
|
116
|
+
"fontawesome": {
|
|
117
|
+
"renderer": "easy_icons.renderers.ProviderRenderer",
|
|
118
|
+
"config": {
|
|
119
|
+
"tag": "i"
|
|
120
|
+
},
|
|
121
|
+
"icons": {
|
|
122
|
+
"home": "fas fa-home",
|
|
123
|
+
"user": "fas fa-user",
|
|
124
|
+
"heart": "fas fa-heart"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Sprites Renderer
|
|
131
|
+
|
|
132
|
+
For SVG sprite sheets:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
EASY_ICONS = {
|
|
136
|
+
"sprites": {
|
|
137
|
+
"renderer": "easy_icons.renderers.SpritesRenderer",
|
|
138
|
+
"config": {
|
|
139
|
+
"sprite_url": "/static/icons.svg"
|
|
140
|
+
},
|
|
141
|
+
"icons": {
|
|
142
|
+
"logo": "brand-logo",
|
|
143
|
+
"menu": "hamburger-menu"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Configuration
|
|
150
|
+
|
|
151
|
+
### Multiple Renderers
|
|
152
|
+
|
|
153
|
+
You can configure multiple renderers and choose which one to use:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
EASY_ICONS = {
|
|
157
|
+
"default": {
|
|
158
|
+
"renderer": "easy_icons.renderers.SvgRenderer",
|
|
159
|
+
"config": {"svg_dir": "icons"},
|
|
160
|
+
"icons": {"home": "home.svg"}
|
|
161
|
+
},
|
|
162
|
+
"fontawesome": {
|
|
163
|
+
"renderer": "easy_icons.renderers.ProviderRenderer",
|
|
164
|
+
"config": {"tag": "i"},
|
|
165
|
+
"icons": {"heart": "fas fa-heart"}
|
|
166
|
+
},
|
|
167
|
+
"sprites": {
|
|
168
|
+
"renderer": "easy_icons.renderers.SpritesRenderer",
|
|
169
|
+
"config": {"sprite_url": "/static/icons.svg"},
|
|
170
|
+
"icons": {"logo": "brand"}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Use in templates:
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
{% icon "home" %} <!-- Uses default renderer -->
|
|
179
|
+
{% icon "heart" renderer="fontawesome" %}
|
|
180
|
+
{% icon "logo" renderer="sprites" %}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Default Attributes
|
|
184
|
+
|
|
185
|
+
Configure default attributes that will be applied to all icons from a renderer:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
"config": {
|
|
189
|
+
"default_attrs": {
|
|
190
|
+
"class": "icon",
|
|
191
|
+
"aria-hidden": "true",
|
|
192
|
+
"height": "1em"
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Note**: Template tag attributes completely override default attributes - they don't merge.
|
|
198
|
+
|
|
199
|
+
### Attribute Overriding
|
|
200
|
+
|
|
201
|
+
Template tag attributes override default attributes:
|
|
202
|
+
|
|
203
|
+
- Provided attributes completely replace defaults: `height="1em"` + `height="2em"` = `height="2em"`
|
|
204
|
+
- This includes class attributes: `class="icon"` + `class="large"` = `class="large"`
|
|
205
|
+
|
|
206
|
+
## Advanced Usage
|
|
207
|
+
|
|
208
|
+
### Custom Renderers
|
|
209
|
+
|
|
210
|
+
Create custom renderers by extending `BaseRenderer`:
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
from easy_icons.base import BaseRenderer
|
|
214
|
+
from django.utils.safestring import SafeString
|
|
215
|
+
|
|
216
|
+
class CustomRenderer(BaseRenderer):
|
|
217
|
+
def render(self, name: str, **kwargs) -> SafeString:
|
|
218
|
+
resolved_name = self.get_icon(name)
|
|
219
|
+
attrs = self.build_attrs(**kwargs)
|
|
220
|
+
html = f'<custom-icon {attrs}>{resolved_name}</custom-icon>'
|
|
221
|
+
return self.safe_return(html)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Disable Default Attributes
|
|
225
|
+
|
|
226
|
+
Use `use_defaults=False` to ignore default attributes:
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
{% icon "home" use_defaults=False class="only-this-class" %}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
icon("home", use_defaults=False, **{"class": "only-this-class"})
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Testing
|
|
237
|
+
|
|
238
|
+
Run the test suite:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Install dependencies
|
|
242
|
+
poetry install
|
|
243
|
+
|
|
244
|
+
# Run tests
|
|
245
|
+
poetry run pytest
|
|
246
|
+
|
|
247
|
+
# Run tests with coverage
|
|
248
|
+
poetry run pytest --cov=easy_icons --cov-report=html
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Contributing
|
|
252
|
+
|
|
253
|
+
1. Fork the repository
|
|
254
|
+
2. Create a feature branch
|
|
255
|
+
3. Make your changes
|
|
256
|
+
4. Add tests for new functionality
|
|
257
|
+
5. Run the test suite
|
|
258
|
+
6. Submit a pull request
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
This project is licensed under the MIT License. See the LICENSE file for details.
|
|
263
|
+
|
|
264
|
+
## Changelog
|
|
265
|
+
|
|
266
|
+
### 0.1.0
|
|
267
|
+
|
|
268
|
+
- Initial release
|
|
269
|
+
- SVG, Provider, and Sprites renderers
|
|
270
|
+
- Template tag support
|
|
271
|
+
- Configuration system
|
|
272
|
+
- Comprehensive test suite
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "django-easy-icons"
|
|
3
|
+
version = "v0.2.3"
|
|
4
|
+
description = "Easy, flexible icons for Django"
|
|
5
|
+
authors = ["Sam <samuel.scott.jennings@gmail.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
packages = [{include = "easy_icons", from = "src"}]
|
|
9
|
+
homepage = "https://github.com/SamuelJennings/django-easy-icons"
|
|
10
|
+
|
|
11
|
+
[tool.poetry.dependencies]
|
|
12
|
+
python = "^3.10"
|
|
13
|
+
django = "*"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
[tool.poetry.group.dev.dependencies]
|
|
17
|
+
fairdm-dev-tools = {git = "https://github.com/FAIR-DM/dev-tools"}
|
|
18
|
+
|
|
19
|
+
[tool.poetry.group.docs.dependencies]
|
|
20
|
+
fairdm-docs = {git = "https://github.com/FAIR-DM/fairdm-docs", extras = ["sphinx_book_theme"]}
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["poetry-core"]
|
|
24
|
+
build-backend = "poetry.core.masonry.api"
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
DJANGO_SETTINGS_MODULE = "tests.settings"
|
|
28
|
+
pythonpath = [".", "tests"]
|
|
29
|
+
testpaths = ["tests"]
|
|
30
|
+
python_files = ["test_*.py", "*_test.py", "tests.py", "*_tests.py"]
|
|
31
|
+
addopts = [
|
|
32
|
+
"--strict-markers",
|
|
33
|
+
"--strict-config",
|
|
34
|
+
"--verbose",
|
|
35
|
+
"--tb=short",
|
|
36
|
+
"--cov=easy_icons",
|
|
37
|
+
"--cov-report=html",
|
|
38
|
+
"-ra",
|
|
39
|
+
]
|
|
40
|
+
filterwarnings = [
|
|
41
|
+
"ignore",
|
|
42
|
+
"ignore::DeprecationWarning",
|
|
43
|
+
"ignore::PendingDeprecationWarning"
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[tool.coverage.report]
|
|
47
|
+
skip_empty = true
|
|
48
|
+
show_missing = true
|
|
49
|
+
|
|
50
|
+
[tool.coverage.run]
|
|
51
|
+
branch = true
|
|
52
|
+
source = ["easy_icons"]
|
|
53
|
+
|
|
54
|
+
[tool.djlint]
|
|
55
|
+
profile = "django"
|
|
56
|
+
indent = 2
|
|
57
|
+
preserve_leading_space = true
|
|
58
|
+
preserve_blank_lines = true
|
|
59
|
+
format_attribute_template_tags = true
|
|
60
|
+
format_css = true
|
|
61
|
+
format_js = true
|
|
62
|
+
max_line_length = 120
|
|
63
|
+
max_attribute_length = 70
|
|
64
|
+
include = "H006,H013,H020,H021,H023,H025,H030,H031"
|
|
65
|
+
ignore = "H017"
|
|
66
|
+
extension = "html"
|
|
67
|
+
|
|
68
|
+
[tool.black]
|
|
69
|
+
line-length = 120
|
|
70
|
+
target-version = ['py38']
|
|
71
|
+
preview = true
|
|
72
|
+
|
|
73
|
+
[tool.isort]
|
|
74
|
+
profile = "black"
|
|
75
|
+
|
|
76
|
+
[tool.mypy]
|
|
77
|
+
files = ["src/easy_icons"]
|
|
78
|
+
disallow_untyped_defs = "False"
|
|
79
|
+
disallow_any_unimported = "False"
|
|
80
|
+
ignore_missing_imports = "True"
|
|
81
|
+
no_implicit_optional = "True"
|
|
82
|
+
check_untyped_defs = "False"
|
|
83
|
+
warn_return_any = "True"
|
|
84
|
+
warn_unused_ignores = "True"
|
|
85
|
+
show_error_codes = "True"
|
|
86
|
+
exclude = ["docs/","migrations/","tests/settings.py"]
|
|
87
|
+
mypy_path = "src/easy_icons/"
|
|
88
|
+
plugins = ["mypy_django_plugin.main"]
|
|
89
|
+
|
|
90
|
+
[tool.django-stubs]
|
|
91
|
+
django_settings_module = "tests.settings"
|
|
92
|
+
ignore_missing_model_attributes = "True"
|
|
93
|
+
|
|
94
|
+
[tool.deptry]
|
|
95
|
+
extend_exclude = [
|
|
96
|
+
"tasks.py",
|
|
97
|
+
"tests/",
|
|
98
|
+
"example/",
|
|
99
|
+
"docs/"
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
[tool.deptry.per_rule_ignores]
|
|
103
|
+
DEP003 = ["easy_icons"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Django Easy Icons - Flexible icon rendering for Django templates.
|
|
2
|
+
|
|
3
|
+
This package provides a simple, consistent way to include icons in Django templates
|
|
4
|
+
with support for multiple rendering backends including SVG files, font icon libraries,
|
|
5
|
+
and SVG sprite sheets.
|
|
6
|
+
|
|
7
|
+
Basic usage:
|
|
8
|
+
from easy_icons import icon
|
|
9
|
+
|
|
10
|
+
# Render an icon with default renderer
|
|
11
|
+
home_icon = icon("home")
|
|
12
|
+
|
|
13
|
+
# Render with attributes
|
|
14
|
+
user_icon = icon("user", height="2em", **{"class": "nav-icon"})
|
|
15
|
+
|
|
16
|
+
# Use specific renderer
|
|
17
|
+
fa_icon = icon("heart", renderer="fontawesome")
|
|
18
|
+
|
|
19
|
+
Template usage:
|
|
20
|
+
{% load easy_icons %}
|
|
21
|
+
{% icon "home" %}
|
|
22
|
+
{% icon "user" class="nav-icon" %}
|
|
23
|
+
{% icon "heart" renderer="fontawesome" %}
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from .utils import icon
|
|
27
|
+
|
|
28
|
+
__version__ = "0.1.0"
|
|
29
|
+
__all__ = ["icon"]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Base renderer class and common functionality for django-easy-icons.
|
|
2
|
+
|
|
3
|
+
This module provides the abstract base class that all icon renderers must inherit from,
|
|
4
|
+
along with common functionality for attribute handling and icon name resolution.
|
|
5
|
+
|
|
6
|
+
The BaseRenderer class handles:
|
|
7
|
+
- Icon name mapping and resolution
|
|
8
|
+
- HTML attribute building and merging
|
|
9
|
+
- Default attribute management
|
|
10
|
+
- Common interface for all renderers
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
class MyRenderer(BaseRenderer):
|
|
14
|
+
def render(self, name: str, **kwargs) -> SafeString:
|
|
15
|
+
resolved_name = self.get_icon(name)
|
|
16
|
+
attrs = self.build_attrs(**kwargs)
|
|
17
|
+
return self.safe_return(f'<my-icon {attrs}>{resolved_name}</my-icon>')
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from django.forms.utils import flatatt
|
|
24
|
+
from django.utils.safestring import SafeString, mark_safe
|
|
25
|
+
|
|
26
|
+
from .exceptions import IconNotFoundError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BaseRenderer(ABC):
|
|
30
|
+
"""Base class for all icon renderers.
|
|
31
|
+
|
|
32
|
+
Renderers now declare their configuration explicitly via ``__init__``
|
|
33
|
+
keyword arguments. The settings loader star-expands user supplied
|
|
34
|
+
``config`` dictionaries directly into that initializer. Common shared
|
|
35
|
+
data (like icon name mappings) is still passed via ``icons``.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self, *, icons: dict[str, str] | None = None, default_attrs: dict[str, Any] | None = None, **_: Any
|
|
40
|
+
): # pragma: no cover - slim wrapper
|
|
41
|
+
"""Initialize the base renderer.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
icons: Optional[Dict[str, str]]
|
|
46
|
+
Mapping of logical icon names to renderer-specific identifiers.
|
|
47
|
+
default_attrs: Optional[Dict[str, Any]]
|
|
48
|
+
Default HTML attributes applied (and mergeable) across all renders.
|
|
49
|
+
**_ : Any
|
|
50
|
+
Ignored extra keyword arguments (consumed by concrete subclasses).
|
|
51
|
+
"""
|
|
52
|
+
self.icons = icons or {}
|
|
53
|
+
# Provide a unified place for managing default attributes so concrete
|
|
54
|
+
# renderers don't need to duplicate ``self.default_attrs = default_attrs or {}``.
|
|
55
|
+
self.default_attrs = (default_attrs or {}).copy()
|
|
56
|
+
|
|
57
|
+
def get_icon(self, name: str) -> str:
|
|
58
|
+
"""Resolve icon name through renderer-specific icon mappings."""
|
|
59
|
+
try:
|
|
60
|
+
return self.icons[name]
|
|
61
|
+
except KeyError:
|
|
62
|
+
raise IconNotFoundError(f"Icon '{name}' not listed in available icons for {self.__class__.__name__}")
|
|
63
|
+
|
|
64
|
+
def build_attrs(self, use_defaults: bool = True, **kwargs: Any) -> str:
|
|
65
|
+
"""Build HTML attributes string from configuration and provided kwargs.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
use_defaults: Whether to merge with default attributes
|
|
69
|
+
**kwargs: Additional attributes to include
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
HTML attributes string
|
|
73
|
+
"""
|
|
74
|
+
if not use_defaults:
|
|
75
|
+
return flatatt(kwargs)
|
|
76
|
+
|
|
77
|
+
# creates a copy of default_attrs then override with kwargs
|
|
78
|
+
attrs = self.default_attrs.copy()
|
|
79
|
+
attrs.update(kwargs)
|
|
80
|
+
|
|
81
|
+
return flatatt(attrs)
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def render(self, name: str, **kwargs: Any) -> SafeString:
|
|
85
|
+
"""Render an icon with the given name and attributes.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
name: The icon name to render
|
|
89
|
+
**kwargs: Additional attributes for the icon
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Safe HTML string containing the rendered icon
|
|
93
|
+
"""
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
def __call__(self, name: str, **kwargs: Any) -> SafeString:
|
|
97
|
+
"""Make renderer instances callable.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
name: The icon name to render
|
|
101
|
+
**kwargs: Additional attributes for the icon
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Safe HTML string containing the rendered icon
|
|
105
|
+
"""
|
|
106
|
+
return self.render(name, **kwargs)
|
|
107
|
+
|
|
108
|
+
def safe_return(self, content: str) -> SafeString:
|
|
109
|
+
"""Return HTML content marked as safe (internal helper)."""
|
|
110
|
+
return mark_safe(content) # noqa: S308
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Custom exceptions for django-easy-icons.
|
|
2
|
+
|
|
3
|
+
This module defines the custom exception classes used throughout the django-easy-icons
|
|
4
|
+
package to provide clear error messages for common failure scenarios.
|
|
5
|
+
|
|
6
|
+
Exceptions:
|
|
7
|
+
IconNotFoundError: Raised when an icon name cannot be resolved
|
|
8
|
+
InvalidSvgError: Raised when SVG content is malformed or invalid
|
|
9
|
+
|
|
10
|
+
These exceptions inherit from the standard Python Exception class and can be caught
|
|
11
|
+
either specifically or as general exceptions.
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
try:
|
|
15
|
+
icon("missing-icon")
|
|
16
|
+
except IconNotFoundError:
|
|
17
|
+
# Handle missing icon case
|
|
18
|
+
pass
|
|
19
|
+
except InvalidSvgError:
|
|
20
|
+
# Handle malformed SVG case
|
|
21
|
+
pass
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IconNotFoundError(Exception):
|
|
26
|
+
"""Raised when an icon name cannot be resolved or underlying asset is missing."""
|
|
27
|
+
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class InvalidSvgError(Exception):
|
|
32
|
+
"""Raised when SVG content is malformed or missing required elements."""
|
|
33
|
+
|
|
34
|
+
pass
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Concrete renderer implementations for django-easy-icons.
|
|
2
|
+
|
|
3
|
+
All input validation responsibility is delegated to a single place:
|
|
4
|
+
``BaseRenderer.get_icon``. Renderers themselves assume any name
|
|
5
|
+
they receive has passed that minimal check and focus purely on output
|
|
6
|
+
generation.
|
|
7
|
+
|
|
8
|
+
The public API for consumers is ``easy_icons.utils.icon``; this module
|
|
9
|
+
only exposes the concrete classes.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from django.template.loader import render_to_string
|
|
15
|
+
from django.utils.safestring import SafeString
|
|
16
|
+
|
|
17
|
+
from .base import BaseRenderer
|
|
18
|
+
from .exceptions import InvalidSvgError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SvgRenderer(BaseRenderer):
|
|
22
|
+
"""Renderer for SVG icons sourced from template files.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
svg_dir: str = "icons"
|
|
27
|
+
Directory (template prefix) where SVG template fragments live.
|
|
28
|
+
default_attrs: dict | None
|
|
29
|
+
Default attributes to inject/merge into rendered SVG root element.
|
|
30
|
+
icons: dict[str,str] | None
|
|
31
|
+
Name mapping provided via settings.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, *, svg_dir: str = "icons", **kwargs: Any):
|
|
35
|
+
super().__init__(**kwargs)
|
|
36
|
+
self.svg_dir = svg_dir
|
|
37
|
+
|
|
38
|
+
def render(self, name: str, **kwargs: Any) -> SafeString: # noqa: D401
|
|
39
|
+
resolved_name = self.get_icon(name)
|
|
40
|
+
template_name = f"{self.svg_dir}/{resolved_name}"
|
|
41
|
+
svg_str = render_to_string(template_name)
|
|
42
|
+
return self._inject_svg_attrs(svg_str, **kwargs)
|
|
43
|
+
|
|
44
|
+
def _inject_svg_attrs(self, svg_str: str, **kwargs: Any) -> SafeString:
|
|
45
|
+
"""Inject attributes into the SVG element.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
svg_str: The SVG content as a string
|
|
49
|
+
**kwargs: Additional attributes to inject into the SVG tag
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Safe HTML string with attributes injected
|
|
53
|
+
"""
|
|
54
|
+
attrs = self.build_attrs(**kwargs)
|
|
55
|
+
|
|
56
|
+
if not attrs:
|
|
57
|
+
return self.safe_return(svg_str)
|
|
58
|
+
|
|
59
|
+
# Split on '<svg' to isolate the svg tag
|
|
60
|
+
before, sep, after = svg_str.partition("<svg")
|
|
61
|
+
|
|
62
|
+
if not sep: # No '<svg' found
|
|
63
|
+
raise InvalidSvgError("No <svg> tag found in SVG content")
|
|
64
|
+
|
|
65
|
+
result = f"{before}<svg {attrs} {after.strip()}"
|
|
66
|
+
return self.safe_return(result)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ProviderRenderer(BaseRenderer):
|
|
70
|
+
"""Renderer for provider / font icon classes using full class strings."""
|
|
71
|
+
|
|
72
|
+
template = '<{tag} class="{css_class}" {attrs}></{tag}>'
|
|
73
|
+
|
|
74
|
+
def __init__(self, *, tag: str = "i", **kwargs: Any):
|
|
75
|
+
super().__init__(**kwargs)
|
|
76
|
+
self.tag = tag
|
|
77
|
+
|
|
78
|
+
def render(self, name: str, **kwargs: Any) -> SafeString: # noqa: D401
|
|
79
|
+
tag = self.tag
|
|
80
|
+
resolved_icon = f"{self.get_icon(name)} {kwargs.pop('class', '')} "
|
|
81
|
+
attrs = self.build_attrs(**kwargs)
|
|
82
|
+
element = self.template.format(tag=tag, css_class=resolved_icon.strip(), attrs=attrs).strip()
|
|
83
|
+
return self.safe_return(element)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class SpritesRenderer(BaseRenderer):
|
|
87
|
+
"""Renderer for SVG sprite symbols via <use>.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
sprite_url: str (required)
|
|
92
|
+
Base URL/path to the sprite sheet. Required; raises ValueError if missing.
|
|
93
|
+
default_attrs: dict | None
|
|
94
|
+
Default attributes for the outer <svg> element.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
template = """<svg {attrs}>
|
|
98
|
+
<use href="{sprite_url}#{resolved_name}"></use>
|
|
99
|
+
</svg>"""
|
|
100
|
+
|
|
101
|
+
def __init__(self, *, sprite_url: str | None = None, **kwargs: Any):
|
|
102
|
+
if not sprite_url:
|
|
103
|
+
raise ValueError("SpritesRenderer requires 'sprite_url' keyword argument")
|
|
104
|
+
super().__init__(**kwargs)
|
|
105
|
+
self.sprite_url = sprite_url
|
|
106
|
+
|
|
107
|
+
def render(self, name: str, **kwargs: Any) -> SafeString: # noqa: D401
|
|
108
|
+
resolved_name = self.get_icon(name)
|
|
109
|
+
sprite_url = self.sprite_url
|
|
110
|
+
attrs = self.build_attrs(**kwargs)
|
|
111
|
+
|
|
112
|
+
element = self.template.format(sprite_url=sprite_url, resolved_name=resolved_name, attrs=attrs).strip()
|
|
113
|
+
|
|
114
|
+
return self.safe_return(element)
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Template tags for django-easy-icons.
|
|
2
|
+
|
|
3
|
+
This module provides Django template tags for rendering icons in templates.
|
|
4
|
+
The main template tag is {% icon %} which integrates with the django-easy-icons
|
|
5
|
+
rendering system.
|
|
6
|
+
|
|
7
|
+
Usage in templates:
|
|
8
|
+
{% load easy_icons %}
|
|
9
|
+
|
|
10
|
+
<!-- Basic usage -->
|
|
11
|
+
{% icon "home" %}
|
|
12
|
+
|
|
13
|
+
<!-- With attributes -->
|
|
14
|
+
{% icon "user" class="nav-icon" height="2em" %}
|
|
15
|
+
|
|
16
|
+
<!-- Using specific renderer -->
|
|
17
|
+
{% icon "heart" renderer="fontawesome" %}
|
|
18
|
+
|
|
19
|
+
<!-- With template variables -->
|
|
20
|
+
{% icon icon_name class=css_class %}
|
|
21
|
+
|
|
22
|
+
The template tag supports all the same functionality as the Python icon() function,
|
|
23
|
+
including multiple renderers, attribute merging, and Django's automatic HTML escaping.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from django import template
|
|
27
|
+
from django.utils.safestring import SafeString
|
|
28
|
+
|
|
29
|
+
from .. import utils
|
|
30
|
+
|
|
31
|
+
register = template.Library()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@register.simple_tag
|
|
35
|
+
def icon(name: str, renderer: str = "default", **kwargs) -> SafeString:
|
|
36
|
+
"""Template tag to render an icon.
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
{% icon "home" %}
|
|
40
|
+
{% icon "home" renderer="fontawesome" %}
|
|
41
|
+
{% icon "home" renderer="sprites" %}
|
|
42
|
+
{% icon "home" class="large" height="2em" %}
|
|
43
|
+
{% icon "heart" renderer="fontawesome" class="gold" %}
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
name: The icon name to render
|
|
47
|
+
renderer: Name of the renderer to use (defaults to 'default')
|
|
48
|
+
**kwargs: Additional attributes for the icon
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Safe HTML string containing the rendered icon
|
|
52
|
+
"""
|
|
53
|
+
return utils.icon(name, renderer=renderer, **kwargs)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Main interface functions for django-easy-icons.
|
|
2
|
+
|
|
3
|
+
This module provides the primary public API for the django-easy-icons package,
|
|
4
|
+
including renderer management, caching, and the main icon() function used by
|
|
5
|
+
both Python code and template tags.
|
|
6
|
+
|
|
7
|
+
Functions:
|
|
8
|
+
get_renderer(name): Get a configured renderer instance by name
|
|
9
|
+
clear_cache(): Clear the renderer instance cache
|
|
10
|
+
icon(name, renderer, **kwargs): Render an icon using the specified renderer
|
|
11
|
+
|
|
12
|
+
The icon() function is the main entry point for rendering icons and supports:
|
|
13
|
+
- Multiple configured renderers
|
|
14
|
+
- Attribute merging and customization
|
|
15
|
+
- Caching for performance
|
|
16
|
+
- Integration with Django's SafeString for secure HTML output
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
# Basic usage
|
|
20
|
+
home_icon = icon("home")
|
|
21
|
+
|
|
22
|
+
# With custom attributes
|
|
23
|
+
user_icon = icon("user", **{"class": "large", "data-role": "button"})
|
|
24
|
+
|
|
25
|
+
# Using named renderer
|
|
26
|
+
fa_icon = icon("heart", renderer="fontawesome")
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from typing import Any, cast
|
|
30
|
+
|
|
31
|
+
from django.conf import settings
|
|
32
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
33
|
+
from django.utils.module_loading import import_string
|
|
34
|
+
from django.utils.safestring import SafeString
|
|
35
|
+
|
|
36
|
+
# Simple module-level cache for renderer instances
|
|
37
|
+
_renderer_cache: dict[str, Any] = {}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_renderer(name: str = "default") -> Any:
|
|
41
|
+
"""Get a configured renderer instance.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
name: Name of the renderer to get (defaults to 'default')
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Configured renderer instance
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ImproperlyConfigured: If renderer is not configured or cannot be imported
|
|
51
|
+
"""
|
|
52
|
+
# Check cache first
|
|
53
|
+
if name in _renderer_cache:
|
|
54
|
+
return _renderer_cache[name]
|
|
55
|
+
|
|
56
|
+
# Get configuration from settings or use empty dict
|
|
57
|
+
config = getattr(settings, "EASY_ICONS", {})
|
|
58
|
+
|
|
59
|
+
# Validate basic configuration structure
|
|
60
|
+
if not isinstance(config, dict):
|
|
61
|
+
raise ImproperlyConfigured("EASY_ICONS setting must be a dictionary")
|
|
62
|
+
|
|
63
|
+
# Ensure requested renderer exists
|
|
64
|
+
if name not in config:
|
|
65
|
+
raise ImproperlyConfigured(f"Renderer '{name}' is not configured in EASY_ICONS")
|
|
66
|
+
|
|
67
|
+
renderer_config = config[name]
|
|
68
|
+
|
|
69
|
+
# Validate renderer configuration structure
|
|
70
|
+
if not isinstance(renderer_config, dict):
|
|
71
|
+
raise ImproperlyConfigured(f"EASY_ICONS['{name}'] must be a dictionary")
|
|
72
|
+
|
|
73
|
+
if "renderer" not in renderer_config:
|
|
74
|
+
raise ImproperlyConfigured(f"EASY_ICONS['{name}'] must specify a 'renderer' class path")
|
|
75
|
+
|
|
76
|
+
# Import and instantiate the renderer class
|
|
77
|
+
renderer_class_path = renderer_config["renderer"]
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
renderer_class = import_string(renderer_class_path)
|
|
81
|
+
except ImportError as e:
|
|
82
|
+
raise ImproperlyConfigured(f"Cannot import renderer class '{renderer_class_path}': {e}")
|
|
83
|
+
|
|
84
|
+
# Extract configuration options
|
|
85
|
+
renderer_kwargs = renderer_config.get("config", {}) or {}
|
|
86
|
+
renderer_icons = renderer_config.get("icons", {}) or {}
|
|
87
|
+
|
|
88
|
+
# Create instance
|
|
89
|
+
try:
|
|
90
|
+
renderer_instance = renderer_class(
|
|
91
|
+
icons=renderer_icons,
|
|
92
|
+
**renderer_kwargs,
|
|
93
|
+
)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
raise ImproperlyConfigured(f"Cannot instantiate renderer '{name}' with class '{renderer_class_path}': {e}")
|
|
96
|
+
|
|
97
|
+
# Cache the instance
|
|
98
|
+
_renderer_cache[name] = renderer_instance
|
|
99
|
+
return renderer_instance
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def clear_cache() -> None:
|
|
103
|
+
"""Clear renderer cache.
|
|
104
|
+
|
|
105
|
+
Useful for testing and development when settings might change.
|
|
106
|
+
"""
|
|
107
|
+
_renderer_cache.clear()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def icon(name: str, renderer: str = "default", **kwargs: Any) -> SafeString:
|
|
111
|
+
"""Render an icon using the specified or default renderer.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
name: The icon name to render
|
|
115
|
+
renderer: Name of the renderer to use (defaults to 'default')
|
|
116
|
+
**kwargs: Additional attributes for the icon
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Safe HTML string containing the rendered icon
|
|
120
|
+
"""
|
|
121
|
+
renderer_instance = get_renderer(renderer)
|
|
122
|
+
return cast(SafeString, renderer_instance(name, **kwargs))
|