pax_global_header00006660000000000000000000000064147411661150014520gustar00rootroot0000000000000052 comment=f38b1d2c90b30bfbbff97689d8d111a9ec5d7352 django-iconify-0.4.1/000077500000000000000000000000001474116611500144225ustar00rootroot00000000000000django-iconify-0.4.1/.gitignore000066400000000000000000000010041474116611500164050ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt pip-delete-this-directory.txt # Translations *.mo *.pot # Django stuff: *.log local_settings.py # pyenv .python-version # Environments .env .venv env/ venv/ ENV/ # Editors *~ DEADJOE \#*# # Database db.sqlite3 node_modules/ package.json yarn.lock django-iconify-0.4.1/LICENSE000066400000000000000000000261241474116611500154340ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 [Copyright holder] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. django-iconify-0.4.1/README.rst000066400000000000000000000116151474116611500161150ustar00rootroot00000000000000Iconify API implementation and tools for Django projects ======================================================== This re-usable app hepls integrating `Iconify`_ into Django projects. Iconify is a unified icons framework, providing access to 40,000+ icons from different icon sets. Iconify replaces classical icon fonts, claiming that such fonts would get too large for some icon sets out there. Instead, it provides an API to add icons in SVG format from its collections. `django-iconify`_ eases integration into Django. Iconify, to be performant, uses a server-side API to request icons from (in batches, with transformations applied, etc.). Upstream provides a CDN-served central API as well as self-hosted options written in PHP or Node.js, all of which are undesirable for Django projects. `django-iconify`_ implements the Iconify API as a re-usable Django app. Installation ------------ To add `django-iconify`_ to a project, first add it as dependency to your project, e.g. using `poetry`_:: $ poetry add django-iconify Then, add it to your `INSTALLED_APPS` setting to make its views available later:: INSTALLED_APPS = [ ... "dj_iconify.apps.DjIconifyConfig", ... ] You need to make the `JSON collection`_ available by some means. You can download it manually, or use your favourite asset management library. For instance, you could use `django-yarnpkg`_ to depend on the `@iconify/json` Yarn package:: YARN_INSTALLED_APPS = [ "@iconify/json", ] NODE_MODULES_ROOT = os.path.join(BASE_DIR, "node_modules") No matter which way, finally, you have to configure the path to the collections in your settings:: ICONIFY_JSON_ROOT = os.path.join(NODE_MODULES_ROOT, "@iconify", "json") If you do not use `django-yarnpkg`_, construct the path manually, ot use whatever mechanism your asset manager provides. You can configure which icon collections are available using two settings: ICONIFY_COLLECTIONS_ALLOWED = ["foo", "bar"] This list controls which collections can be used. If it is set to a non-empty list, only the collections listed are allowed. ICONIFY_COLLECTIONS_DISALLOWED = ["foo", "bar"] This list, on the other hand, controls which collections cannot be used. If this list contains values, while `COLLECTIONS_ALLOWED` doesn't, all collections except the listed ones are allowed. The allow/disallow feature can be used in cases where only a limited set of collections should be available due to design principles or for legal reasons. Finally, include the URLs in your `urlpatterns`:: from django.urls import include, path urlpatterns = [ path("icons/", include("dj_iconify.urls")), ] Usage ----- Iconify SVG Framework ~~~~~~~~~~~~~~~~~~~~~ To use the `Iconify SVG Framework`_, get its JavaScript from somewhere (e.g. using `django-yarnpkg`_ again, or directly from the CDN, from your ow nstatic files, or wherever). `django-iconify`_ provides a view that returns a small JavaScript snippet that configures the `Iconify SVG Framework`_ to use its API endpoints. In the following example, we first load this configuration snippet, then include the `Iconify SVG Framework`_ from the CDN (do not do this in production, where data protection matters):: Loading SVG directly ("How to use Iconify in CSS") ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ `django-iconify`_ also implements the direct SVG API. For now, you have to use Django's regular URL reverse resolver to construct an SVG URL, or craft it by hand:: Documentation on what query parameters are supported can be found in the documentation on `How to use Iconify in CSS`_. In the future, a template tag will be available to simplify this. Including SVG in template directly ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *Not implemented yet* In the future, a template tag will be available to render SVG icons directly into the template, which can be helpful in situations where retrieving external resources in undesirable, like HTML e-mails. Example ------- The source distribution as well as the `Git repository`_ contain a full example application that serves the API under `/icons/` and a test page under `/test.html`. It is reduced to a minimal working example for the reader's convenience. .. _Iconify: https://iconify.design/ .. _django-iconify: https://edugit.org/AlekSIS/libs/django-iconify .. _poetry: https://python-poetry.org/ .. _JSON collection: https://github.com/iconify/collections-json .. _django-yarnpkg: https://edugit.org/AlekSIS/libs/django-yarnpkg .. _Iconify SVG Framework: https://docs.iconify.design/implementations/svg-framework/ .. _How to use Iconify in CSS: https://docs.iconify.design/implementations/css.html .. _Git repository: https://edugit.org/AlekSIS/libs/django-iconify django-iconify-0.4.1/dj_iconify/000077500000000000000000000000001474116611500165375ustar00rootroot00000000000000django-iconify-0.4.1/dj_iconify/__init__.py000066400000000000000000000000001474116611500206360ustar00rootroot00000000000000django-iconify-0.4.1/dj_iconify/apps.py000066400000000000000000000001641474116611500200550ustar00rootroot00000000000000from django.apps import AppConfig class DjIconifyConfig(AppConfig): name = "dj_iconify" label = "iconify" django-iconify-0.4.1/dj_iconify/conf.py000066400000000000000000000004721474116611500200410ustar00rootroot00000000000000"""App configuration for django-iconify.""" from django.conf import settings _prefix = "ICONIFY_" JSON_ROOT = getattr(settings, f"{_prefix}JSON_ROOT") COLLECTIONS_ALLOWED = getattr(settings, f"{_prefix}COLLECTIONS_ALLOWED", []) COLLECTIONS_DISALLOWED = getattr(settings, f"{_prefix}COLLECTIONS_DISALLOWED", []) django-iconify-0.4.1/dj_iconify/types.py000066400000000000000000000336331474116611500202650ustar00rootroot00000000000000"""Iconify data types used in API. Documentation: https://docs.iconify.design/types/ """ from typing import Collection, Dict, List, Optional, Sequence, TextIO, Union import json 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 height: Optional[int] = None rotate: Optional[int] = None h_flip: Optional[bool] = None v_flip: Optional[bool] = None def _as_dict_optional(self) -> dict: res = {} if self.left is not None: res["left"] = self.left if self.top is not None: res["top"] = self.top if self.width is not None: res["width"] = self.width if self.height is not None: res["height"] = self.height if self.rotate is not None: res["rotate"] = self.rotate if self.h_flip is not None: res["hFlip"] = self.h_flip if self.v_flip is not None: res["vFlip"] = self.v_flip return res def _from_dict_optional(self, src: dict) -> None: self.left = src.get("left", None) self.top = src.get("top", None) self.width = src.get("width", None) self.height = src.get("height", None) self.rotate = src.get("rotate", None) self.h_flip = src.get("hFlip", None) self.v_flip = src.get("vFlip", None) class IconifyIcon(IconifyOptional): """Single icon as loaded from Iconify JSON data. 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": self = cls() self.body = src["body"] self._name = name self._from_dict_optional(src) self._collection = collection return self def as_dict(self) -> dict: res = { "body": self.body, } res.update(self._as_dict_optional()) return res def get_width(self): """Get the width of the icon. If the icon has an explicit width, it is returned. If not, the width set in the collection is returned, or the default of 16. """ if self.width: return self.width elif self._collection and self._collection.width: return self._collection.height else: return 16 def get_height(self): """Get the height of the icon. If the icon has an explicit height, it is returned. If not, the height set in the collection is returned, or the default of 16. """ if self.height: return self.height elif self._collection and self._collection.height: return self._collection.height 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: """Generate a full SVG of this icon. Some transformations can be applied by passing arguments: width, height - Scale the icon; if only one is set and the other is not (or set to 'auto'), the other is calculated, preserving aspect ratio. Suffixes (i.e. CSS units) are allowed rotate - Either a degress value with 'deg' suffix, or a number from 0 to 4 expressing the number of 90 degreee rotations flip - horizontal, vertical, or both values with comma box - Include a transparent box spanning the whole viewbox Documentation: https://docs.iconify.design/types/iconify-icon.html """ # Original dimensions, the viewbox we use later orig_width, orig_height = self.get_width(), self.get_height() if width and (height is None or height.lower() == "auto"): # Width given, determine height automatically value, unit = split_css_unit(width) height = str(value / (orig_width / orig_height)) + unit elif height and (width is None or width.lower() == "auto"): # Height given, determine width automatically value, unit = split_css_unit(height) width = str(value / (orig_height / orig_width)) + unit elif width is None and height is None: # Neither width nor height given, default to browser text size width, height = "1em", "1em" # Build attributes to inject into element svg_dim_attrs = f'width="{width}" height="{height}"' # SVG root element (copied bluntly from example output on api.iconify.design) head = ( '' ) foot = "" # Build up all transformations, which are added as an SVG group ( element) transform = [] if rotate is not None: # Rotation will be around center of viewbox center_x, center_y = int(orig_width / 2), int(orig_height / 2) if rotate.isnumeric(): # Plain number, calculate degrees in 90deg steps deg = int(rotate) * 90 elif rotate.endswith("deg"): deg = int(rotate[:-3]) transform.append(f"rotate({deg} {center_x} {center_y})") if flip is not None: if isinstance(flip, str): # Split flip attribute if passed verbatim from request flip = flip.split(",") # Seed with no-op values translate_x, translate_y = 0, 0 scale_x, scale_y = 1, 1 if "horizontal" in flip: # Flip around X axis translate_x = orig_width scale_x = -1 if "vertical" in flip: # Flip around Y axis translate_y = orig_height scale_y = -1 # Build transform functions for attribute transform.append(f"translate({translate_x} {translate_y})") transform.append(f"scale({scale_x} {scale_y})") if transform: # Generate a attribute if any transformations were generated transform = " ".join(transform) g = f'', "" else: # use dummy empty strings to make string building easier further down g = "", "" # Body from icon data body = self.body if color is not None: # Color is replaced anywhere it appears as attribute value # FIXME Find a better way to repalce only color values safely body = body.replace('"currentColor"', f'"{color}"') if box: # Add a transparent box spanning the whole viewbox for browsers that do not support viewbox box = f'' else: # Dummy empty string for easier string building further down box = "" # 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 def get_icon(self): """Get the real icon by retrieving it from the parent collection, if any.""" if self._collection: return self._collection.get_icon(self.parent) @classmethod def from_dict(cls, src: dict, collection: Optional["IconifyJSON"] = None) -> "IconifyAlias": self = cls() self.parent = src["parent"] self._from_dict_optional(src) self._collection = collection return self def as_dict(self) -> dict: res = { "parent": self.parent, } res.update(self._as_dict_optional()) return res class IconifyInfo(IconifyOptional): """Meta information on a colelction. 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 samples: Optional[List[IconifyIcon]] category: str palette: bool @property def total(self): """Determine icon count from parent collection.""" if self._collection: return len(self._collection.icons) @classmethod def from_dict(cls, src: dict, collection: Optional["IconifyJSON"] = None) -> "IconifyInfo": self = cls() self.name = src.get("name", None) self.category = src.get("category", None) self.palette = src.get("palette", None) self.author = src.get("author", None) self.license_ = src.get("license", None) self.samples = [collection.get_icon(name) for name in src.get("samples", [])] or None self._from_dict_optional(src) self._collection = collection return self def as_dict(self) -> dict: res = {} if self.name is not None: res["name"] = self.name if self.category is not None: res["category"] = self.category if self.palette is not None: res["palette"] = self.palette if self.author is not None: res["author"] = self.author if self.license_ is not None: res["license"] = self.license_ if self.total is not None: res["total"] = self.total if self.samples is not None: res["samples"] = [icon._name for icon in self.samples if icon is not None] if self._collection is not None: res["uncategorized"] = list(self._collection.icons.keys()) 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]] info: Optional[IconifyInfo] not_found: List[str] def get_icon(self, name: str): """Get an icon by name. First, tries to find a real icon with the name. If none is found, tries to resolve the name from aliases. """ if name in self.icons.keys(): return self.icons[name] elif name in self.aliases.keys(): return self.aliases[name].get_icon() @classmethod 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 these names are loaded (and real icons for aliases). """ if collection is None: # Load from a dummy empty collection collection = {} if only is None: # Construct a list of all names from source collection only = set(collection["icons"].keys()) if "aliases" in collection: only |= set(collection["aliases"].keys()) self = cls() self.prefix = collection["prefix"] self.icons, self.aliases = {}, {} self.not_found = [] for name in only: # Try to find a real icon with the name icon_dict = collection["icons"].get(name, None) if icon_dict: self.icons[name] = IconifyIcon.from_dict(name, icon_dict, collection=self) continue # If we got here, try finding an alias with the name alias_dict = collection["aliases"].get(name, None) 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 ) continue # If we got here, track the we did not find the icon # Undocumented, but the original API server seems to return this field in its # response instead of throwing a 404 error or so self.not_found.append(name) if "info" in collection: self.info = IconifyInfo.from_dict(collection["info"], self) self._from_dict_optional(collection) return self @classmethod def from_file(cls, src_file: Union[str, TextIO] = None, **kwargs) -> "IconifyJSON": """Construct collection by reading a JSON file and calling from_dict.""" if isinstance(src_file, str): with open(src_file, "r") as in_file: src = json.load(in_file) else: src = json.load(src_file) return cls.from_dict(src, **kwargs) def as_dict(self, include_info: bool = False) -> dict: res = { "prefix": self.prefix, "icons": {name: icon.as_dict() for name, icon in self.icons.items()}, "aliases": {name: alias.as_dict() for name, alias in self.aliases.items()}, } if self.not_found: res["not_found"] = self.not_found if self.info and include_info: res["info"] = self.info.as_dict() res.update(self._as_dict_optional()) return res �����������������������������������������������������������������������������������������������������django-iconify-0.4.1/dj_iconify/urls.py�������������������������������������������������������������0000664�0000000�0000000�00000001152�14741166115�0020075�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������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[A-Za-z0-9-]+)\.(?Pjs(on)?)", views.IconifyJSONView.as_view(), name="iconify_json", ), re_path( r"^(?P[A-Za-z0-9-]+)/(?P[A-Za-z0-9-]+)\.svg", views.IconifySVGView.as_view(), name="iconify_svg", ), ] ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������django-iconify-0.4.1/dj_iconify/util.py�������������������������������������������������������������0000664�0000000�0000000�00000002601�14741166115�0020065�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Utility code used by other parts of django-iconify.""" import os import re from .conf import COLLECTIONS_ALLOWED, COLLECTIONS_DISALLOWED, JSON_ROOT def split_css_unit(string: str): """Split string into value and unit. >>> split_css_unit("12px") (12, 'px') >>> split_css_unit("1.5em") (1.5, 'em') >>> split_css_unit("18%") (18, '%') >>> split_css_unit("200") (200, '') """ _value = re.findall("^[0-9.]+", string) value = float(_value[0]) if "." in _value[0] else int(_value[0]) unit = string[len(_value[0]) :] return value, unit def collection_allowed(collection: str) -> bool: """Determine whether a collection is allowed by settings.""" if collection in COLLECTIONS_DISALLOWED: return False if COLLECTIONS_ALLOWED and collection not in COLLECTIONS_ALLOWED: return False return True def icon_choices(collection: str) -> list[tuple[str, str]]: """Get Django model/form choices for icons in one collection.""" from .types import IconifyJSON if not collection_allowed(collection): raise KeyError("The collection %s is disallowed." % collection) # Load icon set through Iconify types collection_file = os.path.join(JSON_ROOT, "json", f"{collection}.json") icon_set = IconifyJSON.from_file(collection_file) return [(name, name) for name in sorted(icon_set.icons.keys())] �������������������������������������������������������������������������������������������������������������������������������django-iconify-0.4.1/dj_iconify/views.py������������������������������������������������������������0000664�0000000�0000000�00000015404�14741166115�0020252�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Iconify API endpoints as views. Documentation: https://docs.iconify.design/sources/api/queries.html """ import json import os import re from django.http import Http404, HttpRequest, HttpResponse, HttpResponseBadRequest from django.urls import reverse from django.views.generic import View from .conf import JSON_ROOT from .types import IconifyJSON from .util import collection_allowed class BaseJSONView(View): """Base view that wraps JSON and JSONP responses. It relies on the following query parameters: callback - name of the JavaScript callback function to call via JSONP pretty - 1 or true to pretty-print JSON (default is condensed) 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: """Generate a dictionary contianing the data to return.""" raise NotImplementedError("You must implement this method in your view.") def get(self, request: HttpRequest, format_: str = "json", *args, **kwargs) -> HttpResponse: """Get the JSON or JSONP response containing the data from the get_data method.""" # For JSONP, the callback name has to be passed if format_ == "js": callback = request.GET.get("callback", self.default_callback) # Client can request pretty-printing of JSON if request.GET.get("pretty", "0").lower() in ("1", "true"): indent = 2 else: indent = None # Call main function implemented by children data = self.get_data(request, *args, **kwargs) # Get result JSON and form response res = json.dumps(data, indent=indent, sort_keys=True) if format_ == "js": # Format is JSONP res = f"{callback}({res})" return HttpResponse(res, content_type="application/javascript") else: # Format is plain JSON return HttpResponse(res, content_type="application/json") class ConfigView(View): """Get JavaScript snippet to conifugre Iconify for our API. 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"}) # Iconify SVG Framework expects just the base path to the API api_base = rev[:-10] # Put together configuration as dict and output as JSON config = { "resources": api_base, } config_json = json.dumps(config) return HttpResponse("var IconifyProviders = {'': " + config_json + "}", content_type="text/javascript") 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): return HttpResponseBadRequest("You must provide a valid prefix name.") # Check whether this collection is allowed if not collection_allowed(collection): raise Http404(f"Collection {collection} not allowed") # Load icon set through Iconify types collection_file = os.path.join(JSON_ROOT, "json", f"{collection}.json") try: icon_set = IconifyJSON.from_file(collection_file) except FileNotFoundError as exc: raise Http404(f"Icon collection {collection} not found") from exc # Return the info member, which holds the data we want return icon_set.info.as_dict() 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 collections_path = os.path.join(JSON_ROOT, "collections.json") with open(collections_path, "r") as collections_file: data = json.load(collections_file) return data class IconifyJSONView(BaseJSONView): """Serve the Iconify icon data retrieval API.""" default_callback: str = "SimpleSVG._loaderCallback" def get_data(self, request: HttpRequest, collection: str) -> dict: # Icon names are passed as comma-separated list icons = request.GET.get("icons", None) if icons is not None: icons = icons.split(",") # Check whether this collection is allowed if not collection_allowed(collection): raise Http404(f"Collection {collection} not allowed") # Load icon set through Iconify types collection_file = os.path.join(JSON_ROOT, "json", f"{collection}.json") try: icon_set = IconifyJSON.from_file(collection_file, only=icons) except FileNotFoundError as exc: raise Http404(f"Icon collection {collection} not found") from exc return icon_set.as_dict() class IconifySVGView(View): """Serve the Iconify SVG retrieval API. 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") box = request.GET.get("box", "0").lower() in ("1", "true") # SVG manipulation parameters color = request.GET.get("color", None) width = request.GET.get("width", None) height = request.GET.get("height", None) rotate = request.GET.get("rotate", None) flip = request.GET.get("flip", None) # Check whether this collection is allowed if not collection_allowed(collection): raise Http404(f"Collection {collection} not allowed") # Load icon set through Iconify types collection_file = os.path.join(JSON_ROOT, "json", f"{collection}.json") try: icon_set = IconifyJSON.from_file(collection_file, only=[name]) except FileNotFoundError as exc: raise Http404(f"Icon collection {collection} not found") from exc # 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 ) # Form response res = HttpResponse(icon_svg, content_type="image/svg+xml") if download: res["Content-Disposition"] = f"attachment; filename={name}.svg" return res ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������django-iconify-0.4.1/examples/����������������������������������������������������������������������0000775�0000000�0000000�00000000000�14741166115�0016240�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������django-iconify-0.4.1/examples/dj-iconify-server/����������������������������������������������������0000775�0000000�0000000�00000000000�14741166115�0021577�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������django-iconify-0.4.1/examples/dj-iconify-server/dj_iconify_server/����������������������������������0000775�0000000�0000000�00000000000�14741166115�0025302�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������django-iconify-0.4.1/examples/dj-iconify-server/dj_iconify_server/__init__.py�����������������������0000664�0000000�0000000�00000000000�14741166115�0027401�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������django-iconify-0.4.1/examples/dj-iconify-server/dj_iconify_server/apps.py���������������������������0000664�0000000�0000000�00000000153�14741166115�0026616�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from django.apps import AppConfig class DjIconifyServerConfig(AppConfig): name = "dj_iconify_server" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������django-iconify-0.4.1/examples/dj-iconify-server/dj_iconify_server/asgi.py���������������������������0000664�0000000�0000000�00000000633�14741166115�0026601�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" ASGI config for dj_iconify_server project. It exposes the ASGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ """ import os from django.core.asgi import get_asgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj_iconify_server.settings") application = get_asgi_application() �����������������������������������������������������������������������������������������������������django-iconify-0.4.1/examples/dj-iconify-server/dj_iconify_server/settings.py�����������������������0000664�0000000�0000000�00000002152�14741166115�0027514�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent 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", ] YARN_INSTALLED_APPS = [ "@iconify/json", ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.middleware.common.CommonMiddleware", ] 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", ], }, }, ] WSGI_APPLICATION = "dj_iconify_server.wsgi.application" STATIC_URL = "/static/" NODE_MODULES_ROOT = os.path.join(BASE_DIR, "node_modules") ICONIFY_JSON_ROOT = os.path.join(NODE_MODULES_ROOT, "@iconify", "json") ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������django-iconify-0.4.1/examples/dj-iconify-server/dj_iconify_server/templates/������������������������0000775�0000000�0000000�00000000000�14741166115�0027300�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������django-iconify-0.4.1/examples/dj-iconify-server/dj_iconify_server/templates/test.html���������������0000664�0000000�0000000�00000001064�14741166115�0031146�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Example of Iconify usage in Django django-iconify-0.4.1/examples/dj-iconify-server/dj_iconify_server/urls.py000066400000000000000000000003371474116611500266440ustar00rootroot00000000000000from 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")), ] django-iconify-0.4.1/examples/dj-iconify-server/dj_iconify_server/wsgi.py000066400000000000000000000006331474116611500266270ustar00rootroot00000000000000""" WSGI config for dj_iconify_server project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj_iconify_server.settings") application = get_wsgi_application() django-iconify-0.4.1/examples/dj-iconify-server/manage.py000077500000000000000000000012411474116611500234020ustar00rootroot00000000000000#!/usr/bin/env python """Django's command-line utility for administrative tasks.""" import os import sys def main(): """Run administrative tasks.""" os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dj_iconify_server.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) if __name__ == "__main__": main() django-iconify-0.4.1/examples/dj-iconify-server/pyproject.toml000066400000000000000000000006561474116611500245220ustar00rootroot00000000000000[tool.poetry] name = "dj-iconify-server" version = "0.1.0" description = "" authors = ["Dominik George "] license = "MIT" packages = [ { include = "dj_iconify_server" }, ] [tool.poetry.dependencies] python = "^3.7" django-yarnpkg = "^6.0" django-iconify = {path = "../.."} [tool.poetry.dev-dependencies] [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" django-iconify-0.4.1/pyproject.toml000066400000000000000000000014071474116611500173400ustar00rootroot00000000000000[tool.poetry] name = "django-iconify" version = "0.4.1" description = "Iconify API implementation and tools for Django projects" authors = ["Dominik George "] license = "Apache-2.0" readme = "README.rst" repository = "https://edugit.org/AlekSIS/libs/django-iconify" keywords = ["iconify", "icons", "svg", "django"] classifiers = [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", ] packages = [ { include = "dj_iconify" }, ] include = ["LICENSE", "examples/"] [tool.poetry.dependencies] python = "^3.10" Django = ">4.2,<6.0" [tool.poetry.dev-dependencies] [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" django-iconify-0.4.1/renovate.json000066400000000000000000000001531474116611500171370ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" ] }