diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..a919862 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,326 @@ +Metadata-Version: 1.1 +Name: django-js-reverse +Version: 0.9.1 +Summary: Javascript url handling for Django that doesn't hurt. +Home-page: https://github.com/ierror/django-js-reverse +Author: Bernhard Janetzki +Author-email: boerni@gmail.com +License: MIT +Download-URL: http://pypi.python.org/pypi/django-js-reverse/ +Description: ================= + Django JS Reverse + ================= + + .. image:: https://img.shields.io/pypi/v/django-js-reverse.svg + :target: https://pypi.python.org/pypi/django-js-reverse/ + + .. image:: https://img.shields.io/travis/ierror/django-js-reverse/master.svg + :target: https://travis-ci.org/ierror/django-js-reverse + + .. image:: https://img.shields.io/coveralls/ierror/django-js-reverse/master.svg + :alt: Coverage Status + :target: https://coveralls.io/r/ierror/django-js-reverse?branch=master + + .. image:: https://img.shields.io/github/license/ierror/django-js-reverse.svg + :target: https://raw.githubusercontent.com/ierror/django-js-reverse/develop/LICENSE + + .. image:: https://img.shields.io/pypi/wheel/django-js-reverse.svg + + + **Javascript url handling for Django that doesn’t hurt.** + + + Overview + -------- + + Django JS Reverse is a small django app that makes url handling of + `named urls `__ in javascript easy and non-annoying.. + + For example you can retrieve a named url: + + urls.py: + + :: + + url(r'^/betterliving/(?P[-\w]+)/(?P\d+)/$', 'get_house', name='betterliving_get_house'), + + in javascript like: + + :: + + Urls.betterliving_get_house('house', 12) + + Result: + + :: + + /betterliving/house/12/ + + + + Changelog + _________ + + 0.9.0 + New: Support for Python 3.7 + + New: Support for Django 2.2 + + New: Unit Tests Script prefix with no slash, changed URL Conf`#72 `__ + Thank you `graingert `__ + + Fix: "ROOT_URLCONF not taken into account" `#73 `__ `#74 `__ + Thank you `LuukOost `__ and `graingert `__ + + Refactoring: "move template logic to view" `#64 `__ + Thank you `graingert `__ + + Fix: "Now using LooseVersion instead of StrictVersion to avoid issues with rc releases" `#67 `__ + Thank you `kavdev `__ + + 0.8.2 + Fix: A bug fix in Django 2.0.6 has broken django-js-reverse `#65 `__ + Thank you `kavdev `__ + + 0.8.1 + Fix: The tests folder of the `#53 `__ was still present in the build. => Added cleanup to the release make command. + + 0.8.0 + New: Support for Django 2.0: `#58 `__ + Thank you `wlonk `__ + + Fix: `#53 `__ - Don't install the tests folder as a separate folder. Moved inside the django_js_reverse namespace. + + + `Full changelog `__ + + + Requirements + ------------ + + +----------------+------------------------------------------+ + | Python version | Django versions | + +================+==========================================+ + | 3.7 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | + +----------------+------------------------------------------+ + | 3.6 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | + +----------------+------------------------------------------+ + | 3.5 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | + +----------------+------------------------------------------+ + | 3.4 | 2.0, 1.11, 1.10, 1.9, 1.8, 1.7, 1.6, 1.5 | + +----------------+------------------------------------------+ + | 2.7 | 1.11, 1.10, 1.9, 1.8, 1.7, 1.6, 1.5 | + +----------------+------------------------------------------+ + + + Installation + ------------ + + Install using ``pip`` … + + :: + + pip install django-js-reverse + + … or clone the project from github. + + :: + + git clone https://github.com/ierror/django-js-reverse.git + + Add ``'django_js_reverse'`` to your ``INSTALLED_APPS`` setting. + + :: + + INSTALLED_APPS = ( + ... + 'django_js_reverse', + ) + + + Usage as static file + -------------------- + + First generate static file by + + :: + + ./manage.py collectstatic_js_reverse + + If you change some urls or add an app and want to update the reverse.js file, + run the command again. + + After this add the file to your template + + :: + + + + + Usage with views + ---------------- + + Include none-cached view … + + :: + + urlpatterns = patterns('', + url(r'^jsreverse/$', 'django_js_reverse.views.urls_js', name='js_reverse'), + ) + + … or a cached one that delivers the urls javascript + + :: + + from django_js_reverse.views import urls_js + urlpatterns = patterns('', + url(r'^jsreverse/$', cache_page(3600)(urls_js), name='js_reverse'), + ) + + Include javascript in your template + + :: + + + + or, if you are using Django > 1.5 + + :: + + + + + Usage as template tag + _____________________ + + You can place the js_reverse JavaScript inline into your templates, + however use of inline JavaScript is not recommended, because it + will make it impossible to deploy a secure Content Security Policy. + See `django-csp `__ + + :: + + {% load js_reverse %} + + + + + Use the urls in javascript + -------------------------- + + If your url names are valid javascript identifiers ([$A-Z\_][-Z\_$]\*)i + you can access them by the Dot notation: + + :: + + Urls.betterliving_get_house('house', 12) + + If the named url contains invalid identifiers use the Square bracket + notation instead: + + :: + + Urls['betterliving-get-house']('house', 12) + Urls['namespace:betterliving-get-house']('house', 12) + + You can also pass javascript objects to match keyword aguments like the + examples bellow: + + :: + + Urls['betterliving-get-house']({ category_slug: 'house', entry_pk: 12 }) + Urls['namespace:betterliving-get-house']({ category_slug: 'house', entry_pk: 12 }) + + Options + ------- + + Optionally, you can overwrite the default javascript variable ‘Urls’ used + to access the named urls by django setting + + :: + + JS_REVERSE_JS_VAR_NAME = 'Urls' + + Optionally, you can change the name of the global object the javascript variable + used to access the named urls is attached to. Default is :code:`this` + + :: + + JS_REVERSE_JS_GLOBAL_OBJECT_NAME = 'window' + + + Optionally, you can disable the minfication of the generated javascript file + by django setting + + :: + + JS_REVERSE_JS_MINIFY = False + + + By default all namespaces are included + + :: + + JS_REVERSE_EXCLUDE_NAMESPACES = [] + + To exclude any namespaces from the generated javascript file, add them to the `JS_REVERSE_EXCLUDE_NAMESPACES` setting + + :: + + JS_REVERSE_EXCLUDE_NAMESPACES = ['admin', 'djdt', ...] + + If you want to include only specific namespaces add them to the `JS_REVERSE_INCLUDE_ONLY_NAMESPACES` setting + tips: + * Use "" (empty string) for urls without namespace + * Use "foo\0" to include urls just from "foo" namaspace and not from any subnamespaces (e.g. "foo:bar") + + :: + + JS_REVERSE_INCLUDE_ONLY_NAMESPACES = ['poll', 'calendar', ...] + + If you run your application under a subpath, the collectstatic_js_reverse needs to take care of this. + Define the prefix in your django settings: + + :: + + JS_REVERSE_SCRIPT_PREFIX = '/myprefix/' + + By default collectstatic_js_reverse writes its output (reverse.js) to your project's STATIC_ROOT. + You can change the output path: + + :: + + JS_REVERSE_OUTPUT_PATH = 'some_path' + + + Running the test suite + ---------------------- + + :: + + make test + + License + ------- + + `MIT `__ + + + Contact + ------- + + `@i_error `__ + + -------------- + + Enjoy! + +Platform: UNKNOWN +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Framework :: Django +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..c48cffe --- /dev/null +++ b/README.rst @@ -0,0 +1,309 @@ +================= +Django JS Reverse +================= + +.. image:: https://img.shields.io/pypi/v/django-js-reverse.svg + :target: https://pypi.python.org/pypi/django-js-reverse/ + +.. image:: https://img.shields.io/travis/ierror/django-js-reverse/master.svg + :target: https://travis-ci.org/ierror/django-js-reverse + +.. image:: https://img.shields.io/coveralls/ierror/django-js-reverse/master.svg + :alt: Coverage Status + :target: https://coveralls.io/r/ierror/django-js-reverse?branch=master + +.. image:: https://img.shields.io/github/license/ierror/django-js-reverse.svg + :target: https://raw.githubusercontent.com/ierror/django-js-reverse/develop/LICENSE + +.. image:: https://img.shields.io/pypi/wheel/django-js-reverse.svg + + +**Javascript url handling for Django that doesn’t hurt.** + + +Overview +-------- + +Django JS Reverse is a small django app that makes url handling of +`named urls `__ in javascript easy and non-annoying.. + +For example you can retrieve a named url: + +urls.py: + +:: + + url(r'^/betterliving/(?P[-\w]+)/(?P\d+)/$', 'get_house', name='betterliving_get_house'), + +in javascript like: + +:: + + Urls.betterliving_get_house('house', 12) + +Result: + +:: + + /betterliving/house/12/ + + + +Changelog +_________ + +0.9.0 + New: Support for Python 3.7 + + New: Support for Django 2.2 + + New: Unit Tests Script prefix with no slash, changed URL Conf`#72 `__ + Thank you `graingert `__ + + Fix: "ROOT_URLCONF not taken into account" `#73 `__ `#74 `__ + Thank you `LuukOost `__ and `graingert `__ + + Refactoring: "move template logic to view" `#64 `__ + Thank you `graingert `__ + + Fix: "Now using LooseVersion instead of StrictVersion to avoid issues with rc releases" `#67 `__ + Thank you `kavdev `__ + +0.8.2 + Fix: A bug fix in Django 2.0.6 has broken django-js-reverse `#65 `__ + Thank you `kavdev `__ + +0.8.1 + Fix: The tests folder of the `#53 `__ was still present in the build. => Added cleanup to the release make command. + +0.8.0 + New: Support for Django 2.0: `#58 `__ + Thank you `wlonk `__ + + Fix: `#53 `__ - Don't install the tests folder as a separate folder. Moved inside the django_js_reverse namespace. + + +`Full changelog `__ + + +Requirements +------------ + ++----------------+------------------------------------------+ +| Python version | Django versions | ++================+==========================================+ +| 3.7 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | ++----------------+------------------------------------------+ +| 3.6 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | ++----------------+------------------------------------------+ +| 3.5 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | ++----------------+------------------------------------------+ +| 3.4 | 2.0, 1.11, 1.10, 1.9, 1.8, 1.7, 1.6, 1.5 | ++----------------+------------------------------------------+ +| 2.7 | 1.11, 1.10, 1.9, 1.8, 1.7, 1.6, 1.5 | ++----------------+------------------------------------------+ + + +Installation +------------ + +Install using ``pip`` … + +:: + + pip install django-js-reverse + +… or clone the project from github. + +:: + + git clone https://github.com/ierror/django-js-reverse.git + +Add ``'django_js_reverse'`` to your ``INSTALLED_APPS`` setting. + +:: + + INSTALLED_APPS = ( + ... + 'django_js_reverse', + ) + + +Usage as static file +-------------------- + +First generate static file by + +:: + + ./manage.py collectstatic_js_reverse + +If you change some urls or add an app and want to update the reverse.js file, +run the command again. + +After this add the file to your template + +:: + + + + +Usage with views +---------------- + +Include none-cached view … + +:: + + urlpatterns = patterns('', + url(r'^jsreverse/$', 'django_js_reverse.views.urls_js', name='js_reverse'), + ) + +… or a cached one that delivers the urls javascript + +:: + + from django_js_reverse.views import urls_js + urlpatterns = patterns('', + url(r'^jsreverse/$', cache_page(3600)(urls_js), name='js_reverse'), + ) + +Include javascript in your template + +:: + + + +or, if you are using Django > 1.5 + +:: + + + + +Usage as template tag +_____________________ + +You can place the js_reverse JavaScript inline into your templates, +however use of inline JavaScript is not recommended, because it +will make it impossible to deploy a secure Content Security Policy. +See `django-csp `__ + +:: + + {% load js_reverse %} + + + + +Use the urls in javascript +-------------------------- + +If your url names are valid javascript identifiers ([$A-Z\_][-Z\_$]\*)i +you can access them by the Dot notation: + +:: + + Urls.betterliving_get_house('house', 12) + +If the named url contains invalid identifiers use the Square bracket +notation instead: + +:: + + Urls['betterliving-get-house']('house', 12) + Urls['namespace:betterliving-get-house']('house', 12) + +You can also pass javascript objects to match keyword aguments like the +examples bellow: + +:: + + Urls['betterliving-get-house']({ category_slug: 'house', entry_pk: 12 }) + Urls['namespace:betterliving-get-house']({ category_slug: 'house', entry_pk: 12 }) + +Options +------- + +Optionally, you can overwrite the default javascript variable ‘Urls’ used +to access the named urls by django setting + +:: + + JS_REVERSE_JS_VAR_NAME = 'Urls' + +Optionally, you can change the name of the global object the javascript variable +used to access the named urls is attached to. Default is :code:`this` + +:: + + JS_REVERSE_JS_GLOBAL_OBJECT_NAME = 'window' + + +Optionally, you can disable the minfication of the generated javascript file +by django setting + +:: + + JS_REVERSE_JS_MINIFY = False + + +By default all namespaces are included + +:: + + JS_REVERSE_EXCLUDE_NAMESPACES = [] + +To exclude any namespaces from the generated javascript file, add them to the `JS_REVERSE_EXCLUDE_NAMESPACES` setting + +:: + + JS_REVERSE_EXCLUDE_NAMESPACES = ['admin', 'djdt', ...] + +If you want to include only specific namespaces add them to the `JS_REVERSE_INCLUDE_ONLY_NAMESPACES` setting +tips: +* Use "" (empty string) for urls without namespace +* Use "foo\0" to include urls just from "foo" namaspace and not from any subnamespaces (e.g. "foo:bar") + +:: + + JS_REVERSE_INCLUDE_ONLY_NAMESPACES = ['poll', 'calendar', ...] + +If you run your application under a subpath, the collectstatic_js_reverse needs to take care of this. +Define the prefix in your django settings: + +:: + + JS_REVERSE_SCRIPT_PREFIX = '/myprefix/' + +By default collectstatic_js_reverse writes its output (reverse.js) to your project's STATIC_ROOT. +You can change the output path: + +:: + + JS_REVERSE_OUTPUT_PATH = 'some_path' + + +Running the test suite +---------------------- + +:: + + make test + +License +------- + +`MIT `__ + + +Contact +------- + +`@i_error `__ + +-------------- + +Enjoy! diff --git a/django_js_reverse.egg-info/PKG-INFO b/django_js_reverse.egg-info/PKG-INFO new file mode 100644 index 0000000..a919862 --- /dev/null +++ b/django_js_reverse.egg-info/PKG-INFO @@ -0,0 +1,326 @@ +Metadata-Version: 1.1 +Name: django-js-reverse +Version: 0.9.1 +Summary: Javascript url handling for Django that doesn't hurt. +Home-page: https://github.com/ierror/django-js-reverse +Author: Bernhard Janetzki +Author-email: boerni@gmail.com +License: MIT +Download-URL: http://pypi.python.org/pypi/django-js-reverse/ +Description: ================= + Django JS Reverse + ================= + + .. image:: https://img.shields.io/pypi/v/django-js-reverse.svg + :target: https://pypi.python.org/pypi/django-js-reverse/ + + .. image:: https://img.shields.io/travis/ierror/django-js-reverse/master.svg + :target: https://travis-ci.org/ierror/django-js-reverse + + .. image:: https://img.shields.io/coveralls/ierror/django-js-reverse/master.svg + :alt: Coverage Status + :target: https://coveralls.io/r/ierror/django-js-reverse?branch=master + + .. image:: https://img.shields.io/github/license/ierror/django-js-reverse.svg + :target: https://raw.githubusercontent.com/ierror/django-js-reverse/develop/LICENSE + + .. image:: https://img.shields.io/pypi/wheel/django-js-reverse.svg + + + **Javascript url handling for Django that doesn’t hurt.** + + + Overview + -------- + + Django JS Reverse is a small django app that makes url handling of + `named urls `__ in javascript easy and non-annoying.. + + For example you can retrieve a named url: + + urls.py: + + :: + + url(r'^/betterliving/(?P[-\w]+)/(?P\d+)/$', 'get_house', name='betterliving_get_house'), + + in javascript like: + + :: + + Urls.betterliving_get_house('house', 12) + + Result: + + :: + + /betterliving/house/12/ + + + + Changelog + _________ + + 0.9.0 + New: Support for Python 3.7 + + New: Support for Django 2.2 + + New: Unit Tests Script prefix with no slash, changed URL Conf`#72 `__ + Thank you `graingert `__ + + Fix: "ROOT_URLCONF not taken into account" `#73 `__ `#74 `__ + Thank you `LuukOost `__ and `graingert `__ + + Refactoring: "move template logic to view" `#64 `__ + Thank you `graingert `__ + + Fix: "Now using LooseVersion instead of StrictVersion to avoid issues with rc releases" `#67 `__ + Thank you `kavdev `__ + + 0.8.2 + Fix: A bug fix in Django 2.0.6 has broken django-js-reverse `#65 `__ + Thank you `kavdev `__ + + 0.8.1 + Fix: The tests folder of the `#53 `__ was still present in the build. => Added cleanup to the release make command. + + 0.8.0 + New: Support for Django 2.0: `#58 `__ + Thank you `wlonk `__ + + Fix: `#53 `__ - Don't install the tests folder as a separate folder. Moved inside the django_js_reverse namespace. + + + `Full changelog `__ + + + Requirements + ------------ + + +----------------+------------------------------------------+ + | Python version | Django versions | + +================+==========================================+ + | 3.7 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | + +----------------+------------------------------------------+ + | 3.6 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | + +----------------+------------------------------------------+ + | 3.5 | 2.2, 2.1, 2.0, 1.11, 1.10, 1.9, 1.8 | + +----------------+------------------------------------------+ + | 3.4 | 2.0, 1.11, 1.10, 1.9, 1.8, 1.7, 1.6, 1.5 | + +----------------+------------------------------------------+ + | 2.7 | 1.11, 1.10, 1.9, 1.8, 1.7, 1.6, 1.5 | + +----------------+------------------------------------------+ + + + Installation + ------------ + + Install using ``pip`` … + + :: + + pip install django-js-reverse + + … or clone the project from github. + + :: + + git clone https://github.com/ierror/django-js-reverse.git + + Add ``'django_js_reverse'`` to your ``INSTALLED_APPS`` setting. + + :: + + INSTALLED_APPS = ( + ... + 'django_js_reverse', + ) + + + Usage as static file + -------------------- + + First generate static file by + + :: + + ./manage.py collectstatic_js_reverse + + If you change some urls or add an app and want to update the reverse.js file, + run the command again. + + After this add the file to your template + + :: + + + + + Usage with views + ---------------- + + Include none-cached view … + + :: + + urlpatterns = patterns('', + url(r'^jsreverse/$', 'django_js_reverse.views.urls_js', name='js_reverse'), + ) + + … or a cached one that delivers the urls javascript + + :: + + from django_js_reverse.views import urls_js + urlpatterns = patterns('', + url(r'^jsreverse/$', cache_page(3600)(urls_js), name='js_reverse'), + ) + + Include javascript in your template + + :: + + + + or, if you are using Django > 1.5 + + :: + + + + + Usage as template tag + _____________________ + + You can place the js_reverse JavaScript inline into your templates, + however use of inline JavaScript is not recommended, because it + will make it impossible to deploy a secure Content Security Policy. + See `django-csp `__ + + :: + + {% load js_reverse %} + + + + + Use the urls in javascript + -------------------------- + + If your url names are valid javascript identifiers ([$A-Z\_][-Z\_$]\*)i + you can access them by the Dot notation: + + :: + + Urls.betterliving_get_house('house', 12) + + If the named url contains invalid identifiers use the Square bracket + notation instead: + + :: + + Urls['betterliving-get-house']('house', 12) + Urls['namespace:betterliving-get-house']('house', 12) + + You can also pass javascript objects to match keyword aguments like the + examples bellow: + + :: + + Urls['betterliving-get-house']({ category_slug: 'house', entry_pk: 12 }) + Urls['namespace:betterliving-get-house']({ category_slug: 'house', entry_pk: 12 }) + + Options + ------- + + Optionally, you can overwrite the default javascript variable ‘Urls’ used + to access the named urls by django setting + + :: + + JS_REVERSE_JS_VAR_NAME = 'Urls' + + Optionally, you can change the name of the global object the javascript variable + used to access the named urls is attached to. Default is :code:`this` + + :: + + JS_REVERSE_JS_GLOBAL_OBJECT_NAME = 'window' + + + Optionally, you can disable the minfication of the generated javascript file + by django setting + + :: + + JS_REVERSE_JS_MINIFY = False + + + By default all namespaces are included + + :: + + JS_REVERSE_EXCLUDE_NAMESPACES = [] + + To exclude any namespaces from the generated javascript file, add them to the `JS_REVERSE_EXCLUDE_NAMESPACES` setting + + :: + + JS_REVERSE_EXCLUDE_NAMESPACES = ['admin', 'djdt', ...] + + If you want to include only specific namespaces add them to the `JS_REVERSE_INCLUDE_ONLY_NAMESPACES` setting + tips: + * Use "" (empty string) for urls without namespace + * Use "foo\0" to include urls just from "foo" namaspace and not from any subnamespaces (e.g. "foo:bar") + + :: + + JS_REVERSE_INCLUDE_ONLY_NAMESPACES = ['poll', 'calendar', ...] + + If you run your application under a subpath, the collectstatic_js_reverse needs to take care of this. + Define the prefix in your django settings: + + :: + + JS_REVERSE_SCRIPT_PREFIX = '/myprefix/' + + By default collectstatic_js_reverse writes its output (reverse.js) to your project's STATIC_ROOT. + You can change the output path: + + :: + + JS_REVERSE_OUTPUT_PATH = 'some_path' + + + Running the test suite + ---------------------- + + :: + + make test + + License + ------- + + `MIT `__ + + + Contact + ------- + + `@i_error `__ + + -------------- + + Enjoy! + +Platform: UNKNOWN +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Framework :: Django +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License diff --git a/django_js_reverse.egg-info/SOURCES.txt b/django_js_reverse.egg-info/SOURCES.txt new file mode 100644 index 0000000..d6db634 --- /dev/null +++ b/django_js_reverse.egg-info/SOURCES.txt @@ -0,0 +1,27 @@ +README.rst +setup.cfg +setup.py +django_js_reverse/__init__.py +django_js_reverse/core.py +django_js_reverse/js_reverse_settings.py +django_js_reverse/models.py +django_js_reverse/rjsmin.py +django_js_reverse/views.py +django_js_reverse.egg-info/PKG-INFO +django_js_reverse.egg-info/SOURCES.txt +django_js_reverse.egg-info/dependency_links.txt +django_js_reverse.egg-info/requires.txt +django_js_reverse.egg-info/top_level.txt +django_js_reverse/management/__init__.py +django_js_reverse/management/commands/__init__.py +django_js_reverse/management/commands/collectstatic_js_reverse.py +django_js_reverse/templates/django_js_reverse/urls_js.tpl +django_js_reverse/templatetags/__init__.py +django_js_reverse/templatetags/js_reverse.py +django_js_reverse/tests/__init__.py +django_js_reverse/tests/helper.py +django_js_reverse/tests/settings.py +django_js_reverse/tests/test_urlconf_urls.py +django_js_reverse/tests/test_urls.py +django_js_reverse/tests/unit_tests.py +django_js_reverse/tests/utils.py \ No newline at end of file diff --git a/django_js_reverse.egg-info/dependency_links.txt b/django_js_reverse.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/django_js_reverse.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/django_js_reverse.egg-info/requires.txt b/django_js_reverse.egg-info/requires.txt new file mode 100644 index 0000000..be600a3 --- /dev/null +++ b/django_js_reverse.egg-info/requires.txt @@ -0,0 +1 @@ +Django>=1.5 diff --git a/django_js_reverse.egg-info/top_level.txt b/django_js_reverse.egg-info/top_level.txt new file mode 100644 index 0000000..ce3b244 --- /dev/null +++ b/django_js_reverse.egg-info/top_level.txt @@ -0,0 +1 @@ +django_js_reverse diff --git a/django_js_reverse/__init__.py b/django_js_reverse/__init__.py new file mode 100755 index 0000000..e3b1eff --- /dev/null +++ b/django_js_reverse/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +VERSION = (0, 9, 1) diff --git a/django_js_reverse/core.py b/django_js_reverse/core.py new file mode 100755 index 0000000..3681002 --- /dev/null +++ b/django_js_reverse/core.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +import json +import re +import sys +from distutils.version import LooseVersion + +import django +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.template import loader +from django.utils.safestring import mark_safe +from django.utils.encoding import force_text + +from . import rjsmin +from .js_reverse_settings import (JS_EXCLUDE_NAMESPACES, JS_GLOBAL_OBJECT_NAME, + JS_INCLUDE_ONLY_NAMESPACES, JS_MINIFY, + JS_VAR_NAME) + +try: + from django import urls as urlresolvers +except ImportError: + from django.core import urlresolvers + +if sys.version < '3': + text_type = unicode # NOQA +else: + text_type = str + +JS_IDENTIFIER_RE = re.compile(r'^[$A-Z_][\dA-Z_$]*$') + + +def prepare_url_list(urlresolver, namespace_path='', namespace=''): + """ + returns list of tuples [(, ), ...] + """ + exclude_ns = getattr(settings, 'JS_REVERSE_EXCLUDE_NAMESPACES', JS_EXCLUDE_NAMESPACES) + include_only_ns = getattr(settings, 'JS_REVERSE_INCLUDE_ONLY_NAMESPACES', JS_INCLUDE_ONLY_NAMESPACES) + + if exclude_ns and include_only_ns: + raise ImproperlyConfigured( + 'Neither use JS_REVERSE_EXCLUDE_NAMESPACES nor JS_REVERSE_INCLUDE_ONLY_NAMESPACES setting') + + if namespace[:-1] in exclude_ns: + return + + include_only_allow = True # include_only state varible + + if include_only_ns != []: + # True mean that ns passed the test + in_on_empty_ns = False + in_on_is_in_list = False + in_on_null = False + + # Test urls without ns + if namespace == '' and '' in include_only_ns: + in_on_empty_ns = True + + # check if nestead ns isn't subns of include_only ns + # e.g. ns = "foo:bar" include_only = ["foo"] -> this ns will be used + # works for ns = "lorem:ipsum:dolor" include_only = ["lorem:ipsum"] + # ns "lorem" will be ignored but "lorem:ipsum" & "lorem:ipsum:.." won't + for ns in include_only_ns: + if ns != "" and namespace[:-1].startswith(ns): + in_on_is_in_list = True + break + + # Test if isn't used "\0" flag + # use "foo\0" to add urls just from "foo" not from subns "foo:bar" + if namespace[:-1] + '\0' in include_only_ns: + in_on_null = True + + include_only_allow = in_on_empty_ns or in_on_is_in_list or in_on_null + + if include_only_allow: + for url_name in urlresolver.reverse_dict.keys(): + if isinstance(url_name, (text_type, str)): + url_patterns = [] + for url_pattern in urlresolver.reverse_dict.getlist(url_name): + url_patterns += [ + [namespace_path + pat[0], pat[1]] for pat in url_pattern[0]] + yield [namespace + url_name, url_patterns] + + for inner_ns, (inner_ns_path, inner_urlresolver) in \ + urlresolver.namespace_dict.items(): + inner_ns_path = namespace_path + inner_ns_path + inner_ns = namespace + inner_ns + ':' + + # if we have inner_ns_path, reconstruct a new resolver so that we can + # handle regex substitutions within the regex of a namespace. + if inner_ns_path: + args = [inner_ns_path, inner_urlresolver] + + # https://github.com/ierror/django-js-reverse/issues/65 + if LooseVersion(django.get_version()) >= LooseVersion("2.0.6"): + args.append(tuple(urlresolver.pattern.converters.items())) + + inner_urlresolver = urlresolvers.get_ns_resolver(*args) + inner_ns_path = '' + + for x in prepare_url_list(inner_urlresolver, inner_ns_path, inner_ns): + yield x + + +def generate_json(default_urlresolver, script_prefix=None): + if script_prefix is None: + script_prefix = urlresolvers.get_script_prefix() + + urls = sorted(list(prepare_url_list(default_urlresolver))) + + return { + 'urls': [ + [ + force_text(name), + [ + [force_text(path), [force_text(arg) for arg in args]] + for path, args in patterns + ], + ] for name, patterns in urls + ], + 'prefix': script_prefix, + } + + +def _safe_json(obj): + return mark_safe( + json + .dumps(obj) + .replace('>', '\\u003E') + .replace('<', '\\u003C') + .replace('&', '\\u0026') + ) + + +def generate_js(default_urlresolver): + js_var_name = getattr(settings, 'JS_REVERSE_JS_VAR_NAME', JS_VAR_NAME) + if not JS_IDENTIFIER_RE.match(js_var_name.upper()): + raise ImproperlyConfigured( + 'JS_REVERSE_JS_VAR_NAME setting "%s" is not a valid javascript identifier.' % (js_var_name)) + + js_global_object_name = getattr(settings, 'JS_REVERSE_JS_GLOBAL_OBJECT_NAME', JS_GLOBAL_OBJECT_NAME) + if not JS_IDENTIFIER_RE.match(js_global_object_name.upper()): + raise ImproperlyConfigured( + 'JS_REVERSE_JS_GLOBAL_OBJECT_NAME setting "%s" is not a valid javascript identifier.' % ( + js_global_object_name)) + + minfiy = getattr(settings, 'JS_REVERSE_JS_MINIFY', JS_MINIFY) + if not isinstance(minfiy, bool): + raise ImproperlyConfigured( + 'JS_REVERSE_JS_MINIFY setting "%s" is not a valid. Needs to be set to True or False.' % (minfiy)) + + script_prefix_via_config = getattr(settings, 'JS_REVERSE_SCRIPT_PREFIX', None) + if script_prefix_via_config: + script_prefix = script_prefix_via_config + if not script_prefix.endswith('/'): + script_prefix = '{0}/'.format(script_prefix) + else: + script_prefix = urlresolvers.get_script_prefix() + + data = generate_json(default_urlresolver, script_prefix) + js_content = loader.render_to_string('django_js_reverse/urls_js.tpl', { + 'data': _safe_json(data), + 'js_name': '.'.join([js_global_object_name, js_var_name]), + }) + + if minfiy: + js_content = rjsmin.jsmin(js_content) + return js_content diff --git a/django_js_reverse/js_reverse_settings.py b/django_js_reverse/js_reverse_settings.py new file mode 100755 index 0000000..237e5e0 --- /dev/null +++ b/django_js_reverse/js_reverse_settings.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +JS_VAR_NAME = 'Urls' +JS_MINIFY = True +JS_EXCLUDE_NAMESPACES = [] +JS_INCLUDE_ONLY_NAMESPACES = [] +JS_SCRIPT_PREFIX = None +JS_GLOBAL_OBJECT_NAME = 'this' +JS_OUTPUT_PATH = None diff --git a/django_js_reverse/management/__init__.py b/django_js_reverse/management/__init__.py new file mode 100755 index 0000000..882555b --- /dev/null +++ b/django_js_reverse/management/__init__.py @@ -0,0 +1 @@ +__author__ = 'boerni' diff --git a/django_js_reverse/management/commands/__init__.py b/django_js_reverse/management/commands/__init__.py new file mode 100755 index 0000000..882555b --- /dev/null +++ b/django_js_reverse/management/commands/__init__.py @@ -0,0 +1 @@ +__author__ = 'boerni' diff --git a/django_js_reverse/management/commands/collectstatic_js_reverse.py b/django_js_reverse/management/commands/collectstatic_js_reverse.py new file mode 100755 index 0000000..26c8b7b --- /dev/null +++ b/django_js_reverse/management/commands/collectstatic_js_reverse.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +import os +import sys + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.core.files.base import ContentFile +from django.core.files.storage import FileSystemStorage +from django.core.management.base import BaseCommand +from django_js_reverse.core import generate_js +from django_js_reverse.js_reverse_settings import JS_OUTPUT_PATH + +try: + from django.urls import get_resolver +except ImportError: + from django.core.urlresolvers import get_resolver + + +class Command(BaseCommand): + help = 'Creates a static urls-js file for django-js-reverse' + + def get_location(self): + output_path = getattr(settings, 'JS_REVERSE_OUTPUT_PATH', JS_OUTPUT_PATH) + if output_path: + return output_path + + if not hasattr(settings, 'STATIC_ROOT') or not settings.STATIC_ROOT: + raise ImproperlyConfigured( + 'The collectstatic_js_reverse command needs settings.JS_REVERSE_OUTPUT_PATH or settings.STATIC_ROOT to be set.') + + return os.path.join(settings.STATIC_ROOT, 'django_js_reverse', 'js') + + def handle(self, *args, **options): + location = self.get_location() + file = 'reverse.js' + fs = FileSystemStorage(location=location) + if fs.exists(file): + fs.delete(file) + + urlconf = getattr(settings, 'ROOT_URLCONF', None) + default_urlresolver = get_resolver(urlconf) + content = generate_js(default_urlresolver) + fs.save(file, ContentFile(content)) + if len(sys.argv) > 1 and sys.argv[1] in ['collectstatic_js_reverse']: + self.stdout.write('js-reverse file written to %s' % (location)) # pragma: no cover diff --git a/django_js_reverse/models.py b/django_js_reverse/models.py new file mode 100755 index 0000000..e69de29 diff --git a/django_js_reverse/rjsmin.py b/django_js_reverse/rjsmin.py new file mode 100644 index 0000000..9eab5df --- /dev/null +++ b/django_js_reverse/rjsmin.py @@ -0,0 +1,446 @@ +#!/usr/bin/env python +# -*- coding: ascii -*- +r""" +===================== + Javascript Minifier +===================== + +rJSmin is a javascript minifier written in python. + +The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\\. + +:Copyright: + + Copyright 2011 - 2014 + Andr\xe9 Malo or his licensors, as applicable + +:License: + + 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. + +The module is a re-implementation aiming for speed, so it can be used at +runtime (rather than during a preprocessing step). Usually it produces the +same results as the original ``jsmin.c``. It differs in the following ways: + +- there is no error detection: unterminated string, regex and comment + literals are treated as regular javascript code and minified as such. +- Control characters inside string and regex literals are left untouched; they + are not converted to spaces (nor to \\n) +- Newline characters are not allowed inside string and regex literals, except + for line continuations in string literals (ECMA-5). +- "return /regex/" is recognized correctly. +- "+ +" and "- -" sequences are not collapsed to '++' or '--' +- Newlines before ! operators are removed more sensibly +- Comments starting with an exclamation mark (``!``) can be kept optionally +- rJSmin does not handle streams, but only complete strings. (However, the + module provides a "streamy" interface). + +Since most parts of the logic are handled by the regex engine it's way faster +than the original python port of ``jsmin.c`` by Baruch Even. The speed factor +varies between about 6 and 55 depending on input and python version (it gets +faster the more compressed the input already is). Compared to the +speed-refactored python port by Dave St.Germain the performance gain is less +dramatic but still between 3 and 50 (for huge inputs). See the docs/BENCHMARKS +file for details. + +rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more. + +Both python 2 and python 3 are supported. + +.. _jsmin.c by Douglas Crockford: + http://www.crockford.com/javascript/jsmin.c +""" +if __doc__: + # pylint: disable = W0622 + __doc__ = __doc__.encode('ascii').decode('unicode_escape') +__author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape') +__docformat__ = "restructuredtext en" +__license__ = "Apache License, Version 2.0" +__version__ = '1.0.10' +__all__ = ['jsmin'] + +import re as _re + + +def _make_jsmin(python_only=False): + """ + Generate JS minifier based on `jsmin.c by Douglas Crockford`_ + + .. _jsmin.c by Douglas Crockford: + http://www.crockford.com/javascript/jsmin.c + + :Parameters: + `python_only` : ``bool`` + Use only the python variant. If true, the c extension is not even + tried to be loaded. + + :Return: Minifier + :Rtype: ``callable`` + """ + # pylint: disable = R0912, R0914, W0612 + + if not python_only: + try: + import _rjsmin # pylint: disable = F0401 + except ImportError: + pass + else: + return _rjsmin.jsmin + try: + xrange + except NameError: + xrange = range # pylint: disable = W0622 + + space_chars = r'[\000-\011\013\014\016-\040]' + + line_comment = r'(?://[^\r\n]*)' + space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' + space_comment_nobang = r'(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/)' + bang_comment = r'(?:/\*![^*]*\*+(?:[^/*][^*]*\*+)*/)' + + string1 = \ + r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)' + string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")' + strings = r'(?:%s|%s)' % (string1, string2) + + charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])' + nospecial = r'[^/\\\[\r\n]' + regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % ( + nospecial, charclass, nospecial + ) + space = r'(?:%s|%s)' % (space_chars, space_comment) + space_nobang = r'(?:%s|%s)' % (space_chars, space_comment_nobang) + newline = r'(?:%s?[\r\n])' % line_comment + + def fix_charclass(result): + """ Fixup string of chars to fit into a regex char class """ + pos = result.find('-') + if pos >= 0: + result = r'%s%s-' % (result[:pos], result[pos + 1:]) + + def sequentize(string): + """ + Notate consecutive characters as sequence + + (1-4 instead of 1234) + """ + first, last, result = None, None, [] + for char in map(ord, string): + if last is None: + first = last = char + elif last + 1 == char: + last = char + else: + result.append((first, last)) + first = last = char + if last is not None: + result.append((first, last)) + return ''.join(['%s%s%s' % ( + chr(first), + last > first + 1 and '-' or '', + last != first and chr(last) or '' + ) for first, last in result]) + + return _re.sub( + r'([\000-\040\047])', # \047 for better portability + lambda m: '\\%03o' % ord(m.group(1)), ( + sequentize(result) + .replace('\\', '\\\\') + .replace('[', '\\[') + .replace(']', '\\]') + ) + ) + + def id_literal_(what): + """ Make id_literal like char class """ + match = _re.compile(what).match + result = ''.join([ + chr(c) for c in xrange(127) if not match(chr(c)) + ]) + return '[^%s]' % fix_charclass(result) + + def not_id_literal_(keep): + """ Make negated id_literal like char class """ + match = _re.compile(id_literal_(keep)).match + result = ''.join([ + chr(c) for c in xrange(127) if not match(chr(c)) + ]) + return r'[%s]' % fix_charclass(result) + + not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]') + preregex1 = r'[(,=:\[!&|?{};\r\n]' + preregex2 = r'%(not_id_literal)sreturn' % locals() + + id_literal = id_literal_(r'[a-zA-Z0-9_$]') + id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(!+-]') + id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]') + + dull = r'[^\047"/\000-\040]' + + space_sub_simple = _re.compile(( + # noqa pylint: disable = C0330 + + r'(%(dull)s+)' + r'|(%(strings)s%(dull)s*)' + r'|(?<=%(preregex1)s)' + r'%(space)s*(?:%(newline)s%(space)s*)*' + r'(%(regex)s%(dull)s*)' + r'|(?<=%(preregex2)s)' + r'%(space)s*(?:%(newline)s%(space)s)*' + r'(%(regex)s%(dull)s*)' + r'|(?<=%(id_literal_close)s)' + r'%(space)s*(?:(%(newline)s)%(space)s*)+' + r'(?=%(id_literal_open)s)' + r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' + r'|(?<=\+)(%(space)s)+(?=\+)' + r'|(?<=-)(%(space)s)+(?=-)' + r'|%(space)s+' + r'|(?:%(newline)s%(space)s*)+' + ) % locals()).sub + #print space_sub_simple.__self__.pattern + + def space_subber_simple(match): + """ Substitution callback """ + # pylint: disable = R0911 + + groups = match.groups() + if groups[0]: + return groups[0] + elif groups[1]: + return groups[1] + elif groups[2]: + return groups[2] + elif groups[3]: + return groups[3] + elif groups[4]: + return '\n' + elif groups[5] or groups[6] or groups[7]: + return ' ' + else: + return '' + + space_sub_banged = _re.compile(( + # noqa pylint: disable = C0330 + + r'(%(dull)s+)' + r'|(%(strings)s%(dull)s*)' + r'|(%(bang_comment)s%(dull)s*)' + r'|(?<=%(preregex1)s)' + r'%(space)s*(?:%(newline)s%(space)s*)*' + r'(%(regex)s%(dull)s*)' + r'|(?<=%(preregex2)s)' + r'%(space)s*(?:%(newline)s%(space)s)*' + r'(%(regex)s%(dull)s*)' + r'|(?<=%(id_literal_close)s)' + r'%(space)s*(?:(%(newline)s)%(space)s*)+' + r'(?=%(id_literal_open)s)' + r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' + r'|(?<=\+)(%(space)s)+(?=\+)' + r'|(?<=-)(%(space)s)+(?=-)' + r'|%(space)s+' + r'|(?:%(newline)s%(space)s*)+' + ) % dict(locals(), space=space_nobang)).sub + #print space_sub_banged.__self__.pattern + + def space_subber_banged(match): + """ Substitution callback """ + # pylint: disable = R0911 + + groups = match.groups() + if groups[0]: + return groups[0] + elif groups[1]: + return groups[1] + elif groups[2]: + return groups[2] + elif groups[3]: + return groups[3] + elif groups[4]: + return groups[4] + elif groups[5]: + return '\n' + elif groups[6] or groups[7] or groups[8]: + return ' ' + else: + return '' + + def jsmin(script, keep_bang_comments=False): # pylint: disable = W0621 + r""" + Minify javascript based on `jsmin.c by Douglas Crockford`_\. + + Instead of parsing the stream char by char, it uses a regular + expression approach which minifies the whole script with one big + substitution regex. + + .. _jsmin.c by Douglas Crockford: + http://www.crockford.com/javascript/jsmin.c + + :Parameters: + `script` : ``str`` + Script to minify + + `keep_bang_comments` : ``bool`` + Keep comments starting with an exclamation mark? (``/*!...*/``) + + :Return: Minified script + :Rtype: ``str`` + """ + if keep_bang_comments: + return space_sub_banged( + space_subber_banged, '\n%s\n' % script + ).strip() + else: + return space_sub_simple( + space_subber_simple, '\n%s\n' % script + ).strip() + + return jsmin + +jsmin = _make_jsmin(python_only=True) + + +def jsmin_for_posers(script, keep_bang_comments=False): + r""" + Minify javascript based on `jsmin.c by Douglas Crockford`_\. + + Instead of parsing the stream char by char, it uses a regular + expression approach which minifies the whole script with one big + substitution regex. + + .. _jsmin.c by Douglas Crockford: + http://www.crockford.com/javascript/jsmin.c + + :Warning: This function is the digest of a _make_jsmin() call. It just + utilizes the resulting regexes. It's here for fun and may + vanish any time. Use the `jsmin` function instead. + + :Parameters: + `script` : ``str`` + Script to minify + + `keep_bang_comments` : ``bool`` + Keep comments starting with an exclamation mark? (``/*!...*/``) + + :Return: Minified script + :Rtype: ``str`` + """ + if not keep_bang_comments: + rex = ( + r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]' + r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]' + r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?' + r'{};\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*' + r'][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\0' + r'14\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r' + r'\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r' + r'\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*)|(?<' + r'=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\016-\04' + r'0]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[' + r'\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^' + r'*]*\*+)*/)))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:' + r'\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[' + r'^\047"/\000-\040]*)|(?<=[^\000-!#%&(*,./:-@\[\\^`{|~])(?:[\000' + r'-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?' + r':((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]|(?' + r':/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047)*,.' + r'/:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\0' + r'13\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\00' + r'0-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]' + r'|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-' + r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?' + r'=-)|(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]' + r'*\*+)*/))+|(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\0' + r'16-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+' + ) + + def subber(match): + """ Substitution callback """ + groups = match.groups() + return ( + groups[0] or + groups[1] or + groups[2] or + groups[3] or + (groups[4] and '\n') or + (groups[5] and ' ') or + (groups[6] and ' ') or + (groups[7] and ' ') or + '' + ) + else: + rex = ( + r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]' + r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]' + r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|((?:/\*![^*]*\*' + r'+(?:[^/*][^*]*\*+)*/)[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?{};\r' + r'\n])(?:[\000-\011\013\014\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*' + r'][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\0' + r'14\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(' + r'?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:' + r'\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]' + r'*)|(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\0' + r'16-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[' + r'^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*(?!!)[^*' + r']*\*+(?:[^/*][^*]*\*+)*/)))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(' + r'?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/' + r'\\\[\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[^\000-!#%&(*,./:-@\[\\' + r'^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[' + r'^/*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011' + r'\013\014\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+' + r'(?=[^\000-\040"#%-\047)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@' + r'\[-^`{-~-])((?:[\000-\011\013\014\016-\040]|(?:/\*(?!!)[^*]*\*' + r'+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-#%-,./:-@\[-^`{-~-])|(?<=\+)' + r'((?:[\000-\011\013\014\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*][^' + r'*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\013\014\016-\040]|(?:' + r'/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\000-\011\013' + r'\014\016-\040]|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:(?' + r':(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*(' + r'?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+' + ) + + def subber(match): + """ Substitution callback """ + groups = match.groups() + return ( + groups[0] or + groups[1] or + groups[2] or + groups[3] or + groups[4] or + (groups[5] and '\n') or + (groups[6] and ' ') or + (groups[7] and ' ') or + (groups[8] and ' ') or + '' + ) + + return _re.sub(rex, subber, '\n%s\n' % script).strip() + + +if __name__ == '__main__': + def main(): + """ Main """ + import sys as _sys + keep_bang_comments = ( + '-b' in _sys.argv[1:] + or '-bp' in _sys.argv[1:] + or '-pb' in _sys.argv[1:] + ) + if '-p' in _sys.argv[1:] or '-bp' in _sys.argv[1:] \ + or '-pb' in _sys.argv[1:]: + global jsmin # pylint: disable = W0603 + jsmin = _make_jsmin(python_only=True) + _sys.stdout.write(jsmin( + _sys.stdin.read(), keep_bang_comments=keep_bang_comments + )) + main() diff --git a/django_js_reverse/templates/django_js_reverse/urls_js.tpl b/django_js_reverse/templates/django_js_reverse/urls_js.tpl new file mode 100755 index 0000000..d6a8af6 --- /dev/null +++ b/django_js_reverse/templates/django_js_reverse/urls_js.tpl @@ -0,0 +1,97 @@ +{{ js_name }} = (function () { + "use strict"; + var data = {{ data }}; + function factory(d) { + var url_patterns = d.urls; + var url_prefix = d.prefix; + var Urls = {}; + var self_url_patterns = {}; + + var _get_url = function (url_pattern) { + return function () { + var _arguments, index, url, url_arg, url_args, _i, _len, _ref, + _ref_list, match_ref, provided_keys, build_kwargs; + + _arguments = arguments; + _ref_list = self_url_patterns[url_pattern]; + + if (arguments.length == 1 && typeof (arguments[0]) == "object") { + // kwargs mode + var provided_keys_list = Object.keys (arguments[0]); + provided_keys = {}; + for (_i = 0; _i < provided_keys_list.length; _i++) + provided_keys[provided_keys_list[_i]] = 1; + + match_ref = function (ref) + { + var _i; + + // Verify that they have the same number of arguments + if (ref[1].length != provided_keys_list.length) + return false; + + for (_i = 0; + _i < ref[1].length && ref[1][_i] in provided_keys; + _i++); + + // If for loop completed, we have all keys + return _i == ref[1].length; + } + + build_kwargs = function (keys) {return _arguments[0];} + + } else { + // args mode + match_ref = function (ref) + { + return ref[1].length == _arguments.length; + } + + build_kwargs = function (keys) { + var kwargs = {}; + + for (var i = 0; i < keys.length; i++) { + kwargs[keys[i]] = _arguments[i]; + } + + return kwargs; + } + } + + for (_i = 0; + _i < _ref_list.length && !match_ref(_ref_list[_i]); + _i++); + + // can't find a match + if (_i == _ref_list.length) + return null; + + _ref = _ref_list[_i]; + url = _ref[0], url_args = build_kwargs(_ref[1]); + for (url_arg in url_args) { + var url_arg_value = url_args[url_arg]; + if (url_arg_value === undefined || url_arg_value === null) { + url_arg_value = ''; + } else { + url_arg_value = url_arg_value.toString(); + } + url = url.replace("%(" + url_arg + ")s", url_arg_value); + } + return url_prefix + url; + }; + }; + + var name, pattern, url, _i, _len, _ref; + for (_i = 0, _len = url_patterns.length; _i < _len; _i++) { + _ref = url_patterns[_i], name = _ref[0], pattern = _ref[1]; + self_url_patterns[name] = pattern; + url = _get_url(name); + Urls[name.replace(/[-_]+(.)/g, function (_m, p1) { return p1.toUpperCase(); })] = url; + Urls[name.replace(/-/g, '_')] = url; + Urls[name] = url; + } + + return Urls; + } + return data ? factory(data) : factory; +})(); diff --git a/django_js_reverse/templatetags/__init__.py b/django_js_reverse/templatetags/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/django_js_reverse/templatetags/js_reverse.py b/django_js_reverse/templatetags/js_reverse.py new file mode 100755 index 0000000..75738a9 --- /dev/null +++ b/django_js_reverse/templatetags/js_reverse.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +from django import template +from django.utils.safestring import mark_safe +from django_js_reverse.core import generate_js + +try: + from django.urls import get_resolver +except ImportError: + from django.core.urlresolvers import get_resolver + + +register = template.Library() + + +urlconf = template.Variable('request.urlconf') + + +def _get_urlconf(context): + try: + return context.request.urlconf + except AttributeError: + pass + try: + return urlconf.resolve(context) + except template.VariableDoesNotExist: + pass + + +@register.simple_tag(takes_context=True) +def js_reverse_inline(context): + """ + Outputs a string of javascript that can generate URLs via the use + of the names given to those URLs. + """ + return mark_safe(generate_js(get_resolver(_get_urlconf(context)))) diff --git a/django_js_reverse/tests/__init__.py b/django_js_reverse/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_js_reverse/tests/helper.py b/django_js_reverse/tests/helper.py new file mode 100644 index 0000000..d0e7ea1 --- /dev/null +++ b/django_js_reverse/tests/helper.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from django import VERSION + + +def is_django_ver_gte_2(): + return VERSION[0] >= 2 diff --git a/django_js_reverse/tests/settings.py b/django_js_reverse/tests/settings.py new file mode 100644 index 0000000..93c5970 --- /dev/null +++ b/django_js_reverse/tests/settings.py @@ -0,0 +1,39 @@ +import os + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:', + }, +} + +SECRET_KEY = 'wtf' + +ROOT_URLCONF = None + +USE_TZ = True + +INSTALLED_APPS = ( + 'django_js_reverse', +) + +ALLOWED_HOSTS = ['testserver'] + +MIDDLEWARE_CLASSES = () + +MIDDLEWARE = () + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True, + }, +] + +STATIC_ROOT = os.path.join(os.path.dirname(__file__), 'tmp', 'static_root') + +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', + } +} diff --git a/django_js_reverse/tests/test_urlconf_urls.py b/django_js_reverse/tests/test_urlconf_urls.py new file mode 100644 index 0000000..e353e12 --- /dev/null +++ b/django_js_reverse/tests/test_urlconf_urls.py @@ -0,0 +1,6 @@ +from django.conf.urls import url +from django.views.generic import View + +urlpatterns = [ + url(r'^test_changed_urlconf/$', View.as_view(), name='test_changed_urlconf'), +] diff --git a/django_js_reverse/tests/test_urls.py b/django_js_reverse/tests/test_urls.py new file mode 100644 index 0000000..a53ad8f --- /dev/null +++ b/django_js_reverse/tests/test_urls.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +from copy import copy + +from django.conf.urls import include as django_include +from django.conf.urls import url +from django.views.generic import View +from django_js_reverse.tests.helper import is_django_ver_gte_2 +from django_js_reverse.views import urls_js + +try: + from django.urls import path +except ImportError: + pass + + +dummy_view = View.as_view() + +basic_patterns = [ + url(r'^jsreverse/$', urls_js, name='js_reverse'), + + # test urls + url(r'^test_no_url_args/$', dummy_view, name='test_no_url_args'), + url(r'^test_script/$', dummy_view, name='