简体   繁体   中英

How to use autocompleteselect widget in a modelform

I know there is a new feature in Django 2.0, which is AutocompleteSelect widget in ModelAdmin. I am trying to use it in my custom modelForm but just failed.

Tried like this

#unit is the foreign key to the incident

class AccountForm(forms.ModelForm):
    class Meta:
        model = Invoice
        ...
        ...
        widgets = {       'incident':widgets.AutocompleteSelect(Invoice._meta.get_field('incident').remote_field, admin.site)
        }
        ...
#Invoice model

class Invoice(models.Model):
    ...
    incident = models.ForeignKey(Unit, on_delete=models.CASCADE,null=True)
    ...

Hope anyone can help me. Thanks

The AutocompleteSelect widget will not work outside of the admin site. If you are using AccountForm in admin site you can use the following code:

class AccountForm(forms.ModelForm):
    ...
    incident = forms.ModelChoiceField(
                 queryset= Unit.objects.all(),
                 widget=AutocompleteSelect(Invoice.incident.field.remote_field, admin.site),
               )
    ...
    class Meta:
        model = Invoice
        fields = [
            'incident',
            ...
        ]

@admin.register(Invoice)
class InvoiceAdmin(admin.ModelAdmin):
    form = AccountForm

AutocompleteSelect has 2 required args, rel and admin_site . The rel is used to extract the model used to query the data from and relates to an attribute on a ForeignKey or ManyToManyField. Since I wanted to use this on a field that wasn't actually a ForeignKey, I needed to override a few things to make it work:

class ClientAutocompleteSelect(AutocompleteSelect):
    def get_url(self):
        model = Client
        return reverse(self.url_name % (self.admin_site.name, model._meta.app_label, model._meta.model_name))

class ClientChoiceField(forms.ModelChoiceField):
    def __init__(self, queryset=None, widget=None, **kwargs):
        if queryset is None:
            queryset = Client.objects.all()
        if widget is None:
            widget = ClientAutocompleteSelect(None, admin.site)  # pass `None` for `rel`
        super().__init__(queryset, widget=widget, **kwargs)

    def to_python(self, value):
        return value  # just return the client_id and not a Client object

class MyAdminForm(forms.ModelForm):
    client_id=ClientChoiceField()

    ...

This requires that the end user has admin read access to the autocomplete endpoint of the model being queried. You may be able to do more hacking to change that get_url and use your own endpoint to give search results, though.

I spent a few hours trying to understand why my code (built on @Tim 's answer) would not work, until I stumble on a comment about missing references to css/js files.

Here is a complete working solution to use the AutocompleteSelect widget in any custom form for signed-in users having both 'staff' and 'view' access to the given model:

from django.urls import reverse
from django.contrib.admin.widgets import AutocompleteSelect
from django.contrib import admin

class UserAutocompleteSelect(AutocompleteSelect):
    def get_url(self):
        model = CustomUser
        return reverse(self.url_name % (self.admin_site.name, model._meta.app_label, model._meta.model_name))

class UserChoiceField(forms.ModelChoiceField):
    def __init__(self, queryset=None, widget=None, **kwargs):
        if queryset is None:
            queryset = CustomUser.objects.all()
        if widget is None:
            widget = UserAutocompleteSelect(None, admin.site)  # pass `None` for `rel`
        super().__init__(queryset, widget=widget, **kwargs)

class UserAutocompleteSelectForm(forms.ModelForm):
    """
    for changing user on Play objects
    using amdin module autocomplete
    """
    user = UserChoiceField(
        # queryset=CustomUser.objects.all(),
        help_text=_('Select the user to replace the current one')
    )

    class Meta:
        model = Play
        fields = ('user', )

You can use the same, replacing CustomUser and Play by your own models

If (like me) this is not working out-of-the-box with the html template you're using, that means that you need to include the required css/js files to your template. Here is a simple way to do it:

Providing that the form is declared as such in the view:

form = UserAutocompleteSelectForm()
...
context = {
            'form': form, 
            ...
            }
return render(request, 'users/change_user.html', context)

you should add the following lines to the html template to include the required css/js files:

{% block extrahead %}
{{ block.super }}
{{ form.media }}
{% endblock %}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM