diff --git a/dj_iconify/apps.py b/dj_iconify/apps.py index 60b328fc8801b1813967d9055af0f72831808038..99d2e63542fcf538d391f79acff3a50eaaf66a48 100644 --- a/dj_iconify/apps.py +++ b/dj_iconify/apps.py @@ -2,6 +2,5 @@ from django.apps import AppConfig class DjIconifyConfig(AppConfig): - name = 'dj_iconify' - label = 'iconify' - + name = "dj_iconify" + label = "iconify" diff --git a/dj_iconify/types.py b/dj_iconify/types.py index 1842d4c2ec137919e9cdd4f1312246091f19329b..689959f1bc7e4009b866a73b843c117039118e4b 100644 --- a/dj_iconify/types.py +++ b/dj_iconify/types.py @@ -11,6 +11,7 @@ from .util import split_css_unit class IconifyOptional: """Mixin containing optional attributes all other types can contain.""" + left: Optional[int] = None top: Optional[int] = None width: Optional[int] = None @@ -53,12 +54,15 @@ class IconifyIcon(IconifyOptional): Documentation: https://docs.iconify.design/types/iconify-icon.html """ + _collection: Optional["IconifyJSON"] _name: str body: str @classmethod - def from_dict(cls, name: str, src: dict, collection: Optional["IconifyJSON"] = None) -> "IconifyIcon": + def from_dict( + cls, name: str, src: dict, collection: Optional["IconifyJSON"] = None + ) -> "IconifyIcon": self = cls() self.body = src["body"] self._name = name @@ -99,7 +103,15 @@ class IconifyIcon(IconifyOptional): else: return 16 - def as_svg(self, color: Optional[str] = None, width: Optional[str] = None, height: Optional[str] = None, rotate: Optional[str] = None, flip: Optional[Union[str, Sequence]] = None, box: bool = False) -> str: + def as_svg( + self, + color: Optional[str] = None, + width: Optional[str] = None, + height: Optional[str] = None, + rotate: Optional[str] = None, + flip: Optional[Union[str, Sequence]] = None, + box: bool = False, + ) -> str: """Generate a full SVG of this icon. Some transformations can be applied by passing arguments: @@ -132,11 +144,13 @@ class IconifyIcon(IconifyOptional): svg_dim_attrs = f'width="{width}" height="{height}"' # SVG root element (copied bluntly from example output on api.iconify.design) - head = ('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' - f'{svg_dim_attrs} ' - 'preserveAspectRatio="xMidYMid meet" ' - f'viewBox="0 0 {orig_width} {orig_height}" ' - 'style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);">') + head = ( + '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' + f"{svg_dim_attrs} " + 'preserveAspectRatio="xMidYMid meet" ' + f'viewBox="0 0 {orig_width} {orig_height}" ' + 'style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);">' + ) foot = "</svg>" # Build up all transformations, which are added as an SVG group (<g> element) @@ -193,13 +207,14 @@ class IconifyIcon(IconifyOptional): # Construct final SVG data svg = f"{head}{g[0]}{body}{g[1]}{box}{foot}" return svg - + class IconifyAlias(IconifyOptional): """Alias for an icon. Documentation: https://docs.iconify.design/types/iconify-alias.html """ + _collection: Optional["IconifyJSON"] parent: str @@ -229,6 +244,7 @@ class IconifyInfo(IconifyOptional): No documentation; guessed from the JSON data provided by Iconify. """ + name: str author: Dict[str, str] # FIXME turn intoreal object license_: Dict[str, str] # FIXME turn into real object @@ -276,12 +292,13 @@ class IconifyInfo(IconifyOptional): res.update(self._as_dict_optional()) return res - + class IconifyJSON(IconifyOptional): """One collection as a whole. Documentation: https://docs.iconify.design/types/iconify-json.html """ + prefix: str icons: Dict[str, IconifyIcon] aliases: Optional[Dict[str, IconifyAlias]] @@ -300,7 +317,9 @@ class IconifyJSON(IconifyOptional): return self.aliases[name].get_icon() @classmethod - def from_dict(cls, collection: Optional[dict] = None, only: Optional[Collection[str]] = None) -> "IconifyJSON": + def from_dict( + cls, collection: Optional[dict] = None, only: Optional[Collection[str]] = None + ) -> "IconifyJSON": """Construct collection from a dictionary (probably from JSON, originally). If the only parameter is passed a sequence or set, only icons and aliases with @@ -332,7 +351,9 @@ class IconifyJSON(IconifyOptional): if alias_dict: self.aliases[name] = IconifyAlias.from_dict(alias_dict, collection=self) # Make sure we also get the real icon to resolve the alias - self.icons[alias_dict["parent"]] = IconifyIcon.from_dict(alias_dict["parent"], collection["icons"][alias_dict["parent"]], collection=self) + self.icons[alias_dict["parent"]] = IconifyIcon.from_dict( + alias_dict["parent"], collection["icons"][alias_dict["parent"]], collection=self + ) continue # If we got here, track the we did not find the icon diff --git a/dj_iconify/urls.py b/dj_iconify/urls.py index de46f10f8ab5cfb2c15c61a37600d8ac2041da14..060f7dc00914b03dec1d72e1fb0783e8a9bc6c54 100644 --- a/dj_iconify/urls.py +++ b/dj_iconify/urls.py @@ -3,9 +3,17 @@ from django.urls import path, re_path from . import views urlpatterns = [ - path('_config.js', views.ConfigView.as_view(), name='config.js'), - path('collection/', views.CollectionView.as_view(), name='collection'), - path('collections/', views.CollectionsView.as_view(), name='collections'), - re_path(r'^(?P<collection>[A-Za-z0-9-]+)\.(?P<format_>js(on)?)', views.IconifyJSONView.as_view(), name='iconify_json'), - re_path(r'^(?P<collection>[A-Za-z0-9-]+)/(?P<name>[A-Za-z0-9-]+)\.svg', views.IconifySVGView.as_view(), name='iconify_svg'), + path("_config.js", views.ConfigView.as_view(), name="config.js"), + path("collection/", views.CollectionView.as_view(), name="collection"), + path("collections/", views.CollectionsView.as_view(), name="collections"), + re_path( + r"^(?P<collection>[A-Za-z0-9-]+)\.(?P<format_>js(on)?)", + views.IconifyJSONView.as_view(), + name="iconify_json", + ), + re_path( + r"^(?P<collection>[A-Za-z0-9-]+)/(?P<name>[A-Za-z0-9-]+)\.svg", + views.IconifySVGView.as_view(), + name="iconify_svg", + ), ] diff --git a/dj_iconify/util.py b/dj_iconify/util.py index 9b6dfcefd1a51d0545527ebdc5cd5722c2aebdaa..899ee18c00000d99e169b95db00a62d457039c4f 100644 --- a/dj_iconify/util.py +++ b/dj_iconify/util.py @@ -4,7 +4,7 @@ import re def split_css_unit(string: str): """Split string into value and unit. - + >>> split_css_unit("12px") (12, 'px') >>> split_css_unit("1.5em") @@ -16,6 +16,6 @@ def split_css_unit(string: str): """ _value = re.findall("^[0-9.]+", string) value = float(_value[0]) if "." in _value[0] else int(_value[0]) - unit = string[len(_value[0]):] + unit = string[len(_value[0]) :] return value, unit diff --git a/dj_iconify/views.py b/dj_iconify/views.py index cb085cd72929f0690efea005503be69eea546b22..166a0596b59a02032c75eff6da2ab41a7d3759fb 100644 --- a/dj_iconify/views.py +++ b/dj_iconify/views.py @@ -25,6 +25,7 @@ class BaseJSONView(View): The URL route has to pass an argument called format_ containing js or json to determine the output format. """ + default_callback: str = "Console.log" def get_data(self, request: HttpRequest) -> dict: @@ -63,6 +64,7 @@ class ConfigView(View): This sets the API base URL to the endpoint determined by Django's reverse URL mapper. """ + def get(self, request: HttpRequest) -> HttpResponse: # Guess the base URL by reverse-mapping the URL for a fake icon set rev = reverse("iconify_json", kwargs={"collection": "prefix", "format_": "js"}) @@ -79,10 +81,11 @@ class ConfigView(View): class CollectionView(BaseJSONView): """Retrieve the meta-data for a single collection.""" + def get_data(self, request: HttpRequest) -> dict: # Collection name is passed in the prefix query parameter collection = request.GET.get("prefix", None) - if collection is None or not re.match(r'[A-Za-z0-9-]+', collection): + if collection is None or not re.match(r"[A-Za-z0-9-]+", collection): return HttpResponseBadRequest("You must provide a valid prefix name.") # Load icon set through Iconify types @@ -98,6 +101,7 @@ class CollectionView(BaseJSONView): class CollectionsView(BaseJSONView): """Retrieve the available collections with meta-data.""" + def get_data(self, request: HttpRequest) -> dict: # Read the pre-compiled collections list and return it verbatim # FIXME Consider using type models to generate from sources @@ -109,6 +113,7 @@ class CollectionsView(BaseJSONView): class IconifyJSONView(BaseJSONView): """Serve the Iconify icon data retrieval API.""" + default_callback: str = "SimpleSVG._loaderCallback" def get_data(self, request: HttpRequest, collection: str) -> dict: @@ -132,6 +137,7 @@ class IconifySVGView(View): It serves a single icon as SVG, allowing some transformations. """ + def get(self, request: HttpRequest, collection: str, name: str) -> HttpResponse: # General retrieval parameters download = request.GET.get("download", "0").lower() in ("1", "true") @@ -153,7 +159,9 @@ class IconifySVGView(View): # Generate SVG from icon icon = icon_set.icons[name] - icon_svg = icon.as_svg(color=color, width=width, height=height, rotate=rotate, flip=flip, box=box) + icon_svg = icon.as_svg( + color=color, width=width, height=height, rotate=rotate, flip=flip, box=box + ) # Form response res = HttpResponse(icon_svg, content_type="image/svg+xml") diff --git a/examples/dj-iconify-server/dj_iconify_server/apps.py b/examples/dj-iconify-server/dj_iconify_server/apps.py index fa675810a02697c454c17ddc1f199e20ae881a79..ec91b4e1b32606dd4efd9b0b880232ea1bc00430 100644 --- a/examples/dj-iconify-server/dj_iconify_server/apps.py +++ b/examples/dj-iconify-server/dj_iconify_server/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class DjIconifyServerConfig(AppConfig): - name = 'dj_iconify_server' + name = "dj_iconify_server" diff --git a/examples/dj-iconify-server/dj_iconify_server/asgi.py b/examples/dj-iconify-server/dj_iconify_server/asgi.py index 58b05eba33896e2f6d3ec9122eac552230a395df..77eb5517cd94bb45889833f688b482ef1b836504 100644 --- a/examples/dj-iconify-server/dj_iconify_server/asgi.py +++ b/examples/dj-iconify-server/dj_iconify_server/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dj_iconify_server.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj_iconify_server.settings") application = get_asgi_application() diff --git a/examples/dj-iconify-server/dj_iconify_server/settings.py b/examples/dj-iconify-server/dj_iconify_server/settings.py index 0ef8c0c32b996f91051de753f83a9eb51477252a..3983c601e5164c056fa24332bec7c8cf6bb05b3f 100644 --- a/examples/dj-iconify-server/dj_iconify_server/settings.py +++ b/examples/dj-iconify-server/dj_iconify_server/settings.py @@ -3,47 +3,47 @@ from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent -SECRET_KEY = 'cv-d4n^*5*$58w474b15pnoqm^fo*10^nv1gg&7q(uaw14o&4p' +SECRET_KEY = "cv-d4n^*5*$58w474b15pnoqm^fo*10^nv1gg&7q(uaw14o&4p" DEBUG = True ALLOWED_HOSTS = [] INSTALLED_APPS = [ - 'django.contrib.staticfiles', - 'django_yarnpkg', - 'dj_iconify.apps.DjIconifyConfig', - 'dj_iconify_server.apps.DjIconifyServerConfig', + "django.contrib.staticfiles", + "django_yarnpkg", + "dj_iconify.apps.DjIconifyConfig", + "dj_iconify_server.apps.DjIconifyServerConfig", ] YARN_INSTALLED_APPS = [ - '@iconify/json', + "@iconify/json", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.middleware.common.CommonMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.middleware.common.CommonMiddleware", ] -ROOT_URLCONF = 'dj_iconify_server.urls' +ROOT_URLCONF = "dj_iconify_server.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", ], }, }, ] -WSGI_APPLICATION = 'dj_iconify_server.wsgi.application' +WSGI_APPLICATION = "dj_iconify_server.wsgi.application" -STATIC_URL = '/static/' +STATIC_URL = "/static/" -NODE_MODULES_ROOT = os.path.join(BASE_DIR, 'node_modules') +NODE_MODULES_ROOT = os.path.join(BASE_DIR, "node_modules") ICONIFY_JSON_ROOT = os.path.join(NODE_MODULES_ROOT, "@iconify", "json") diff --git a/examples/dj-iconify-server/dj_iconify_server/urls.py b/examples/dj-iconify-server/dj_iconify_server/urls.py index 950a621623714510f438b3e180aad6108ed9e139..61c7c5b7c8d18e90648bcdc9ea5f4c3dcaf53292 100644 --- a/examples/dj-iconify-server/dj_iconify_server/urls.py +++ b/examples/dj-iconify-server/dj_iconify_server/urls.py @@ -2,6 +2,6 @@ from django.views.generic import TemplateView from django.urls import include, path urlpatterns = [ - path('icons/', include("dj_iconify.urls")), - path('test.html', TemplateView.as_view(template_name="test.html")), + path("icons/", include("dj_iconify.urls")), + path("test.html", TemplateView.as_view(template_name="test.html")), ] diff --git a/examples/dj-iconify-server/dj_iconify_server/wsgi.py b/examples/dj-iconify-server/dj_iconify_server/wsgi.py index bbabfd54226a9f2368491ba26a18473a782800c3..30c61061f4a7139eba040e569ce244cbdaab7dc5 100644 --- a/examples/dj-iconify-server/dj_iconify_server/wsgi.py +++ b/examples/dj-iconify-server/dj_iconify_server/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dj_iconify_server.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj_iconify_server.settings") application = get_wsgi_application() diff --git a/examples/dj-iconify-server/manage.py b/examples/dj-iconify-server/manage.py index af1da16d1f6c9f3e43f90d57effb682cfa1633c9..8d2bf04326b7b728779123a86f0d13d6067abcf3 100755 --- a/examples/dj-iconify-server/manage.py +++ b/examples/dj-iconify-server/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dj_iconify_server.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj_iconify_server.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main()