Add jquery to widget Django
Aug. 14, 2018, 11:41 a.m.
There is two simple ways to add jQuery to forms or widgets in django admin.
1. Adding of an other version of jQuery
Firstly, you need download jQuery. As an example for this article I used jQuery.2.2.0. Place library, for example, here: my_project/static/jquery/jquery-2.2.0.min.js
.
from django import forms from django.conf import settings from my_app.models import MyModel class MyModelForm(forms.ModelForm): class Meta: model = MyModel exclude = () class Media: js = [settings.JQUERY_URL, 'my_app/my_script.js']
import os ... JQUERY_URL = os.path.join(STATIC_URL, 'jquery/jquery-2.2.0.min.js') ...
2. Usage Django jQuery
from django import forms from django.conf import settings from my_app.models import MyModel class MyModelForm(forms.ModelForm): class Meta: model = MyModel exclude = () class Media: extra = '' if settings.DEBUG else '.min' # to use minified libraries or not js = [f'admin/js/vendor/jquery/jquery{extra}.js', 'admin/js/vendor/jquery/jquery.init.js' 'my_app/admin_compat.js', 'my_app/my_script.js']
if (window.django !== undefined) var jQuery = django.jQuery, $ = django.jQuery;
Depending on whether our code is in development mode or in production, we use the full or minified version of jquery. This must be done, otherwise two versions of jquery will be loaded either on the local machine or on production - this is not only redundant code, but it may also happen that the connected library can work locally, but not on production and vice versa.
admin/js/vendor/jquery/jquery(/.min).js
- version of jquery, that is in the box with Django.
admin/js/vendor/jquery/jquery.init.js
- renames $
todjango.jQuery
to avoid the namespace conflict, if the page uses the different version of jQuery (or even if another library uses the same $
character to shorten the name of its function). That's its code:
var django = django || {}; django.jQuery = jQuery.noConflict(true);
But sense we othen use $
in our scripts, for example:
$('.test_btn').click(function(){ alert('clicked'); });
then we need to do to the reverse conversion, as it is done in admin_compat.js - adds $
(including jQuery
) to the javascript spacename.
We can and do not use admin_compat.js, but in this case we will have to use django jquery like this:
(function('.test_btn').click(function(){ alert('clicked'); })(django.jQuery);
Also, if we want use $
in our scripts, we should not just use jquery Django without jquery.init.js and admin_compat.js:
class MyModelForm(forms.ModelForm): class Meta: model = MyModel exclude = () class Media: extra = '' if settings.DEBUG else '.min' # bad usage your scripts without jquery.init.js and admin_compat.js js = [f'admin/js/vendor/jquery/jquery{extra}.js', 'my_app/my_script.js']
Because Django can use jquery.init.js when renders another page parts, for example, when renders inline objects. In this case $
will rename to django.jQuery
and for your script my_script.js function $
may be undefined
.
I also want to remind you that if you use admin template inherited, for example, from base_site.html
, you must still explicitly output the variable media
:
from __future__ import unicode_literals from django.core import management from django.shortcuts import redirect, render from django.views.generic import View from my_app.forms import MyModelForm class TestView(View): template_name = 'admin/my_app/test_template.html' form = MyModelForm def get(self, request, *args, **kwargs): return render(request, self.template_name, {'form': self.form()}) def post(self, request, *args, **kwargs): form = self.form(request.POST) if form.is_valid(): # some handling... return redirect('/') return render(request, self.template_name, {'form': form})
from django.conf.urls import url from django.contrib.auth.decorators import user_passes_test from my_app.views import TestView urlpatterns = [ url(r'^my_test/$', user_passes_test(lambda u: u.is_superuser)(TestView.as_view()), name='my_test'), ]
{% extends "admin/base_site.html" %} {% load i18n %} {% block title %}Custom admin page{% endblock %} {% block extrahead %}{{ block.super }} <script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script> {{ form.media }} {% comment %}Note: you can place your scripts here and manage the order of loading of scripts{% endcomment %} {% endblock %} {% block content %} <div class="fieldsets"> <fieldset class="module"> <h1>Custom admin page</h1> <p>Page content</p> </fieldset> </div> {% endblock %}
Sense {{ media }}
is placed only in change_form.html
(and also in delete_confirmation.html
and delete_selected_confirmation.html
to be precies):
{% extends "admin/base_site.html" %} {% load i18n admin_urls static admin_modify sb_tags %} {% block extrahead %}{{ block.super }} <script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script> {{ media }} {% endblock %} {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %} {% block coltype %}colM{% endblock %} ...
Django admin javascript load order in forms and widgets
The above methods works, when you want to load your scripts to admin. But if there are widgets, that required jQuery, then you need load jQuery before loading of widgets, sense when page is rendered, the widget scripts are first loaded, and then what is declared inMedia
of Form
class.
So far, I've found the only solution - to extend the widgets, adding jquery to first position in Media
class. How to do this, I will show in example with django-select2.
Add jQuery to django-select2
Deprecated!
It is actual for django-select2 <= 7.7.1
If you want to quickly add the Select2 autocomplete to your Select in django admin, you can write like this:
from django_select2.forms import Select2Widget class MyForm(forms.Form): my_choice = forms.ChoiceField(widget=Select2Widget)
But most likely it will not be work. See documentation about jQuery requirement (it is not in the box with widget):
- jQuery version 2. This is not included in the package since it is expected that in most scenarios this would already be available.
Okey, it seems that we just need to add jQuery to form class like this:
class MyAdminForm(forms.ModelForm): class Media: model = MyModel js = ('path/to/jquery.js', ) # Declared scripts in Media will be added after all scripts of used widgets.
But Select2 will not works! Sense the declared styles and scripts in Media
will be added after all styles and scripts of used widgets:
<head> ... <link href="/static/select2/css/select2.css" type="text/css" media="all" rel="stylesheet"> <link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" type="text/css" media="screen" rel="stylesheet"> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"></script> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/ru.js"></script> <script type="text/javascript" src="/static/django_select2/django_select2.js"></script> <script type="text/javascript" src="/static/jquery/jquery.js"></script> ... </head>
To load jquery before select2 scripts, I overrode Media
class, created ownSelect2Widget
class and inherited its from base class:
from django_select2.forms import Select2Widget as BaseSelect2Widget class Select2Widget(BaseSelect2Widget): """ Added jquery to widget """ def _get_media(self): media = super()._get_media() css = ['select2/css/select2.css'] # if you want override some styles css.extend(media._css['screen']) extra = '' if settings.DEBUG else '.min' js = [f'admin/js/vendor/jquery/jquery{extra}.js', 'admin/js/jquery.init.js', 'my_app/js/admin_compat.js'] js.extend(media._js) return forms.Media(js=js, css={'screen': css}) media = property(_get_media)
In this code, as you can see, I used jquery, that is in the box with django. If you want to use another version of jQuery, then just replace line:
extra = '' if settings.DEBUG else '.min' js = [f'admin/js/vendor/jquery/jquery{extra}.js', 'admin/js/jquery.init.js', 'my_app/js/admin_compat.js']
to:
js = ['jquery/jquery.js']
Now you can use Select2Widget
, as described in documentation, importing your Select2Widget
:
from my_app.widgets import Select2Widget class MyForm(forms.Form): my_choice = forms.ChoiceField(widget=Select2Widget)
Comments: 0