In formfield_for_dbfield, don't skip adding the RelatedFieldWrapper (add link)

Fabian Büchler avatarFabian Büchler created an issue

In django.contrib.admin.options.BaseModelAdmin.formfield_for_dbfield, which you are overriding in autocomplete.admin.AutocompleteAdmin, all ForeignKey and ManyToMany field widgets are usually wrapped with the django.contrib.admin.widgets.RelatedFieldWidgetWrapper, which adds an "add another link" (plus icon) to such fields, if the user is has the add permission for the model in question.

Your formfield_to_dbfield would look like follows, to make this work again:

    def formfield_for_dbfield(self, db_field, **kwargs):
        request = kwargs.get("request", None)
        
        if db_field.name in self.autocomplete_fields:
            ac_id = self.autocomplete_fields[db_field.name]
            formfield = self.autocomplete_formfield(
                ac_id, db_field.formfield, **kwargs)
        elif (self.autocomplete_autoconfigure and
              db_field in self.autocomplete_view.settings):
            formfield = self.autocomplete_formfield(db_field, **kwargs)
        else:
            formfield = super(AutocompleteAdmin, self).formfield_for_dbfield(
                db_field, **kwargs)
        
        if (isinstance(db_field, (models.ForeignKey, models.ManyToManyField)) and
            formfield and db_field.name not in self.raw_id_fields):
            related_modeladmin = self.admin_site._registry.get(
                db_field.rel.to)
            can_add_related = bool(related_modeladmin and
                related_modeladmin.has_add_permission(request))
            formfield.widget = admin.widgets.RelatedFieldWidgetWrapper(
                formfield.widget, db_field.rel, self.admin_site,
                can_add_related=can_add_related)
        
        return formfield

Regards, Fabian

Comments (9)

  1. Fabian Büchler

    I just realized, that when using the add-another button, saving and closing the popup fills the autocomplete field with the ID of the newly created object. This needs to be changed so that the hidden field recieves the ID and the visible field is filled with a title/name,...

    Basically the function involved is dismissAddAnotherPopup() in contrib/admin/media/js/admin/RelatedObjectLookups.js

    Actually, I'm not quite sure what is the best way to make this work. Do you have a good idea?

    Regards, Fabian

  2. Fabian Büchler

    I've researched the problem mentioned above a bit, and found it not trivial to solve. One solution would be to overwrite the dismissAddAnotherPopup JavaScript function defined in RelatedObjectLookups.js like follows in your jquery_autocomplete.js file:

    if (typeof(dismissAddAnotherPopup) != 'undefined') {
        var original_dismissAddAnotherPopup = dismissAddAnotherPopup;
        dismissAddAnotherPopup = function (win, newId, newRepr) {
            (function($){
                // newId and newRepr are expected to have previously been escaped by
                // django.utils.html.escape.
                newId = html_unescape(newId);
                newRepr = html_unescape(newRepr);
                var name = windowname_to_id(win.name);
                var elem = $('#'+name);
                
                // append 
                if (elem.length > 0 && elem.is('input:text')) {
                    elem.val(newRepr);
                    // ### needs to handle ManyToMany fields, too
                }
                else {
                    original_dismissAddAnotherPopup(win, newId, newRepr);
                }
                
                // fake select event of autocomplete result list
                var ui = new Object();
                ui.item = new Object();
                ui.item.id = newId;
                ui.item.value = newRepr;
                elem.autocomplete('option', 'select')(
                    $.Event('autocompleteselect'), ui);
                
                win.close();
            }(django.jQuery));
        }
    }
    

    This seems a bit freaky, but it seems to work. The code above only handles foreignkey fields, no manytomany fields.

    A better solution would certainly be to rewrite te RelatedObjectLookups.js into a jQuery plugin architecture which could allow to pass a custom popup-dismiss function.

    Regards, Fabian

  3. Germano Gabbianelli

    If you make a patch (with "hg export" or "hg diff") people interested in this feature would be able to use it, and I could consider pushing it into the repo.

    Maybe rewriting RelatedObjectLookup.js with jQuery is a planned thing since they switched to jQuery in the admin site.

  4. Fabian Büchler

    Hej Germano,

    it took me a while to find the time to do this, but finally I've created a patch queue to add my changes as promised: https://bitbucket.org/fabianbuechler/django-autocomplete_related-field-wrapper

    If you prefer that I fork your repository and send you a pull request, that's fine, too. Just thought using MQ would be the right way.

    There are two things missing in my implementation: the JS fix for the dismissAddAnotherPopup function lacks of handling for ManyToMany fields and I have not tested the patch, because I don't really know how to do this as I've just installed django-autocomplete in one project via PyPi...

    Guess I could use mercurialrecipe to install my own version of django-autocomplete including patches... what do you think?

  5. Germano Gabbianelli

    That's great!

    If you prefer that I fork your repository and send you a pull request, that's fine, too. Just thought using MQ would be the right way.

    Using MQ is just perfect, so you can already start using your patch.

    I had a few problems trying to qclone your repo with ssh (using regular http I succeeded). I will try your code as soon as possible, however if you could add what you think is missing it will probably be committed sooner.

    and I have not tested the patch, because I don't really know how to do this

    Running the test suite is now as simple as typing "tox" from the django-autocomplete root dir. (Writing more tests is an highest priority since there aren't so many)

    If you have anything else to ask feel free to send me a private message.

  6. Log in to comment
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.