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
The best way to have DRY Django forms. The app provides a tag and filter that lets you quickly render forms in a div format while providing an enormous amount of capability to configure and control the rendered HTML.

django-crispy-forms The best way to have Django DRY forms. Build programmatic reusable layouts out of components, having full control of the rendered

4.6k Dec 31, 2022
A Python HTML form library.

Deform Introduction Use cases Installation Example Status Projects using Deform Community and links Introduction Deform is a Python form library for g

Pylons Project 391 Jan 03, 2023
Simple integration of Flask and WTForms, including CSRF, file upload and Recaptcha integration.

Flask-WTF Simple integration of Flask and WTForms, including CSRF, file upload, and reCAPTCHA. Links Documentation: https://flask-wtf.readthedocs.io/

WTForms 1.3k Jan 04, 2023
Streaming parser for multipart/form-data written in Python

Streaming multipart/form-data parser streaming_form_data provides a Python parser for parsing multipart/form-data input chunks (the encoding used when

Siddhant Goel 112 Dec 29, 2022
Easy and free contact form on your HTML page. No backend or JS required.

Easy and free contact form on your HTML page. No backend or JS required. 🚀 💬

0xDEADF00D 8 Dec 16, 2022
Tweak the form field rendering in templates, not in python-level form definitions. CSS classes and HTML attributes can be altered.

django-widget-tweaks Tweak the form field rendering in templates, not in python-level form definitions. Altering CSS classes and HTML attributes is su

Jazzband 1.8k Jan 06, 2023
Bootstrap 4 integration with Django.

django-bootstrap 4 Bootstrap 4 integration for Django. Goal The goal of this project is to seamlessly blend Django and Bootstrap 4. Requirements Pytho

Zostera B.V. 979 Dec 26, 2022
FlaskBB is a Forum Software written in Python using the micro framework Flask.

FlaskBB is a Forum Software written in Python using the micro framework Flask.

FlaskBB 2.3k Dec 30, 2022
Bootstrap 3 integration with Django.

django-bootstrap3 Bootstrap 3 integration for Django. Goal The goal of this project is to seamlessly blend Django and Bootstrap 3. Want to use Bootstr

Zostera B.V. 2.3k Dec 24, 2022
A platform independent django form serializer

django-remote-forms A package that allows you to serialize django forms, including fields and widgets into Python dictionary for easy conversion into

WiserTogether, Inc. 219 Sep 20, 2022
A set of high-level abstractions for Django forms

django-formtools Django's "formtools" is a set of high-level abstractions for Django forms. Currently for form previews and multi-step forms. This cod

Jazzband 619 Dec 23, 2022
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

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 suppo

YourLabs 1.7k Jan 01, 2023
A CBV to handle multiple forms in one view

django-shapeshifter A common problem in Django is how to have a view, especially a class-based view that can display and process multiple forms at onc

Kenneth Love 167 Nov 26, 2022
A flexible forms validation and rendering library for Python.

WTForms WTForms is a flexible forms validation and rendering library for Python web development. It can work with whatever web framework and template

WTForms 1.4k Dec 31, 2022
Full control of form rendering in the templates.

django-floppyforms Full control of form rendering in the templates. Authors: Gregor Müllegger and many many contributors Original creator: Bruno Renié

Jazzband 811 Dec 01, 2022
Automate your google form here!

Google Form Filler (GFF) - Automate your google form here! About The idea of this project came from my online lectures as one of my professors takes a

Jay Thorat 13 Jan 05, 2023
The best way to have DRY Django forms. The app provides a tag and filter that lets you quickly render forms in a div format while providing an enormous amount of capability to configure and control the rendered HTML.

django-crispy-forms The best way to have Django DRY forms. Build programmatic reusable layouts out of components, having full control of the rendered

4.6k Jan 05, 2023
Simple integration of Flask and WTForms, including CSRF, file upload and Recaptcha integration.

Flask-WTF Simple integration of Flask and WTForms, including CSRF, file upload, and reCAPTCHA. Links Documentation: https://flask-wtf.readthedocs.io/

WTForms 1.3k Jan 04, 2023