File: views.py

package info (click to toggle)
django-iconify 0.4.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 168 kB
  • sloc: python: 462; makefile: 9
file content (184 lines) | stat: -rw-r--r-- 6,916 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
"""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