A fresh approach to autocomplete implementations, specially for Django. Status: v3 stable, 2.x.x stable, 1.x.x deprecated. Please DO regularely ping us with your link at #yourlabs IRC channel

Overview
https://badge.fury.io/py/django-autocomplete-light.png https://secure.travis-ci.org/yourlabs/django-autocomplete-light.png?branch=master https://codecov.io/github/yourlabs/django-autocomplete-light/coverage.svg?branch=master

Features

  • Python 2.7, 3.4, Django 2.0+ support (Django 1.11 (LTS), is supported until django-autocomplete-light-3.2.10),
  • Django (multiple) choice support,
  • Django (multiple) model choice support,
  • Django generic foreign key support (through django-querysetsequence),
  • Django generic many to many relation support (through django-generic-m2m and django-gm2m)
  • Multiple widget support: select2.js, easy to add more.
  • Creating choices that don't exist in the autocomplete,
  • Offering choices that depend on other fields in the form, in an elegant and innovative way,
  • Dynamic widget creation (ie. inlines), supports YOUR custom scripts too,
  • Provides a test API for your awesome autocompletes, to support YOUR custom use cases too,
  • A documented automatically tested example for each use case in test_project.

Upgrading

See CHANGELOG..

For v2 users and experts, a blog post was published with plenty of details.

Resources

Comments
  • Hard to select items in IE10 Mobile

    Hard to select items in IE10 Mobile

    The dropdown menus are really hard to tap in IE10 Mobile (Windows Phone 8). I have to zoom way in to successfully select an item. At the default zoom using Bootstrap's responsive CSS package, most of my taps are recognized as clicking the label of the field below. Works fine on Mobile Safari (iOS 6).

    When I get a chance, I'll create an isolated test case, try to debug, and (if I find a solution) submit a patch. Creating a tracking issue for now.

    It may be an interaction between this package and Crispy Forms - I haven't tried without Crispy.

    bug 
    opened by vtbassmatt 52
  • The language file for

    The language file for "./i18n/en-us" could not be automatically loaded. A fallback will be used instead

    On a clean install, Django 1.10, I get the following message:

    The language file for "./i18n/en-us" could not be automatically loaded. A fallback will be used instead

    I see that in the src and dist folders the translation files don´t have the sublanguage suffixes.

    What´s the best practice here?

    opened by busla 30
  • Select list not loading (Similar to issue #68)

    Select list not loading (Similar to issue #68)

    [03:10:11.488] TypeError: $(this).yourlabsWidget is not a function @ http://127.0.0.1:8000/static/autocomplete_light/widget.js:297

    The following error is given by my browsers development console. I have a simple inline form in the admin. I'm using the basic setup from the documents.

    The html is:

    I'm using django 1.4. Everything appears to be loading correctly, but there is no sign of network requests to the autocomplete url.

    I thought I had the test_project working, but now that appears to not be working either. Tried re-installing the project, but it still wont work. However, the navigation part is definitely working.

    UPDATE:

    After reinstalling the autocomplete_light application and the test_project, the test_project is working. However my application is still not.

    opened by jglstewart 29
  • Is there a way to add a

    Is there a way to add a "loading" gif to the input?

    It would be useful to show a "loading" gif to the user into the input text while he's searching, otherwise it seems the request didn't find anything even if it's still running... Sorry if this already exists but I couldn't find it (I'm using version 2).

    opened by yliharma 28
  • Validation for

    Validation for "create new item"

    In my case I want some basic validation before the user can do "create new item".

    Use case: The user enters mail-adresses. I don't want to accept [email protected], but I want to accept [email protected].

    I looked at the source. I guess validation is not possible at the moment.

    What do you think?

    Here is the relevant code:

    
        def post(self, request):
            """Create an object given a text after checking permissions."""
            if not self.has_add_permission(request):
                return http.HttpResponseForbidden()
    
            if not self.create_field:
                raise ImproperlyConfigured()
    
            text = request.POST.get('text', None)
    
            if text is None:
                return http.HttpResponseBadRequest()
    
            result = self.create_object(text)
    
            return http.HttpResponse(json.dumps({
                'id': result.pk,
                'text': six.text_type(result),
            }))
    
    
    opened by guettli 24
  • 3.5.1 new Media order is not safely compatible with ModelAdmin.autocomplete_fields

    3.5.1 new Media order is not safely compatible with ModelAdmin.autocomplete_fields

    In the following setup, one inline uses autocomplete_fields the other inline uses DAL.

    It works in 3.5.0. In order to work in 3.5.1. the Media meta-calss should also be defined in the inline that uses autocomplete_fields.

    class WithDALForm(forms.ModelForm):
        field_with_DAL = forms.ModelMultipleChoiceField(
            ...
            widget=autocomplete.ModelSelect2Multiple()
        )
    
        class Media:
            js = ("//code.jquery.com/jquery-3.4.1.min.js",)
    
    
    class WithDALInline(admin.StackedInline):
        form = WithDALForm
    
    
    class WithAutocompleteInline(admin.StackedInline)
        autocomplete_fields = ('field_1', 'field_2')
    
    
    class Admin(admin.ModelAdmin):
        inlines = ('WithAutocompleteInline', 'WithDALIline')
    

    What follows is the order of the scripts in each case:

    3.5.1 with admin autocomplete (not working):

    <script type="text/javascript" src="/static/admin/js/vendor/jquery/jquery.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/jquery.init.js"></script>
    <script type="text/javascript" src="//code.jquery.com/jquery-3.4.1.min.js"></script>
    <script type="text/javascript" src="/static/admin/js/vendor/select2/select2.full.js"></script>
    <script type="text/javascript" src="/static/vendor/select2/dist/js/select2.full.js"></script>
    <script type="text/javascript" src="/static/admin/js/vendor/select2/i18n/en.js"></script>
    <script type="text/javascript" src="/static/vendor/select2/dist/js/i18n/en.js"></script>
    <script type="text/javascript" src="/static/admin/js/jquery.init.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/autocomplete.init.js"></script>
    <script type="text/javascript" src="/static/admin/js/core.js"></script>
    <script type="text/javascript" src="/static/admin/js/inlines.js"></script>
    <script type="text/javascript" src="/static/admin/js/autocomplete.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/forward.js"></script>
    <script type="text/javascript" src="/static/admin/js/admin/RelatedObjectLookups.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/select2.js"></script>
    <script type="text/javascript" src="/static/admin/js/collapse.js"></script>
    <script type="text/javascript" src="/static/admin/js/actions.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/jquery.post-setup.js"></script>
    <script type="text/javascript" src="/static/admin/js/urlify.js"></script>
    <script type="text/javascript" src="/static/admin/js/prepopulate.js"></script>
    <script type="text/javascript" src="/static/admin/js/vendor/xregexp/xregexp.js"></script>
    

    3.5.1 without admin autocomplete (working):

    <script type="text/javascript" src="/static/admin/js/vendor/jquery/jquery.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/jquery.init.js"></script>
    <script type="text/javascript" src="//code.jquery.com/jquery-3.4.1.min.js"></script>
    <script type="text/javascript" src="/static/admin/js/jquery.init.js"></script>
    <script type="text/javascript" src="/static/vendor/select2/dist/js/select2.full.js"></script>
    <script type="text/javascript" src="/static/admin/js/core.js"></script>
    <script type="text/javascript" src="/static/admin/js/inlines.js"></script>
    <script type="text/javascript" src="/static/vendor/select2/dist/js/i18n/en.js"></script>
    <script type="text/javascript" src="/static/admin/js/admin/RelatedObjectLookups.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/autocomplete.init.js"></script>
    <script type="text/javascript" src="/static/admin/js/collapse.js"></script>
    <script type="text/javascript" src="/static/admin/js/actions.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/forward.js"></script>
    <script type="text/javascript" src="/static/admin/js/urlify.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/select2.js"></script>
    <script type="text/javascript" src="/static/admin/js/prepopulate.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/jquery.post-setup.js"></script>
    <script type="text/javascript" src="/static/admin/js/vendor/xregexp/xregexp.js"></script>
    

    3.5.0 with admin autocomplete (working):

    <script type="text/javascript" src="/static/admin/js/vendor/jquery/jquery.js"></script>
    <script type="text/javascript" src="//code.jquery.com/jquery-3.4.1.min.js"></script>
    <script type="text/javascript" src="/static/admin/js/vendor/select2/select2.full.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/jquery.init.js"></script>
    <script type="text/javascript" src="/static/admin/js/vendor/select2/i18n/en.js"></script>
    <script type="text/javascript" src="/static/vendor/select2/dist/js/select2.full.js"></script>
    <script type="text/javascript" src="/static/admin/js/jquery.init.js"></script>
    <script type="text/javascript" src="/static/vendor/select2/dist/js/i18n/en.js"></script>
    <script type="text/javascript" src="/static/admin/js/core.js"></script>
    <script type="text/javascript" src="/static/admin/js/inlines.js"></script>
    <script type="text/javascript" src="/static/admin/js/autocomplete.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/autocomplete.init.js"></script>
    <script type="text/javascript" src="/static/admin/js/admin/RelatedObjectLookups.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/forward.js"></script>
    <script type="text/javascript" src="/static/admin/js/collapse.js"></script>
    <script type="text/javascript" src="/static/admin/js/actions.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/select2.js"></script>
    <script type="text/javascript" src="/static/admin/js/urlify.js"></script>
    <script type="text/javascript" src="/static/autocomplete_light/jquery.post-setup.js"></script>
    <script type="text/javascript" src="/static/admin/js/prepopulate.js"></script>
    <script type="text/javascript" src="/static/admin/js/vendor/xregexp/xregexp.js"></script>
    
    opened by raratiru 23
  • Above 1000 relations using 'through', the page takes 5 minutes to load.

    Above 1000 relations using 'through', the page takes 5 minutes to load.

    When I wasn't using through everything was in one field so django didn't create one field for each relation(as of now).

    Now that I'm using 'through' to intermediate the relation django tries to create on input for each of them and thus takes forever.

    The main purpose of library(as I see) is to remove the need to render a whole list and when in the need to add a new item, it won't need to query the whole database, it will get the ones matching a query which is not very harmful.

    Now the problem is back with this rendering problem. Any idea of how I can fix this and make the django admin page load faster?

    Here's a pic for clarification: image

    pending user feedback 
    opened by pbassut 23
  • ModelChoiceFields: Watch changes to 'queryset'

    ModelChoiceFields: Watch changes to 'queryset'

    A ModelChoiceField's 'queryset' attribute may be overwritten after the field has been created, for example in order to limit the choices that are offered. We should also reflect these changes in the autocompletion.

    A common idiom is to override queryset in a view or form, like in this example: https://docs.djangoproject.com/en/dev/ref/forms/fields/#fields-which-handle-relationships. Currently, these overrides are not reflected in the autocomplete. Hence it is possible to select related objects in the autocomplete that aren't part of queryset anymore, resulting in a validation error.

    This patch fixes this by always reflecting changes to queryset in the autocomplete's choices.

    needs-tests 
    opened by jonashaag 23
  • Fixed JS load order issues

    Fixed JS load order issues

    This PR seeks to solve the same problem as PRs #990, #1147, and #1157.

    I could not find any testing / contributing documentation, so please advise if there are any issues with this PR, I can adjust. I spun up the test_project. However, I am not sure what the results are supposed to be. Most things appeared to be working.

    Here is an overview of what I did.

    ❗ Update: The solution described here has been rewritten. See the update below.

    I initialized a NPM project including select2 as a dependency.

    From there, I used a number of JS build tools to bundle all the autocomplete files into a single JS file called autocomplete.js and a minified version autocomplete.min.js.

    The complicated part was handling i18n. I created a build script select2.build.js in the project root. It does two things.

    First, it collects all the Select2 language files, then writes them to src/dal/static/autocomplete_light/i18n/. It does not just copy each language file, it wraps the entire contents of each file in a function dalLoadLanguage(). The function takes a single argument jQuery. This allows us to call the function during the initialization.

    https://github.com/danielmorell/django-autocomplete-light/blob/e717499a4efe69446aaa5a1141cd4c49531c6ac0/src/dal/static/autocomplete_light/autocomplete.init.js#L71-L81

    It also registers and fires a custom event, dal-language-loaded. This is important since the file may be loaded after the main autocomplete.js bundle. I added an event listener that will call the dalLoadLanguage() function when dal-language-loaded is fired.

    The last thing select2.build.js does is concatenate all the JS files into autocomplete.js. It, does not attempt to perform any optimization or modulization on the code. It simply write them all in order into one file.

    Finally, autocomplete.js is minified using UglifyJS.

    The following build steps are now required.

    Install JS dependencies.

    $ npm install
    

    Build JS files.

    $ npm run build
    
    opened by danielmorell 22
  • Select2WidgetMixin Media as a dynamic property

    Select2WidgetMixin Media as a dynamic property

    partially fixes the issue #731.

    corrects partly because, django get_current_language templatetag return a low case string with language code, and this value is commonly used on html tag, like <html lang="pt-br">, see. if html have any html tag with lang="boo-bar-lang" exists, select2 use the value to try load the language files.

    The problem occur when the language is pt-br, sr-cyrl, zh-cn, zh-tw, because to these languages, some i18n files of select2 is not low case, then select2 can not find the files. (pt-BR.js, sr-Cyrl.js, zh-CN.js, zh-TW.js)

    opened by luzfcb 22
  • Widgets not showing up in and out of Admin panel

    Widgets not showing up in and out of Admin panel

    I've just installed Django-Autocomplete-Light and followed the guide on their site. I've setup the autocomplete-view and it works as intended (returning a JASON of autocomplete options). I can't seem to get the widgets to show up, though. in and out of the admin panel.

    Everything compiles without errors but instead the widget I get an empty drop down selection box.

    Image of empty selection box in admin panel

    This is my settings.py:

    INSTALLED_APPS = [
        'dal',
        'dal_select2',
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'selectable',
        'videos.apps.VideosConfig',
    ]
    

    This is views.py:

    class ActorAutocomplete(autocomplete.Select2QuerySetView):
        def get_queryset(self):
            # Don't forget to filter out results depending on the visitor !
    
            qs = Actor.objects.all()
    
            if self.q:
                qs = qs.filter(name__istartswith=self.q)
    
            return qs
    

    urls.py:

    url(r'^actor-autocomplete/$', views.ActorAutocomplete.as_view(), name='actor-autocomplete'), forms.py:

    class ActorForm(forms.ModelForm):
        class Meta:
            model = Actor
            fields = ('name',)
            widgets = {
                'name': autocomplete.Select(url='videos/actor-autocomplete')
            }
    

    It feels like I'm missing something simple, but I don't know what it is. Perhaps it can't access the static files, but adding 'dal' to installed_app in settings.py should have solved that according to the documentation. I'll appreciate any help!

    I forgot to mention that Actor.name is of type CharField. Perhaps I'm not using the correct widget, but I haven't found one that is specific for CharField.

    opened by mihaelfi 22
  • Add `class Formset(Forward)`

    Add `class Formset(Forward)`

    We have Self(Forward) which forward the field's own value.

    But the is django-autocomplete-light and Django supports Formsets.

    In a Formset, there a field naming convention (on the id and name attributes) that is roughly form-n-field where form is the name of the form and field the name of the field and n the form number in the set (0, 1, 2, 3, ...).

    Formset(Forward) would collect all the values of that field in the formset and submit them as a list. The server side can then use this list conveniently as an exclusion list to prevent for example, any to forms in the formset having the same selection.

    opened by bernd-wechner 0
  • Make unregistered forward handlers fail silently (or with warning) but non-breaking

    Make unregistered forward handlers fail silently (or with warning) but non-breaking

    Currently if you declare a forwarder with forward.JavaScript the named handler must be registered on the client side with yl.registerForwardHandler. If it is not then when the DAL widget is clicked it will display Searching.... and never complete, but on the console will appear this error:

    22:04:42.777 autocomplete_light.js:465 Uncaught TypeError: handler is not a function
        at Object.<anonymous> (autocomplete_light.js:465)
        at Function.each (jquery.js:381)
        at Object.yl.getForwards (autocomplete_light.js:438)
        at jQuery.fn.init.data (select2.js:60)
        at AjaxAdapter.query (select2.full.js:3620)
        at Select2.<anonymous> (select2.full.js:5669)
        at Select2.Observable.invoke (select2.full.js:655)
        at Select2.Observable.trigger (select2.full.js:645)
        at Select2.trigger (select2.full.js:5827)
        at Select2.open (select2.full.js:5851)
        at Select2.toggleDropdown (select2.full.js:5838)
        at DecoratedClass.<anonymous> (select2.full.js:5609)
        at DecoratedClass.Observable.invoke (select2.full.js:655)
        at DecoratedClass.Observable.trigger (select2.full.js:645)
        at HTMLSpanElement.<anonymous> (select2.full.js:1629)
        at HTMLSpanElement.dispatch (jquery.js:5429)
        at HTMLSpanElement.elemData.handle (jquery.js:5233)
    (anonymous) @ autocomplete_light.js:465
    each @ jquery.js:381
    yl.getForwards @ autocomplete_light.js:438
    data @ select2.js:60
    AjaxAdapter.query @ select2.full.js:3620
    (anonymous) @ select2.full.js:5669
    Observable.invoke @ select2.full.js:655
    Observable.trigger @ select2.full.js:645
    Select2.trigger @ select2.full.js:5827
    Select2.open @ select2.full.js:5851
    Select2.toggleDropdown @ select2.full.js:5838
    (anonymous) @ select2.full.js:5609
    Observable.invoke @ select2.full.js:655
    Observable.trigger @ select2.full.js:645
    (anonymous) @ select2.full.js:1629
    dispatch @ jquery.js:5429
    elemData.handle @ jquery.js:5233
    

    This should at worse issue a console warning and still function simply not calling (the unregistered) forward handler.

    This makes for more robust application (the application I had in mind was to declare a handler by default on all my model forms, and only avail myself of it in JS on those I wanted to. But now I need the smarts both client and server side as to which fields will have a forwarder attached or not.

    opened by bernd-wechner 0
  • Javascript isn't correctly initialized when loaded with HTMX

    Javascript isn't correctly initialized when loaded with HTMX

    First - great project, wonderful documentation!

    I am using dal with HTMX to create an autocomplete field in a modal window. (HTMX loads the modal window from a different View/url and appends it to the current page.) When I visit the HTMX URL directly, everything works fine. But, when I use HTMX to append that same working page to my existing page's DOM, the dropdown menu isn't initializing correctly.

    I see a bunch of stuff on Stack Overflow that references this kind of issue, but it's always (?) caused by forgetting lo load Javascript or form tags. I also see #1270 which is the only other reference to HTMX, but this seems to be a bit different than both of those.

    I have a feeling that stuff is getting loaded in the wrong order - that is, HTMX dumps the scripts into the DOM, and they execute on the page before the form has a chance to initialize. I believe this because the form works the second time it's loaded, or the third time if I mess with the order the libraries are loaded in. I am new to HTMX, so it's possible the PEBKAC.

    I was able to hack together a very ugly but functioning solution as follows. In the template that my HTMX view returns, I added jquery:

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
    {{ form.media }}
    {% load crispy_forms_tags %}
    ... some generic html here ...
    <p>{% crispy form form.helper %}</p>
    

    Then, I hacked autocomplete_light.js to split the load event listener into a separate function so I could call it manually:

    window.addEventListener("load", function () {
        console.log('This is never called.');
        dal_init();
    });
    
    function dal_init() {
        console.log('This gets called twice, I am not sure why.');
        // Check if `django.jQuery` exists otherwise set `django.jQuery` to non namespaced jQuery.
        window.django = window.django || {};
        if (!django.hasOwnProperty('jQuery') && jQuery !== 'undefined') {
            django.jQuery = jQuery;
        }
    ...the rest of this is unchanged except for the ) removed at the end of what used to be window.addEventListener(
    

    Finally, I added this snippet to autocomplete_light.js to call my newly created function:

    $( document ).ready(function() {
        dal_init();
    });
    

    This works, every time, as far as I can tell. I have already invested too much time in this to spend more time figuring out the root cause and putting together a PR, but maybe this will help someone else who is using this with HTMX (most likely me after I upgrade DAL in a year or two and my hacks break).

    Here is my complete autocomplete_light.js file:

    /*!
     * Django Autocomplete Light
     */
    
    var yl = yl || {};
    yl.functions = yl.functions || {};
    /**
     * Register your own JS function for DAL.
     *
     * @param name The name of your function. This should be the same as the widget
     *             `autocomplete_function` property value.
     * @param func The callback that will initialize your custom autocomplete.
     */
    yl.registerFunction = function (name, func) {
        if (this.functions.hasOwnProperty(name)) {
            // This function already exists to show an error and skip.
            console.error('The DAL function "' + name + '" has already been registered.');
            return
        }
        if (typeof func != 'function') {
            // It's not a function kill it.
            throw new Error('The custom DAL function must be a function.');
        }
        this.functions[name] = func;
        var event = new CustomEvent('dal-function-registered.' + name, {detail: {name: name, func: func}})
        window.dispatchEvent(event);
    };
    
    
    window.addEventListener("load", function () {
        console.log('This is never called.');
        dal_init();
    });
    
    function dal_init() {
        console.log('This gets called twice, I am not sure why.');
        // Check if `django.jQuery` exists otherwise set `django.jQuery` to non namespaced jQuery.
        window.django = window.django || {};
        if (!django.hasOwnProperty('jQuery') && jQuery !== 'undefined') {
            django.jQuery = jQuery;
        }
    
        (function ($) {
            $.fn.getFormPrefix = function () {
                /* Get the form prefix for a field.
                 *
                 * For example:
                 *
                 *     $(':input[name$=owner]').getFormsetPrefix()
                 *
                 * Would return an empty string for an input with name 'owner' but would return
                 * 'inline_model-0-' for an input named 'inline_model-0-owner'.
                 */
                var parts = $(this).attr('name').split('-');
                var prefix = '';
    
                for (var i in parts) {
                    var testPrefix = parts.slice(0, -i).join('-');
                    if (!testPrefix.length) continue;
                    testPrefix += '-';
    
                    var result = $(':input[name^=' + testPrefix + ']')
    
                    if (result.length) {
                        return testPrefix;
                    }
                }
    
                return '';
            }
    
            $.fn.getFormPrefixes = function () {
                /*
                 * Get the form prefixes for a field, from the most specific to the least.
                 *
                 * For example:
                 *
                 *      $(':input[name$=owner]').getFormPrefixes()
                 *
                 * Would return:
                 * - [''] for an input named 'owner'.
                 * - ['inline_model-0-', ''] for an input named 'inline_model-0-owner' (i.e. nested with a nested inline).
                 * - ['sections-0-items-0-', 'sections-0-', ''] for an input named 'sections-0-items-0-product'
                 *   (i.e. nested multiple time with django-nested-admin).
                 */
                var parts = $(this).attr('name').split('-').slice(0, -1);
                var prefixes = [];
    
                for (i = 0; i < parts.length; i += 2) {
                    var testPrefix = parts.slice(0, -i || parts.length).join('-');
                    if (!testPrefix.length)
                        continue;
    
                    testPrefix += '-';
    
                    var result = $(':input[name^=' + testPrefix + ']')
    
                    if (result.length)
                        prefixes.push(testPrefix);
                }
    
                prefixes.push('');
    
                return prefixes;
            }
    
            /*
             * This ensures the Language file is loaded and passes it our jQuery.
             */
            if (typeof dalLoadLanguage !== 'undefined') {
                dalLoadLanguage($);
            } else {
                document.addEventListener('dal-language-loaded', function (e) {
                    // `e.lang` is the language that was loaded.
                    dalLoadLanguage($);
                })
            }
    
            // Fire init event for yl.registerFunction() execution.
            var event = new CustomEvent('dal-init-function');
            document.dispatchEvent(event);
    
            var initialized = [];
    
            $.fn.excludeTemplateForms = function() {
                // exclude elements that contain '__prefix__' in their id
                // these are used by django formsets for template forms
                return this.not('[id*=__prefix__]').filter(function() {
                    // exclude elements that contain '-empty-' in their ids
                    // these are used by django-nested-admin for nested template formsets
                    // note that the filter also ensures that 'empty' is not actually the related_name for some relation
                    // by ensuring that it is not surrounded by numbers on both sides
                    return !this.id.match(/-empty-/) || this.id.match(/-\d+-empty-\d+-/);
                });
            }
    
            /**
             * Initialize a field element. This function calls the registered init function
             * and ensures that the element is only initialized once.
             *
             * @param element The field to be initialized
             */
            function initialize(element) {
                if (typeof element === 'undefined' || typeof element === 'number') {
                    element = this;
                }
    
                // Ensure element is not already initialized.
                if (initialized.indexOf(element) >= 0) {
                    return;
                }
    
                // The DAL function to execute.
                var dalFunction = $(element).attr('data-autocomplete-light-function');
    
                if (yl.functions.hasOwnProperty(dalFunction) && typeof yl.functions[dalFunction] == 'function') {
                    // If the function has been registered call it.
                    yl.functions[dalFunction]($, element);
                } else if (yl.functions.hasOwnProperty(dalFunction)) {
                    // If the function exists but has not been registered wait for it to be registered.
                    window.addEventListener('dal-function-registered.' + dalFunction, function (e) {
                        yl.functions[dalFunction]($, element);
                    })
                } else {
                    // Otherwise notify that the function should be registered.
                    console.warn('Your custom DAL function "' + dalFunction + '" uses a deprecated event listener that will be removed in future versions. https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#overriding-javascript-code')
                }
    
                // Fire init event for custom function execution.
                // DEPRECATED
                $(element).trigger('autocompleteLightInitialize');
    
                // Add element to the array of already initialized fields
                initialized.push(element);
    
                // creates and dispatches the event to notify of the initialization completed
                var dalElementInitializedEvent = new CustomEvent("dal-element-initialized", {
                    detail: {
                        element: element,
                    }
                });
    
                document.dispatchEvent(dalElementInitializedEvent);
            }
    
            if (!window.__dal__initialize) {
                window.__dal__initialize = initialize;
    
                $(document).ready(function () {
                    $('[data-autocomplete-light-function]').excludeTemplateForms().each(initialize);
                });
    
                if ('MutationObserver' in window) {
                    new MutationObserver(function (mutations) {
                        var mutationRecord;
                        var addedNode;
    
                        for (var i = 0; i < mutations.length; i++) {
                            mutationRecord = mutations[i];
    
                            if (mutationRecord.addedNodes.length > 0) {
                                for (var j = 0; j < mutationRecord.addedNodes.length; j++) {
                                    addedNode = mutationRecord.addedNodes[j];
    
                                    $(addedNode).find('[data-autocomplete-light-function]').excludeTemplateForms().each(initialize);
                                }
                            }
                        }
    
                    }).observe(document.documentElement, {childList: true, subtree: true});
                } else {
                    $(document).on('DOMNodeInserted', function (e) {
                        $(e.target).find('[data-autocomplete-light-function]').excludeTemplateForms().each(initialize);
                    });
                }
            }
    
            // using jQuery
            function getCookie(name) {
                var cookieValue = null;
                if (document.cookie && document.cookie != '') {
                    var cookies = document.cookie.split(';');
                    for (var i = 0; i < cookies.length; i++) {
                        var cookie = $.trim(cookies[i]);
                        // Does this cookie string begin with the name we want?
                        if (cookie.substring(0, name.length + 1) == (name + '=')) {
                            cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                            break;
                        }
                    }
                }
                return cookieValue;
            }
    
            document.csrftoken = getCookie('csrftoken');
            if (document.csrftoken === null) {
                // Try to get CSRF token from DOM when cookie is missing
                var $csrf = $('form :input[name="csrfmiddlewaretoken"]');
                if ($csrf.length > 0) {
                    document.csrftoken = $csrf[0].value;
                }
            }
        })(django.jQuery);
    
        // Does the same thing as django's admin/js/autocomplete.js, but uses yl.jQuery.
        (function ($) {
            'use strict';
            var init = function ($element, options) {
                var settings = $.extend({
                    ajax: {
                        data: function (params) {
                            return {
                                term: params.term,
                                page: params.page,
                                app_label: $element.data('app-label'),
                                model_name: $element.data('model-name'),
                                field_name: $element.data('field-name')
                            };
                        }
                    }
                }, options);
                $element.select2(settings);
            };
    
            $.fn.djangoAdminSelect2 = function (options) {
                var settings = $.extend({}, options);
                $.each(this, function (i, element) {
                    var $element = $(element);
                    init($element, settings);
                });
                return this;
            };
    
            $(function () {
                // Initialize all autocomplete widgets except the one in the template
                // form used when a new formset is added.
                $('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2();
            });
    
            $(document).on('formset:added', (function () {
                return function (event, $newFormset) {
                    return $newFormset.find('.admin-autocomplete').djangoAdminSelect2();
                };
            })(this));
        }(django.jQuery));
    
        (function ($, yl) {
            yl.forwardHandlerRegistry = yl.forwardHandlerRegistry || {};
    
            yl.registerForwardHandler = function (name, handler) {
                yl.forwardHandlerRegistry[name] = handler;
            };
    
            yl.getForwardHandler = function (name) {
                return yl.forwardHandlerRegistry[name];
            };
    
            function getForwardStrategy(element) {
                var checkForCheckboxes = function () {
                    var all = true;
                    $.each(element, function (ix, e) {
                        if ($(e).attr("type") !== "checkbox") {
                            all = false;
                        }
                    });
                    return all;
                };
    
                if (element.length === 1 &&
                    element.attr("type") === "checkbox" &&
                    element.attr("value") === undefined) {
                    // Single checkbox without 'value' attribute
                    // Boolean field
                    return "exists";
                } else if (element.length === 1 &&
                    element.attr("multiple") !== undefined) {
                    // Multiple by HTML semantics. E. g. multiple select
                    // Multiple choice field
                    return "multiple";
                } else if (checkForCheckboxes()) {
                    // Multiple checkboxes or one checkbox with 'value' attribute.
                    // Multiple choice field represented by checkboxes
                    return "multiple";
                } else {
                    // Other cases
                    return "single";
                }
            }
    
            /**
             * Get fields with name `name` relative to `element` with considering form
             * prefixes.
             * @param element the element
             * @param name name of the field
             * @returns jQuery object with found fields or empty jQuery object if no
             * field was found
             */
            yl.getFieldRelativeTo = function (element, name) {
                var prefixes = $(element).getFormPrefixes();
    
                for (var i = 0; i < prefixes.length; i++) {
                    var fieldSelector = "[name=" + prefixes[i] + name + "]";
                    var field = $(fieldSelector);
    
                    if (field.length) {
                        return field;
                    }
                }
    
                return $();
            };
    
            /**
             * Get field value which is put to forwarded dictionary
             * @param field the field
             * @returns forwarded value
             */
            yl.getValueFromField = function (field) {
                var strategy = getForwardStrategy(field);
                var serializedField = $(field).serializeArray();
    
                if ((serializedField == false) && ($(field).prop('disabled'))) {
                    $(field).prop('disabled', false);
                    serializedField = $(field).serializeArray();
                    $(field).prop('disabled', true);
                }
    
                var getSerializedFieldElementAt = function (index) {
                    // Return serializedField[index]
                    // or null if something went wrong
                    if (serializedField.length > index) {
                        return serializedField[index];
                    } else {
                        return null;
                    }
                };
    
                var getValueOf = function (elem) {
                    // Return elem.value
                    // or null if something went wrong
                    if (elem.hasOwnProperty("value") &&
                        elem.value !== undefined
                    ) {
                        return elem.value;
                    } else {
                        return null;
                    }
                };
    
                var getSerializedFieldValueAt = function (index) {
                    // Return serializedField[index].value
                    // or null if something went wrong
                    var elem = getSerializedFieldElementAt(index);
                    if (elem !== null) {
                        return getValueOf(elem);
                    } else {
                        return null;
                    }
                };
    
                if (strategy === "multiple") {
                    return serializedField.map(
                        function (item) {
                            return getValueOf(item);
                        }
                    );
                } else if (strategy === "exists") {
                    return serializedField.length > 0;
                } else {
                    return getSerializedFieldValueAt(0);
                }
            };
    
            yl.getForwards = function (element) {
                var forwardElem,
                    forwardList,
                    forwardedData,
                    divSelector,
                    form;
                divSelector = "div.dal-forward-conf#dal-forward-conf-for-" +
                    element.attr("id") + ", " +
                    "div.dal-forward-conf#dal-forward-conf-for_" +
                    element.attr("id");
                form = element.length > 0 ? $(element[0].form) : $();
    
                forwardElem =
                    form.find(divSelector).find('script');
                if (forwardElem.length === 0) {
                    return;
                }
                try {
                    forwardList = JSON.parse(forwardElem.text());
                } catch (e) {
                    return;
                }
    
                if (!Array.isArray(forwardList)) {
                    return;
                }
    
                forwardedData = {};
    
                $.each(forwardList, function (ix, field) {
                    var srcName, dstName;
                    if (field.type === "const") {
                        forwardedData[field.dst] = field.val;
                    } else if (field.type === "self") {
                        if (field.hasOwnProperty("dst")) {
                            dstName = field.dst;
                        } else {
                            dstName = "self";
                        }
                        forwardedData[dstName] = yl.getValueFromField(element);
                    } else if (field.type === "field") {
                        srcName = field.src;
                        if (field.hasOwnProperty("dst")) {
                            dstName = field.dst;
                        } else {
                            dstName = srcName;
                        }
                        var forwardedField = yl.getFieldRelativeTo(element, srcName);
    
                        if (!forwardedField.length) {
                            return;
                        }
    
                        forwardedData[dstName] = yl.getValueFromField(forwardedField);
                    } else if (field.type === "javascript") {
                        var handler = yl.getForwardHandler(field.handler);
                        forwardedData[field.dst || field.handler] = handler(element);
                    }
    
                });
                return JSON.stringify(forwardedData);
            };
    
        })(django.jQuery, yl);
    }
    $( document ).ready(function() {
        dal_init();
    });
    
    opened by iragm 0
  • DAL inside Wagtail with Telepath

    DAL inside Wagtail with Telepath

    Hello. I've search the docs and the issues and the entire web, but without any luck. Has anyone managed to get DAL working with Wagtail / Telepath? Anyone got any pointers? Thanks!

    opened by drcongo 2
  • Correctly render selected choice for ListSelect2 grouped fields

    Correctly render selected choice for ListSelect2 grouped fields

    ListSelect2 does not correctly render the selected choices when used with groups.

    This snippet works for groups but I did not make a PR as I'll let the way to detect or declare grouped vs non-grouped to you.

    class LgrGroupedListSelect2(autocomplete.ListSelect2):
    
        def filter_choices_to_render(self, selected_choices):
            # dal.widget.WidgetMixin.filter_choices_to_render does not handle correctly grouped lists
            ch = []
            for group in self.choices:
                c = [c for c in group[1] if str(c[0]) in selected_choices]
                ch.extend(c)
            self.choices = ch
    
    opened by j-bernard 2
Releases(2.2.0)
  • 2.2.0(Jun 2, 2015)

    PENDING BREAK

    The good old import autocomplete_light API support will be dropped with Django 1.9. All imports have moved to autocomplete_light.shortcuts and importing autocomplete_light will work until the project is used with Django 1.9.

    To be forward compatible with Django master (>=1.9) support, replace::

    import autocomplete_light
    

    By:

    from autocomplete_light import shortcuts as al
    

    This will also make your scripts a lot shorter.

    CSS BREAK

    We've moved back to pre-1.1.10 CSS positioning. This means appending the autocomplete box to an arbitrary DOM element (body by default) and using calculating the top and bottom attribute in javascript with yourlabs.Autocomplete.fixPosition() pretty much like Django admin's calendar widget does. While blunt, this change should help the widget being more compatible across Django admin themes.

    While this positioning system has been used since around 2005 in Django when Adrian Holovaty open sourced admin media in commit dd5320d, it has never been documented that's it's a good system that works well and there's no reason to break backward compatibility in Django admin for that

    • note to Django admin template customizers.

    JS BREAK

    Javascript yourlabs.Autocomplete object does not bind to the same events as it used too. Event handling has been backported from twitter typeahead and tested on firefox and android (#411).

    PYTHON BREAK

    The form field doesn't call super().validate() anymore and now completely relies on AutocompleteInterface.validate_values(). This was how django-autocomplete-light was initially designed for, kudos to @zhiyajun11 for pointing it out ! This optimises code which was doing validation twice and gives the flexibility it was initially designed for from within the Autocomplete class (#410).

    SQL BREAK

    Model Autocompletes now generate custom SQL be able to save the order in which users have filled an autocomplete field. This actually comes from the last 2.x version.

    CHANGES

    Most users won't notice the break except maybe the CSS ones and of course also for Django 1.9 users.

    • #419: ANSI SQL compliance (@sbaum)
    • #413: Exception when using models having primary key names different from id.
    • #412: Support models with a pk different than "id" and non-numeric. (@mhuailin)
    • #411: Android compatibility (js bind changes).
    • #410: Removed double validation by not calling suport of Field.validate().
    • #408: Support Django 1.8 change-link.
    • #409: Compatibility with non-autocomplete inputs present in the widget (@SebCorbin)
    • #318: Remove extra spaces rendered in choices.
    • #438: Hide autocomplete on scroll in Firefox because it bugs (temp fix).
    • #432: New bootstrap_modal test_app by @lucky-user.
    • #118: Extracted JS into a standalone jquery-plugin: https://github.com/yourlabs/jquery-autocomplete-light
    • Reduce default latency because hardware is better.
    • #426: handle z-index since we're absolutely-positioning in 2.2.x

    CONTRIBUTING CHANGES

    The JS part has been extracted to be packaged as a standalone jQuery library to get more pull requests on the JS / CSS part. It sounds like a pretty good start in the JS / UI testing and packaging world. Any help there is welcome. CI now has tests against MySQL and PostgreSQL since we're generating custom SQL.

    Again welcome to new contributors @lucky-user @mhuailin and @SebCorbin and thanks all for reporting issues on GitHub with all needed details and forks which make it easy to reproduce.

    And thanks to @blueyed who helped sinking this year's backlog like crazy.

    Source code(tar.gz)
    Source code(zip)
Owner
YourLabs
OSS Hack'n'Dev, we provide all kind of paid services on OSS and sponsor OSS
YourLabs
Django REST Client API

Django REST Client API Client data provider API.

Ulysses Monteiro 1 Nov 08, 2021
Py-instant-search-redis - Source code example for how to build an instant search with redis in python

py-instant-search-redis Source code example for how to build an instant search (

Giap Le 4 Feb 17, 2022
Send logs to RabbitMQ from Python/Django.

python-logging-rabbitmq Logging handler to ships logs to RabbitMQ. Compatible with Django. Installation Install using pip. pip install python_logging_

Alberto Menendez Romero 38 Nov 17, 2022
Django Audit is a simple Django app that tracks and logs requests to your application.

django-audit Django Audit is a simple Django app that tracks and logs requests to your application. Quick Start Install django-audit pip install dj-au

Oluwafemi Tairu 6 Dec 01, 2022
It takes time to start a Django Project and make it almost production-ready.

It takes time to start a Django Project and make it almost production-ready. A developer needs to spend a lot of time installing required libraries, setup a database, setup cache as well as hiding se

Khan Asfi Reza 1 Jan 01, 2022
based official code from django channels, replace frontend with reactjs

django_channels_chat_official_tutorial demo project for django channels tutorial code from tutorial page: https://channels.readthedocs.io/en/stable/tu

lightsong 1 Oct 22, 2021
Simple tagging for django

django-taggit This is a Jazzband project. By contributing you agree to abide by the Contributor Code of Conduct and follow the guidelines. django-tagg

Jazzband 3k Jan 02, 2023
Django project starter on steroids: quickly create a Django app AND generate source code for data models + REST/GraphQL APIs (the generated code is auto-linted and has 100% test coverage).

Create Django App 💛 We're a Django project starter on steroids! One-line command to create a Django app with all the dependencies auto-installed AND

imagine.ai 68 Oct 19, 2022
Phoenix LiveView but for Django

Reactor, a LiveView library for Django Reactor enables you to do something similar to Phoenix framework LiveView using Django Channels. What's in the

Eddy Ernesto del Valle Pino 526 Jan 02, 2023
django social media app with real time features

django-social-media django social media app with these features: signup, login and old registered users are saved by cookies posts, comments, replies,

8 Apr 30, 2022
Modular search for Django

Haystack author: Daniel Lindsley date: 2013/07/28 Haystack provides modular search for Django. It features a unified, familiar API that allows you to

Daniel Lindsley 4 Dec 23, 2022
Python port of Google's libphonenumber

phonenumbers Python Library This is a Python port of Google's libphonenumber library It supports Python 2.5-2.7 and Python 3.x (in the same codebase,

David Drysdale 3.1k Jan 08, 2023
A Redis cache backend for django

Redis Django Cache Backend A Redis cache backend for Django Docs can be found at http://django-redis-cache.readthedocs.org/en/latest/. Changelog 3.0.0

Sean Bleier 1k Dec 15, 2022
Neighbourhood - A python-django web app to help the residence of a given neighborhood know their surrounding better

Neighbourhood A python-django web app to help the residence of a given neighborh

Levy Omolo 4 Aug 25, 2022
A blog app powered by python-django

Django_BlogApp This is a blog app powered by python-django Features Add and delete blog post View someone else blog Can add comment to that blog And o

Manish Jalui 1 Sep 12, 2022
Drf-stripe-subscription - An out-of-box Django REST framework solution for payment and subscription management using Stripe

Drf-stripe-subscription - An out-of-box Django REST framework solution for payment and subscription management using Stripe

Oscar Y Chen 68 Jan 07, 2023
📝 Sticky Notes in Django admin

django-admin-sticky-notes Share notes between superusers. Installation Install via pip: pip install django_admin_sticky_notes Put django_admin_sticky_

Dariusz Choruży 7 Oct 06, 2021
A standalone package to scrape financial data from listed Vietnamese companies via Vietstock

Scrape Financial Data of Vietnamese Listed Companies - Version 2 A standalone package to scrape financial data from listed Vietnamese companies via Vi

Viet Anh (Vincent) Tran 45 Nov 16, 2022
DCM is a set of tools that helps you to keep your data in your Django Models consistent.

Django Consistency Model DCM is a set of tools that helps you to keep your data in your Django Models consistent. Motivation You have a lot of legacy

Occipital 59 Dec 21, 2022
Django-Text-to-HTML-converter - The simple Text to HTML Converter using Django framework

Django-Text-to-HTML-converter This is the simple Text to HTML Converter using Dj

Nikit Singh Kanyal 6 Oct 09, 2022