Django URLs reversing in JS code
Basil Dubyk
Software developer @ Metaclass
Pycon PL 2018
// Get all products which are in stock
axios.get('api/products/in-stock/').then(function(response) {
// Process received data (from 'response.data')
}).catch(function(error) {
// Redirect to 'error_page' in case of error
window.location.href = 'error_page/';
};
Example for 'betterliving/{category_slug}/{entry_pk}/'
let url = 'betterliving/' + categorySlug + '/' + 'entryId' + '/';
// or for ES2015+
let url = `betterliving/${categorySlug}/${entryId}/`;
urls.py
url(r'^/betterliving/(?P<category_slug>[-\w]+)/(?P<entry_pk>\d+)/$',
'get_house',
name='betterliving_get_house'
)
In view
reverse('betterliving_get_house', args=('house', 12))
# or
reverse('betterliving_get_house', kwargs=('category_slug': 'house', 'entry_pk': 12)
In template
<p>Please go
<a href="{% url 'betterliving_get_house' category_slug='house' entry_pk=12 %}">
here
</a>
</p>
Options
1. Crossing (https://github.com/lincolnloop/crossing)
let Crossing = require('crossing');
let urls = new Crossing();
// Load your url list
urls.load({
'api:products_in_stock' 'api/products/in-stock/',
'betterliving_get_house': 'betterliving/<category_slug>/<entry_pk>/',
'error_page': 'error_page/',
});
// Get a url - similar to Django’s `reverse`
const path = urls.get(
'betterliving_get_house',
{'category_slug': 'house', 'entry_pk': 12}
);
PROS
- No python dependencies :)
CONS
-
Violates DRY principle, since all URLs must be manually duplicated in js and each
time after URLs updates you need to manually update related js code.
Options
2. Django JS Utils (https://github.com/Dimitri-Gnidash/django-js-utils)
Original library (https://github.com/mlouro/django-js-utils).
Used management command to generate js file with available urls.
var path = dutils.urls.resolve('time_edit', { project_id: 1, time_id: 2 });
PROS
- In your js code you can get URLs by their name in Django project.
CONS
-
Quite outdated, thus does not support modern versions of Django (since last commit was made in 2013).
-
Additional python dependency in your project.
Options
3. Django JS Reverse (https://github.com/ierror/django-js-reverse/)
Used management command to generate js file with available urls.
Urls.betterliving_get_house('house', 12);
// or
Url['betterliving_get_house']('house', 12);
PROS
- In your js code you can get URLs by their name in Django project.
CONS
-
Additional python dependency and potentially incompatible in the future within the Django upgrade.
Working with multilingual platforms
Django JS Reverse management command for sites with multiple languages for URLs which wrapped with 'i18n_patterns'
creates incorrect URLs.
E.g 'None/catalogue'
instead of 'en/catalogue'
*
* This is not the bug of Django JS Reverse - this is specific work of Django itself -
it cannot get needed locale/language in this case and returns None
instead.
Solutions
The easiest one - separate settings only for generating js file.
# settings_for_django_js_reverse.py
from settings.base import *
USE_I18N = False # Just take off i18n
PROS
- You need to create one file with 2 lines.
- Can be used for any project.
CONS
-
Additional redirections - each time two requests will be made instead of one -
E.g. from
'catalogue/'
to 'en/catalogue/'
-
Additional note for developers that they need to use special command to generate js file.
python manage.py collectstatic_js_reverse --settings settings.settings_for_django_js_reverse
Solutions
Update generated js file to add locale for all URLs (suggested in tickets by GitHub user @vladlep)
var language = window.preferred_language;
if(!language){
language= document.getElementsByTagName('html')[0].getAttribute('lang');
}
if (!url.startsWith(language) && language){
url = language + url.substring(url.indexOf('/'));
}
But
- Can be used only for projects where all URLs under
'i18n_patterns'
.
What we think should be done
Updated python code to catch “incorrect” URLs and prepare them for js.
def generate_url_pattern(namespace_path, pattern):
url, args = pattern
if url.startswith('None'):
url = url.replace('None', '%(locale)s', 1)
return '{0}{1}'.format(namespace_path, url), args
Updated js code to use locale based on browser settings.
// ...
if (url.indexOf("%(locale)s") !== -1) {
// Populate url with locale
url = url.replace("%(locale)s", detected_locale());
}
// ...
function detected_locale() {
var allowed_language_codes = {{ allowed_language_codes|safe }};
// Shorten strings to use two chars (E.g. "en-US" -> "en")
var locale = window.navigator.language.substr(0, 2);
var isLocaleAllowed = allowed_language_codes.indexOf(locale) !== -1;
if (locale && allowed_language_codes && isLocaleAllowed) {
return locale;
}
return "{{ default_language_code }}"; // `LANGUAGE_CODE` in Django settings
}
My github account - https://github.com/samitnuk
Sample project - https://github.com/metaclassco/pyconpl2018_sample_project
metaclass.co
Good luck with your projects.
Thank you.