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.
@@ -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,9 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class EasyIconsConfig(AppConfig):
5
+ """Configuration for the Easy Icons Django app."""
6
+
7
+ name = "easy_icons"
8
+ default_auto_field = "django.db.models.BigAutoField"
9
+ verbose_name = "Easy Icons"
@@ -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)
@@ -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))