We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
$computedClass warning might be too sensitive
### Observed behavior

We added a warning:

> Used the $computedClass method for a class definition without any pseudo selectors ...  Please use a v-bind:style directive instead.

However I think there might be some cases where it's necessary to use this even without pseudo-selectors:

* I've been having a bit of trouble with CSS specificity rules, which are different depending on whether the style is defined using `:style` vs `:class`. It has been a bit challenging to get the two to have the right precedence related to each other. (Side-note - I think source maps would have helped here)
* There might be situations where a class is necessary in non-pseudo-selector cases - for example when defining the classes in vue transitions.



### Expected behavior

we should investigate these cases and decide whether the warning is appropriate, or if there was another way of implementing

### User-facing consequences

N/A

### Errors and logs

![image](https://user-images.githubusercontent.com/2367265/51809236-95a8ac00-2253-11e9-9a29-225b3b7d0123.png)


### Context

0.12

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kolibri/core/hooks.py`
Content:
```
1 """
2 Kolibri Core hooks
3 ------------------
4 
5 WIP! Many applications are supposed to live inside the core namespace to make
6 it explicit that they are part of the core.
7 
8 Do we put all their hooks in one module or should each app have its own hooks
9 module?
10 
11 Anyways, for now to get hooks started, we have some defined here...
12 """
13 from __future__ import absolute_import
14 from __future__ import print_function
15 from __future__ import unicode_literals
16 
17 import logging
18 
19 from kolibri.plugins.hooks import KolibriHook
20 from kolibri.plugins.utils import plugin_url
21 
22 logger = logging.getLogger(__name__)
23 
24 
25 class NavigationHook(KolibriHook):
26 
27     # : A string label for the menu item
28     label = "Untitled"
29 
30     # : A string or lazy proxy for the url
31     url = "/"
32 
33     # Set this to True so that any time this is mixed in with a
34     # frontend asset hook, the resulting frontend code will be rendered inline.
35     inline = True
36 
37     def get_menu(self):
38         menu = {}
39         for hook in self.registered_hooks:
40             menu[hook.label] = self.url
41         return menu
42 
43     class Meta:
44 
45         abstract = True
46 
47 
48 class RoleBasedRedirectHook(KolibriHook):
49     # User role to redirect for
50     role = None
51 
52     # URL to redirect to
53     url = None
54 
55     # Special flag to only redirect on first login
56     # Default to False
57     first_login = False
58 
59     def plugin_url(self, plugin_class, url_name):
60         return plugin_url(plugin_class, url_name)
61 
62     class Meta:
63 
64         abstract = True
65
```

Path: `kolibri/core/templatetags/kolibri_tags.py`
Content:
```
1 """
2 Kolibri template tags
3 =====================
4 """
5 from __future__ import absolute_import
6 from __future__ import print_function
7 from __future__ import unicode_literals
8 
9 import copy
10 import json
11 import logging
12 import re
13 
14 from django import template
15 from django.conf import settings
16 from django.contrib.staticfiles.templatetags.staticfiles import static
17 from django.core.urlresolvers import get_resolver
18 from django.core.urlresolvers import get_script_prefix
19 from django.core.urlresolvers import resolve
20 from django.core.urlresolvers import reverse
21 from django.template.loader import render_to_string
22 from django.utils.html import mark_safe
23 from django.utils.translation import get_language
24 from django.utils.translation import get_language_bidi
25 from django.utils.translation import get_language_info
26 from django_js_reverse.core import prepare_url_list
27 from django_js_reverse.rjsmin import jsmin
28 from rest_framework.renderers import JSONRenderer
29 from six import iteritems
30 
31 import kolibri
32 from kolibri.core.device.models import ContentCacheKey
33 from kolibri.core.hooks import NavigationHook
34 from kolibri.core.webpack.utils import webpack_asset_render
35 from kolibri.utils import conf
36 from kolibri.utils import i18n
37 
38 register = template.Library()
39 
40 logger = logging.getLogger(__name__)
41 
42 
43 @register.simple_tag()
44 def kolibri_content_cache_key():
45     js = """
46     <script>
47       var contentCacheKey = '{cache_key}';
48     </script>
49     """.format(
50         cache_key=ContentCacheKey.get_cache_key()
51     )
52     return mark_safe(js)
53 
54 
55 @register.simple_tag(takes_context=True)
56 def kolibri_language_globals(context):
57 
58     template = """
59     <script>
60       var languageCode = '{lang_code}';
61       var languageDir = '{lang_dir}';
62       var languages = JSON.parse('{languages}');
63       var fullCSSFileModern = '{full_css_file_modern}?v={version}';
64       var fullCSSFileBasic = '{full_css_file_basic}?v={version}';
65     </script>
66     <link type="text/css" href="{common_css_file}?v={version}" rel="stylesheet"/>
67     <link type="text/css" href="{subset_css_file}?v={version}" rel="stylesheet"/>
68     """
69 
70     language_code = get_language()
71     lang_dir = "rtl" if get_language_bidi() else "ltr"
72 
73     languages = {}
74     for code, language_name in settings.LANGUAGES:
75         lang_info = next(
76             (
77                 lang
78                 for lang in i18n.KOLIBRI_SUPPORTED_LANGUAGES
79                 if lang["intl_code"] == code
80             ),
81             None,
82         )
83         languages[code] = {
84             # Format to match the schema of the content Language model
85             "id": code,
86             "lang_name": language_name,
87             "english_name": lang_info["english_name"]
88             if lang_info
89             else get_language_info(code)["name"],
90             "lang_direction": get_language_info(code)["bidi"],
91         }
92 
93     common_file = static("assets/fonts/noto-common.css")
94     subset_file = static("assets/fonts/noto-subset.{}.css".format(language_code))
95     full_file = "assets/fonts/noto-full.{}.{}.css"
96     full_file_modern = static(full_file.format(language_code, "modern"))
97     full_file_basic = static(full_file.format(language_code, "basic"))
98 
99     return mark_safe(
100         template.format(
101             lang_code=language_code,
102             lang_dir=lang_dir,
103             languages=json.dumps(languages),
104             common_css_file=common_file,
105             subset_css_file=subset_file,
106             full_css_file_modern=full_file_modern,
107             full_css_file_basic=full_file_basic,
108             # Temporary cache busting strategy.
109             # Would be better to use ManifestStaticFilesStorage
110             version=kolibri.__version__,
111         )
112     )
113 
114 
115 @register.simple_tag()
116 def kolibri_navigation_actions():
117     """
118     A tag to include an initial JS-object to bootstrap nav action data into the app.
119     :return: An html string
120     """
121     return webpack_asset_render(NavigationHook)
122 
123 
124 @register.simple_tag(takes_context=True)
125 def kolibri_set_urls(context):
126     # Modified from:
127     # https://github.com/ierror/django-js-reverse/blob/master/django_js_reverse/core.py#L101
128     js_global_object_name = "window"
129     js_var_name = "kolibriUrls"
130     script_prefix = get_script_prefix()
131 
132     if "request" in context:
133         default_urlresolver = get_resolver(getattr(context["request"], "urlconf", None))
134     else:
135         default_urlresolver = get_resolver(None)
136 
137     js = render_to_string(
138         "django_js_reverse/urls_js.tpl",
139         {
140             "urls": sorted(list(prepare_url_list(default_urlresolver))),
141             "url_prefix": script_prefix,
142             "js_var_name": js_var_name,
143             "js_global_object_name": js_global_object_name,
144         },
145     )
146 
147     js = jsmin(js)
148 
149     js = (
150         """<script type="text/javascript">"""
151         + js
152         + """
153         {global_object}.staticUrl = '{static_url}';
154         </script>
155         """.format(
156             global_object=js_global_object_name, static_url=settings.STATIC_URL
157         )
158     )
159     return mark_safe(js)
160 
161 
162 @register.simple_tag(takes_context=True)
163 def kolibri_bootstrap_model(context, base_name, api_resource, **kwargs):
164     response, kwargs = _kolibri_bootstrap_helper(
165         context, base_name, api_resource, "detail", **kwargs
166     )
167     html = (
168         "<script type='text/javascript'>"
169         "var model = {0}.resources.{1}.createModel(JSON.parse({2}));"
170         "model.synced = true;"
171         "</script>".format(
172             conf.KOLIBRI_CORE_JS_NAME,
173             api_resource,
174             json.dumps(JSONRenderer().render(response.data).decode("utf-8")),
175         )
176     )
177     return mark_safe(html)
178 
179 
180 @register.simple_tag(takes_context=True)
181 def kolibri_bootstrap_collection(context, base_name, api_resource, **kwargs):
182     response, kwargs = _kolibri_bootstrap_helper(
183         context, base_name, api_resource, "list", **kwargs
184     )
185     html = (
186         "<script type='text/javascript'>"
187         "var collection = {0}.resources.{1}.createCollection({2}, JSON.parse({3}));"
188         "collection.synced = true;"
189         "</script>".format(
190             conf.KOLIBRI_CORE_JS_NAME,
191             api_resource,
192             json.dumps(kwargs),
193             json.dumps(JSONRenderer().render(response.data).decode("utf-8")),
194         )
195     )
196     return mark_safe(html)
197 
198 
199 def _replace_dict_values(check, replace, dict):
200     for (key, value) in iteritems(dict):
201         if dict[key] is check:
202             dict[key] = replace
203 
204 
205 def _kolibri_bootstrap_helper(context, base_name, api_resource, route, **kwargs):
206     reversal = dict()
207     kwargs_check = "kwargs_"
208     # remove prepended string and matching items from kwargs
209     for key in list(kwargs.keys()):
210         if kwargs_check in key:
211             item = kwargs.pop(key)
212             key = re.sub(kwargs_check, "", key)
213             reversal[key] = item
214     view, view_args, view_kwargs = resolve(
215         reverse("kolibri:core:{0}-{1}".format(base_name, route), kwargs=reversal)
216     )
217     # switch out None temporarily because invalid filtering and caching can occur
218     _replace_dict_values(None, str(""), kwargs)
219     request = copy.copy(context["request"])
220     request.GET = request.GET.copy()
221     for key in kwargs:
222         request.GET[key] = kwargs[key]
223     response = view(request, **view_kwargs)
224     _replace_dict_values(str(""), None, kwargs)
225     return response, kwargs
226 
227 
228 @register.simple_tag()
229 def kolibri_sentry_error_reporting():
230 
231     if not conf.OPTIONS["Debug"]["SENTRY_FRONTEND_DSN"]:
232         return ""
233 
234     template = """
235       <script>
236         var sentryDSN = '{dsn}';
237         var sentryEnv = '{env}';
238       </script>
239     """
240     return mark_safe(
241         template.format(
242             dsn=conf.OPTIONS["Debug"]["SENTRY_FRONTEND_DSN"],
243             env=conf.OPTIONS["Debug"]["SENTRY_ENVIRONMENT"],
244         )
245     )
246
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kolibri/core/hooks.py b/kolibri/core/hooks.py
--- a/kolibri/core/hooks.py
+++ b/kolibri/core/hooks.py
@@ -15,7 +15,9 @@
 from __future__ import unicode_literals
 
 import logging
+import warnings
 
+from kolibri.plugins.hooks import abstract_method
 from kolibri.plugins.hooks import KolibriHook
 from kolibri.plugins.utils import plugin_url
 
@@ -62,3 +64,47 @@
     class Meta:
 
         abstract = True
+
+
+class MultipleThemesWarning(UserWarning):
+    pass
+
+
+class ThemeHook(KolibriHook):
+    """
+    A hook to allow custom theming of Kolibri
+    """
+
+    class Meta:
+
+        abstract = True
+
+    @property
+    @abstract_method
+    def theme(self):
+        default = {
+            # Whether to show the Kolibri log
+            # Boolean
+            "showKolibriLogo": True,
+            # URL for custom logo
+            "customLogoURL": None,
+            # URL for custom login background image
+            "splashBackgroundURL": None,
+            # Color Palette specification
+            "paletteColors": {},
+            # Brand Color specification
+            "brandColors": {},
+            # Mapping from colors to particular usage
+            "tokenMapping": {},
+        }
+        theme = {}
+        once = False
+        for hook in self.registered_hooks:
+            if once:
+                warnings.warn("Multiple themes defined by plugins, ignoring all themes")
+                return default
+            for key in default:
+                theme[key] = getattr(hook, key, theme[key])
+            once = True
+
+        return theme or default
diff --git a/kolibri/core/templatetags/kolibri_tags.py b/kolibri/core/templatetags/kolibri_tags.py
--- a/kolibri/core/templatetags/kolibri_tags.py
+++ b/kolibri/core/templatetags/kolibri_tags.py
@@ -31,6 +31,7 @@
 import kolibri
 from kolibri.core.device.models import ContentCacheKey
 from kolibri.core.hooks import NavigationHook
+from kolibri.core.hooks import ThemeHook
 from kolibri.core.webpack.utils import webpack_asset_render
 from kolibri.utils import conf
 from kolibri.utils import i18n
@@ -121,6 +122,20 @@
     return webpack_asset_render(NavigationHook)
 
 
+@register.simple_tag()
+def kolibri_theme():
+    """
+    A tag to include a theme configuration object to add custom theming to Kolibri.
+    :return: An html string
+    """
+    template = """
+    <script>
+      var customTheme = JSON.parse('{theme}');
+    </script>
+    """
+    return mark_safe(template.format(theme=json.dumps(ThemeHook().theme)))
+
+
 @register.simple_tag(takes_context=True)
 def kolibri_set_urls(context):
     # Modified from:

{"golden_diff": "diff --git a/kolibri/core/hooks.py b/kolibri/core/hooks.py\n--- a/kolibri/core/hooks.py\n+++ b/kolibri/core/hooks.py\n@@ -15,7 +15,9 @@\n from __future__ import unicode_literals\n \n import logging\n+import warnings\n \n+from kolibri.plugins.hooks import abstract_method\n from kolibri.plugins.hooks import KolibriHook\n from kolibri.plugins.utils import plugin_url\n \n@@ -62,3 +64,47 @@\n     class Meta:\n \n         abstract = True\n+\n+\n+class MultipleThemesWarning(UserWarning):\n+    pass\n+\n+\n+class ThemeHook(KolibriHook):\n+    \"\"\"\n+    A hook to allow custom theming of Kolibri\n+    \"\"\"\n+\n+    class Meta:\n+\n+        abstract = True\n+\n+    @property\n+    @abstract_method\n+    def theme(self):\n+        default = {\n+            # Whether to show the Kolibri log\n+            # Boolean\n+            \"showKolibriLogo\": True,\n+            # URL for custom logo\n+            \"customLogoURL\": None,\n+            # URL for custom login background image\n+            \"splashBackgroundURL\": None,\n+            # Color Palette specification\n+            \"paletteColors\": {},\n+            # Brand Color specification\n+            \"brandColors\": {},\n+            # Mapping from colors to particular usage\n+            \"tokenMapping\": {},\n+        }\n+        theme = {}\n+        once = False\n+        for hook in self.registered_hooks:\n+            if once:\n+                warnings.warn(\"Multiple themes defined by plugins, ignoring all themes\")\n+                return default\n+            for key in default:\n+                theme[key] = getattr(hook, key, theme[key])\n+            once = True\n+\n+        return theme or default\ndiff --git a/kolibri/core/templatetags/kolibri_tags.py b/kolibri/core/templatetags/kolibri_tags.py\n--- a/kolibri/core/templatetags/kolibri_tags.py\n+++ b/kolibri/core/templatetags/kolibri_tags.py\n@@ -31,6 +31,7 @@\n import kolibri\n from kolibri.core.device.models import ContentCacheKey\n from kolibri.core.hooks import NavigationHook\n+from kolibri.core.hooks import ThemeHook\n from kolibri.core.webpack.utils import webpack_asset_render\n from kolibri.utils import conf\n from kolibri.utils import i18n\n@@ -121,6 +122,20 @@\n     return webpack_asset_render(NavigationHook)\n \n \n+@register.simple_tag()\n+def kolibri_theme():\n+    \"\"\"\n+    A tag to include a theme configuration object to add custom theming to Kolibri.\n+    :return: An html string\n+    \"\"\"\n+    template = \"\"\"\n+    <script>\n+      var customTheme = JSON.parse('{theme}');\n+    </script>\n+    \"\"\"\n+    return mark_safe(template.format(theme=json.dumps(ThemeHook().theme)))\n+\n+\n @register.simple_tag(takes_context=True)\n def kolibri_set_urls(context):\n     # Modified from:\n", "issue": "$computedClass warning might be too sensitive\n### Observed behavior\r\n\r\nWe added a warning:\r\n\r\n> Used the $computedClass method for a class definition without any pseudo selectors ...  Please use a v-bind:style directive instead.\r\n\r\nHowever I think there might be some cases where it's necessary to use this even without pseudo-selectors:\r\n\r\n* I've been having a bit of trouble with CSS specificity rules, which are different depending on whether the style is defined using `:style` vs `:class`. It has been a bit challenging to get the two to have the right precedence related to each other. (Side-note - I think source maps would have helped here)\r\n* There might be situations where a class is necessary in non-pseudo-selector cases - for example when defining the classes in vue transitions.\r\n\r\n\r\n\r\n### Expected behavior\r\n\r\nwe should investigate these cases and decide whether the warning is appropriate, or if there was another way of implementing\r\n\r\n### User-facing consequences\r\n\r\nN/A\r\n\r\n### Errors and logs\r\n\r\n![image](https://user-images.githubusercontent.com/2367265/51809236-95a8ac00-2253-11e9-9a29-225b3b7d0123.png)\r\n\r\n\r\n### Context\r\n\r\n0.12\n", "before_files": [{"content": "\"\"\"\nKolibri Core hooks\n------------------\n\nWIP! Many applications are supposed to live inside the core namespace to make\nit explicit that they are part of the core.\n\nDo we put all their hooks in one module or should each app have its own hooks\nmodule?\n\nAnyways, for now to get hooks started, we have some defined here...\n\"\"\"\nfrom __future__ import absolute_import\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport logging\n\nfrom kolibri.plugins.hooks import KolibriHook\nfrom kolibri.plugins.utils import plugin_url\n\nlogger = logging.getLogger(__name__)\n\n\nclass NavigationHook(KolibriHook):\n\n    # : A string label for the menu item\n    label = \"Untitled\"\n\n    # : A string or lazy proxy for the url\n    url = \"/\"\n\n    # Set this to True so that any time this is mixed in with a\n    # frontend asset hook, the resulting frontend code will be rendered inline.\n    inline = True\n\n    def get_menu(self):\n        menu = {}\n        for hook in self.registered_hooks:\n            menu[hook.label] = self.url\n        return menu\n\n    class Meta:\n\n        abstract = True\n\n\nclass RoleBasedRedirectHook(KolibriHook):\n    # User role to redirect for\n    role = None\n\n    # URL to redirect to\n    url = None\n\n    # Special flag to only redirect on first login\n    # Default to False\n    first_login = False\n\n    def plugin_url(self, plugin_class, url_name):\n        return plugin_url(plugin_class, url_name)\n\n    class Meta:\n\n        abstract = True\n", "path": "kolibri/core/hooks.py"}, {"content": "\"\"\"\nKolibri template tags\n=====================\n\"\"\"\nfrom __future__ import absolute_import\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport copy\nimport json\nimport logging\nimport re\n\nfrom django import template\nfrom django.conf import settings\nfrom django.contrib.staticfiles.templatetags.staticfiles import static\nfrom django.core.urlresolvers import get_resolver\nfrom django.core.urlresolvers import get_script_prefix\nfrom django.core.urlresolvers import resolve\nfrom django.core.urlresolvers import reverse\nfrom django.template.loader import render_to_string\nfrom django.utils.html import mark_safe\nfrom django.utils.translation import get_language\nfrom django.utils.translation import get_language_bidi\nfrom django.utils.translation import get_language_info\nfrom django_js_reverse.core import prepare_url_list\nfrom django_js_reverse.rjsmin import jsmin\nfrom rest_framework.renderers import JSONRenderer\nfrom six import iteritems\n\nimport kolibri\nfrom kolibri.core.device.models import ContentCacheKey\nfrom kolibri.core.hooks import NavigationHook\nfrom kolibri.core.webpack.utils import webpack_asset_render\nfrom kolibri.utils import conf\nfrom kolibri.utils import i18n\n\nregister = template.Library()\n\nlogger = logging.getLogger(__name__)\n\n\n@register.simple_tag()\ndef kolibri_content_cache_key():\n    js = \"\"\"\n    <script>\n      var contentCacheKey = '{cache_key}';\n    </script>\n    \"\"\".format(\n        cache_key=ContentCacheKey.get_cache_key()\n    )\n    return mark_safe(js)\n\n\n@register.simple_tag(takes_context=True)\ndef kolibri_language_globals(context):\n\n    template = \"\"\"\n    <script>\n      var languageCode = '{lang_code}';\n      var languageDir = '{lang_dir}';\n      var languages = JSON.parse('{languages}');\n      var fullCSSFileModern = '{full_css_file_modern}?v={version}';\n      var fullCSSFileBasic = '{full_css_file_basic}?v={version}';\n    </script>\n    <link type=\"text/css\" href=\"{common_css_file}?v={version}\" rel=\"stylesheet\"/>\n    <link type=\"text/css\" href=\"{subset_css_file}?v={version}\" rel=\"stylesheet\"/>\n    \"\"\"\n\n    language_code = get_language()\n    lang_dir = \"rtl\" if get_language_bidi() else \"ltr\"\n\n    languages = {}\n    for code, language_name in settings.LANGUAGES:\n        lang_info = next(\n            (\n                lang\n                for lang in i18n.KOLIBRI_SUPPORTED_LANGUAGES\n                if lang[\"intl_code\"] == code\n            ),\n            None,\n        )\n        languages[code] = {\n            # Format to match the schema of the content Language model\n            \"id\": code,\n            \"lang_name\": language_name,\n            \"english_name\": lang_info[\"english_name\"]\n            if lang_info\n            else get_language_info(code)[\"name\"],\n            \"lang_direction\": get_language_info(code)[\"bidi\"],\n        }\n\n    common_file = static(\"assets/fonts/noto-common.css\")\n    subset_file = static(\"assets/fonts/noto-subset.{}.css\".format(language_code))\n    full_file = \"assets/fonts/noto-full.{}.{}.css\"\n    full_file_modern = static(full_file.format(language_code, \"modern\"))\n    full_file_basic = static(full_file.format(language_code, \"basic\"))\n\n    return mark_safe(\n        template.format(\n            lang_code=language_code,\n            lang_dir=lang_dir,\n            languages=json.dumps(languages),\n            common_css_file=common_file,\n            subset_css_file=subset_file,\n            full_css_file_modern=full_file_modern,\n            full_css_file_basic=full_file_basic,\n            # Temporary cache busting strategy.\n            # Would be better to use ManifestStaticFilesStorage\n            version=kolibri.__version__,\n        )\n    )\n\n\n@register.simple_tag()\ndef kolibri_navigation_actions():\n    \"\"\"\n    A tag to include an initial JS-object to bootstrap nav action data into the app.\n    :return: An html string\n    \"\"\"\n    return webpack_asset_render(NavigationHook)\n\n\n@register.simple_tag(takes_context=True)\ndef kolibri_set_urls(context):\n    # Modified from:\n    # https://github.com/ierror/django-js-reverse/blob/master/django_js_reverse/core.py#L101\n    js_global_object_name = \"window\"\n    js_var_name = \"kolibriUrls\"\n    script_prefix = get_script_prefix()\n\n    if \"request\" in context:\n        default_urlresolver = get_resolver(getattr(context[\"request\"], \"urlconf\", None))\n    else:\n        default_urlresolver = get_resolver(None)\n\n    js = render_to_string(\n        \"django_js_reverse/urls_js.tpl\",\n        {\n            \"urls\": sorted(list(prepare_url_list(default_urlresolver))),\n            \"url_prefix\": script_prefix,\n            \"js_var_name\": js_var_name,\n            \"js_global_object_name\": js_global_object_name,\n        },\n    )\n\n    js = jsmin(js)\n\n    js = (\n        \"\"\"<script type=\"text/javascript\">\"\"\"\n        + js\n        + \"\"\"\n        {global_object}.staticUrl = '{static_url}';\n        </script>\n        \"\"\".format(\n            global_object=js_global_object_name, static_url=settings.STATIC_URL\n        )\n    )\n    return mark_safe(js)\n\n\n@register.simple_tag(takes_context=True)\ndef kolibri_bootstrap_model(context, base_name, api_resource, **kwargs):\n    response, kwargs = _kolibri_bootstrap_helper(\n        context, base_name, api_resource, \"detail\", **kwargs\n    )\n    html = (\n        \"<script type='text/javascript'>\"\n        \"var model = {0}.resources.{1}.createModel(JSON.parse({2}));\"\n        \"model.synced = true;\"\n        \"</script>\".format(\n            conf.KOLIBRI_CORE_JS_NAME,\n            api_resource,\n            json.dumps(JSONRenderer().render(response.data).decode(\"utf-8\")),\n        )\n    )\n    return mark_safe(html)\n\n\n@register.simple_tag(takes_context=True)\ndef kolibri_bootstrap_collection(context, base_name, api_resource, **kwargs):\n    response, kwargs = _kolibri_bootstrap_helper(\n        context, base_name, api_resource, \"list\", **kwargs\n    )\n    html = (\n        \"<script type='text/javascript'>\"\n        \"var collection = {0}.resources.{1}.createCollection({2}, JSON.parse({3}));\"\n        \"collection.synced = true;\"\n        \"</script>\".format(\n            conf.KOLIBRI_CORE_JS_NAME,\n            api_resource,\n            json.dumps(kwargs),\n            json.dumps(JSONRenderer().render(response.data).decode(\"utf-8\")),\n        )\n    )\n    return mark_safe(html)\n\n\ndef _replace_dict_values(check, replace, dict):\n    for (key, value) in iteritems(dict):\n        if dict[key] is check:\n            dict[key] = replace\n\n\ndef _kolibri_bootstrap_helper(context, base_name, api_resource, route, **kwargs):\n    reversal = dict()\n    kwargs_check = \"kwargs_\"\n    # remove prepended string and matching items from kwargs\n    for key in list(kwargs.keys()):\n        if kwargs_check in key:\n            item = kwargs.pop(key)\n            key = re.sub(kwargs_check, \"\", key)\n            reversal[key] = item\n    view, view_args, view_kwargs = resolve(\n        reverse(\"kolibri:core:{0}-{1}\".format(base_name, route), kwargs=reversal)\n    )\n    # switch out None temporarily because invalid filtering and caching can occur\n    _replace_dict_values(None, str(\"\"), kwargs)\n    request = copy.copy(context[\"request\"])\n    request.GET = request.GET.copy()\n    for key in kwargs:\n        request.GET[key] = kwargs[key]\n    response = view(request, **view_kwargs)\n    _replace_dict_values(str(\"\"), None, kwargs)\n    return response, kwargs\n\n\n@register.simple_tag()\ndef kolibri_sentry_error_reporting():\n\n    if not conf.OPTIONS[\"Debug\"][\"SENTRY_FRONTEND_DSN\"]:\n        return \"\"\n\n    template = \"\"\"\n      <script>\n        var sentryDSN = '{dsn}';\n        var sentryEnv = '{env}';\n      </script>\n    \"\"\"\n    return mark_safe(\n        template.format(\n            dsn=conf.OPTIONS[\"Debug\"][\"SENTRY_FRONTEND_DSN\"],\n            env=conf.OPTIONS[\"Debug\"][\"SENTRY_ENVIRONMENT\"],\n        )\n    )\n", "path": "kolibri/core/templatetags/kolibri_tags.py"}], "after_files": [{"content": "\"\"\"\nKolibri Core hooks\n------------------\n\nWIP! Many applications are supposed to live inside the core namespace to make\nit explicit that they are part of the core.\n\nDo we put all their hooks in one module or should each app have its own hooks\nmodule?\n\nAnyways, for now to get hooks started, we have some defined here...\n\"\"\"\nfrom __future__ import absolute_import\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport logging\nimport warnings\n\nfrom kolibri.plugins.hooks import abstract_method\nfrom kolibri.plugins.hooks import KolibriHook\nfrom kolibri.plugins.utils import plugin_url\n\nlogger = logging.getLogger(__name__)\n\n\nclass NavigationHook(KolibriHook):\n\n    # : A string label for the menu item\n    label = \"Untitled\"\n\n    # : A string or lazy proxy for the url\n    url = \"/\"\n\n    # Set this to True so that any time this is mixed in with a\n    # frontend asset hook, the resulting frontend code will be rendered inline.\n    inline = True\n\n    def get_menu(self):\n        menu = {}\n        for hook in self.registered_hooks:\n            menu[hook.label] = self.url\n        return menu\n\n    class Meta:\n\n        abstract = True\n\n\nclass RoleBasedRedirectHook(KolibriHook):\n    # User role to redirect for\n    role = None\n\n    # URL to redirect to\n    url = None\n\n    # Special flag to only redirect on first login\n    # Default to False\n    first_login = False\n\n    def plugin_url(self, plugin_class, url_name):\n        return plugin_url(plugin_class, url_name)\n\n    class Meta:\n\n        abstract = True\n\n\nclass MultipleThemesWarning(UserWarning):\n    pass\n\n\nclass ThemeHook(KolibriHook):\n    \"\"\"\n    A hook to allow custom theming of Kolibri\n    \"\"\"\n\n    class Meta:\n\n        abstract = True\n\n    @property\n    @abstract_method\n    def theme(self):\n        default = {\n            # Whether to show the Kolibri log\n            # Boolean\n            \"showKolibriLogo\": True,\n            # URL for custom logo\n            \"customLogoURL\": None,\n            # URL for custom login background image\n            \"splashBackgroundURL\": None,\n            # Color Palette specification\n            \"paletteColors\": {},\n            # Brand Color specification\n            \"brandColors\": {},\n            # Mapping from colors to particular usage\n            \"tokenMapping\": {},\n        }\n        theme = {}\n        once = False\n        for hook in self.registered_hooks:\n            if once:\n                warnings.warn(\"Multiple themes defined by plugins, ignoring all themes\")\n                return default\n            for key in default:\n                theme[key] = getattr(hook, key, theme[key])\n            once = True\n\n        return theme or default\n", "path": "kolibri/core/hooks.py"}, {"content": "\"\"\"\nKolibri template tags\n=====================\n\"\"\"\nfrom __future__ import absolute_import\nfrom __future__ import print_function\nfrom __future__ import unicode_literals\n\nimport copy\nimport json\nimport logging\nimport re\n\nfrom django import template\nfrom django.conf import settings\nfrom django.contrib.staticfiles.templatetags.staticfiles import static\nfrom django.core.urlresolvers import get_resolver\nfrom django.core.urlresolvers import get_script_prefix\nfrom django.core.urlresolvers import resolve\nfrom django.core.urlresolvers import reverse\nfrom django.template.loader import render_to_string\nfrom django.utils.html import mark_safe\nfrom django.utils.translation import get_language\nfrom django.utils.translation import get_language_bidi\nfrom django.utils.translation import get_language_info\nfrom django_js_reverse.core import prepare_url_list\nfrom django_js_reverse.rjsmin import jsmin\nfrom rest_framework.renderers import JSONRenderer\nfrom six import iteritems\n\nimport kolibri\nfrom kolibri.core.device.models import ContentCacheKey\nfrom kolibri.core.hooks import NavigationHook\nfrom kolibri.core.hooks import ThemeHook\nfrom kolibri.core.webpack.utils import webpack_asset_render\nfrom kolibri.utils import conf\nfrom kolibri.utils import i18n\n\nregister = template.Library()\n\nlogger = logging.getLogger(__name__)\n\n\n@register.simple_tag()\ndef kolibri_content_cache_key():\n    js = \"\"\"\n    <script>\n      var contentCacheKey = '{cache_key}';\n    </script>\n    \"\"\".format(\n        cache_key=ContentCacheKey.get_cache_key()\n    )\n    return mark_safe(js)\n\n\n@register.simple_tag(takes_context=True)\ndef kolibri_language_globals(context):\n\n    template = \"\"\"\n    <script>\n      var languageCode = '{lang_code}';\n      var languageDir = '{lang_dir}';\n      var languages = JSON.parse('{languages}');\n      var fullCSSFileModern = '{full_css_file_modern}?v={version}';\n      var fullCSSFileBasic = '{full_css_file_basic}?v={version}';\n    </script>\n    <link type=\"text/css\" href=\"{common_css_file}?v={version}\" rel=\"stylesheet\"/>\n    <link type=\"text/css\" href=\"{subset_css_file}?v={version}\" rel=\"stylesheet\"/>\n    \"\"\"\n\n    language_code = get_language()\n    lang_dir = \"rtl\" if get_language_bidi() else \"ltr\"\n\n    languages = {}\n    for code, language_name in settings.LANGUAGES:\n        lang_info = next(\n            (\n                lang\n                for lang in i18n.KOLIBRI_SUPPORTED_LANGUAGES\n                if lang[\"intl_code\"] == code\n            ),\n            None,\n        )\n        languages[code] = {\n            # Format to match the schema of the content Language model\n            \"id\": code,\n            \"lang_name\": language_name,\n            \"english_name\": lang_info[\"english_name\"]\n            if lang_info\n            else get_language_info(code)[\"name\"],\n            \"lang_direction\": get_language_info(code)[\"bidi\"],\n        }\n\n    common_file = static(\"assets/fonts/noto-common.css\")\n    subset_file = static(\"assets/fonts/noto-subset.{}.css\".format(language_code))\n    full_file = \"assets/fonts/noto-full.{}.{}.css\"\n    full_file_modern = static(full_file.format(language_code, \"modern\"))\n    full_file_basic = static(full_file.format(language_code, \"basic\"))\n\n    return mark_safe(\n        template.format(\n            lang_code=language_code,\n            lang_dir=lang_dir,\n            languages=json.dumps(languages),\n            common_css_file=common_file,\n            subset_css_file=subset_file,\n            full_css_file_modern=full_file_modern,\n            full_css_file_basic=full_file_basic,\n            # Temporary cache busting strategy.\n            # Would be better to use ManifestStaticFilesStorage\n            version=kolibri.__version__,\n        )\n    )\n\n\n@register.simple_tag()\ndef kolibri_navigation_actions():\n    \"\"\"\n    A tag to include an initial JS-object to bootstrap nav action data into the app.\n    :return: An html string\n    \"\"\"\n    return webpack_asset_render(NavigationHook)\n\n\n@register.simple_tag()\ndef kolibri_theme():\n    \"\"\"\n    A tag to include a theme configuration object to add custom theming to Kolibri.\n    :return: An html string\n    \"\"\"\n    template = \"\"\"\n    <script>\n      var customTheme = JSON.parse('{theme}');\n    </script>\n    \"\"\"\n    return mark_safe(template.format(theme=json.dumps(ThemeHook().theme)))\n\n\n@register.simple_tag(takes_context=True)\ndef kolibri_set_urls(context):\n    # Modified from:\n    # https://github.com/ierror/django-js-reverse/blob/master/django_js_reverse/core.py#L101\n    js_global_object_name = \"window\"\n    js_var_name = \"kolibriUrls\"\n    script_prefix = get_script_prefix()\n\n    if \"request\" in context:\n        default_urlresolver = get_resolver(getattr(context[\"request\"], \"urlconf\", None))\n    else:\n        default_urlresolver = get_resolver(None)\n\n    js = render_to_string(\n        \"django_js_reverse/urls_js.tpl\",\n        {\n            \"urls\": sorted(list(prepare_url_list(default_urlresolver))),\n            \"url_prefix\": script_prefix,\n            \"js_var_name\": js_var_name,\n            \"js_global_object_name\": js_global_object_name,\n        },\n    )\n\n    js = jsmin(js)\n\n    js = (\n        \"\"\"<script type=\"text/javascript\">\"\"\"\n        + js\n        + \"\"\"\n        {global_object}.staticUrl = '{static_url}';\n        </script>\n        \"\"\".format(\n            global_object=js_global_object_name, static_url=settings.STATIC_URL\n        )\n    )\n    return mark_safe(js)\n\n\n@register.simple_tag(takes_context=True)\ndef kolibri_bootstrap_model(context, base_name, api_resource, **kwargs):\n    response, kwargs = _kolibri_bootstrap_helper(\n        context, base_name, api_resource, \"detail\", **kwargs\n    )\n    html = (\n        \"<script type='text/javascript'>\"\n        \"var model = {0}.resources.{1}.createModel(JSON.parse({2}));\"\n        \"model.synced = true;\"\n        \"</script>\".format(\n            conf.KOLIBRI_CORE_JS_NAME,\n            api_resource,\n            json.dumps(JSONRenderer().render(response.data).decode(\"utf-8\")),\n        )\n    )\n    return mark_safe(html)\n\n\n@register.simple_tag(takes_context=True)\ndef kolibri_bootstrap_collection(context, base_name, api_resource, **kwargs):\n    response, kwargs = _kolibri_bootstrap_helper(\n        context, base_name, api_resource, \"list\", **kwargs\n    )\n    html = (\n        \"<script type='text/javascript'>\"\n        \"var collection = {0}.resources.{1}.createCollection({2}, JSON.parse({3}));\"\n        \"collection.synced = true;\"\n        \"</script>\".format(\n            conf.KOLIBRI_CORE_JS_NAME,\n            api_resource,\n            json.dumps(kwargs),\n            json.dumps(JSONRenderer().render(response.data).decode(\"utf-8\")),\n        )\n    )\n    return mark_safe(html)\n\n\ndef _replace_dict_values(check, replace, dict):\n    for (key, value) in iteritems(dict):\n        if dict[key] is check:\n            dict[key] = replace\n\n\ndef _kolibri_bootstrap_helper(context, base_name, api_resource, route, **kwargs):\n    reversal = dict()\n    kwargs_check = \"kwargs_\"\n    # remove prepended string and matching items from kwargs\n    for key in list(kwargs.keys()):\n        if kwargs_check in key:\n            item = kwargs.pop(key)\n            key = re.sub(kwargs_check, \"\", key)\n            reversal[key] = item\n    view, view_args, view_kwargs = resolve(\n        reverse(\"kolibri:core:{0}-{1}\".format(base_name, route), kwargs=reversal)\n    )\n    # switch out None temporarily because invalid filtering and caching can occur\n    _replace_dict_values(None, str(\"\"), kwargs)\n    request = copy.copy(context[\"request\"])\n    request.GET = request.GET.copy()\n    for key in kwargs:\n        request.GET[key] = kwargs[key]\n    response = view(request, **view_kwargs)\n    _replace_dict_values(str(\"\"), None, kwargs)\n    return response, kwargs\n\n\n@register.simple_tag()\ndef kolibri_sentry_error_reporting():\n\n    if not conf.OPTIONS[\"Debug\"][\"SENTRY_FRONTEND_DSN\"]:\n        return \"\"\n\n    template = \"\"\"\n      <script>\n        var sentryDSN = '{dsn}';\n        var sentryEnv = '{env}';\n      </script>\n    \"\"\"\n    return mark_safe(\n        template.format(\n            dsn=conf.OPTIONS[\"Debug\"][\"SENTRY_FRONTEND_DSN\"],\n            env=conf.OPTIONS[\"Debug\"][\"SENTRY_ENVIRONMENT\"],\n        )\n    )\n", "path": "kolibri/core/templatetags/kolibri_tags.py"}]}