Ron
Ron

Reputation: 245

How to insert placeholder in Django Admin autocomplete input?

I don't want to use my own JS or alter the template when Django has other mechanisms.

I've tried three methods, first as described in how-to-add-placeholder-text-to-a-django-admin-field and in the docs.

from django import forms

    class AppointmentAdmin(admin.ModelAdmin):
        
        def get_form(self, request, obj=None, **kwargs):
            kwargs['widgets'] = {
                'name': forms.TextInput(attrs={'placeholder': 'Type here'})
            }
            return super().get_form(request, obj, **kwargs)

Then the method described in django-add-placeholder-text-to-form-field, slightly altered to get around Python errors.

class AppointmentAdmin(admin.ModelAdmin):
    
   def get_form(self, request, obj=None, **kwargs):
      form = super().get_form(request, obj, **kwargs)
      form.base_fields['customer'].widget.attrs["placeholder"] = "Type here"
      return form

Finally, using formfield_overrides:

class AppointmentAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.TextField: { "widget":forms.TextInput(attrs={'placeholder':'Type here'})},
    }

None of these are working. I must be overlooking something simple?

The relevant portion of models.py:

class Appointment(models.Model):
    customer = models.ForeignKey(Customer, blank=False, null=True, default=None, on_delete=models.CASCADE, related_name='appointments')
    autocomplete_fields = ["customer"]

I removed the autocomplete_fields line to no effect vis a vis a placeholder. (Side note: I think autocomplete_fields should have a placeholder option rather than requiring all the jumping through hoops as described above.)

The 'help_text' option on the field in models.py would almost be a cheap replacement for a placeholder, except that it makes no sense in other parts of this program.

Here's the HTML that Django generates using the second method above (form.base_fields...etc.):

<div class="related-widget-wrapper" data-model-ref="customer">
<select name="customer" placeholder="Type here" required="" id="id_customer" class="admin-autocomplete select2-hidden-accessible" data-ajax--cache="true" data-ajax--delay="250" data-ajax--type="GET" data-ajax--url="/admin/autocomplete/" data-app-label="giraffe" data-model-name="appointment" data-field-name="customer" data-theme="admin-autocomplete" data-allow-clear="false" data-placeholder="" lang="en" data-select2-id="id_customer" tabindex="-1" aria-hidden="true">
</select>
        
<span class="select2-container select2-container--admin-autocomplete select2-container--open" style="position: absolute; top: 164.484px; left: 15px;">
<span class="select2-dropdown select2-dropdown--above" dir="ltr" style="width: 249px;">
<span class="select2-search select2-search--dropdown">
<input class="select2-search__field" type="search" tabindex="0" autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false" role="searchbox" aria-autocomplete="list" aria-controls="select2-id_customer-results">
</span>
<span class="select2-results">
<ul class="select2-results__options" role="listbox" id="select2-id_customer-results" aria-expanded="true" aria-hidden="false">
<li class="select2-results__option" role="option" aria-selected="false" data-select2-id="6">customer name here</li>
        (more li and closing of spans here)

UPDATE: Django implements autocomplete select boxes using Select2, whose docs say:

For single selects only, in order for the placeholder value to appear, you must have a blank as the first option in your control. This is because the browser tries to select the first option by default. If your first option were non-empty, the browser would display this instead of the placeholder.

But Django inserts a customer as the first option. Though it's non-empty, the browser doesn't display it as described, but maybe it's still breaking the Select2 placeholder insert? Maybe the answer is how to make Django insert an empty object?

Upvotes: 0

Views: 426

Answers (1)

aaron
aaron

Reputation: 43073

  1. autocomplete_fields is defined on ModelAdmin, rather than Model.

  2. It is attrs['data-placeholder'] rather than attrs['placeholder']. However, Django doesn't properly support it as data-placeholder is overwritten in AutocompleteMixin:

    class AutocompleteMixin:
        ...
    
        def build_attrs(self, base_attrs, extra_attrs=None):
            ...
            attrs = super().build_attrs(base_attrs, extra_attrs=extra_attrs)
            ...
            attrs.update({
                ...
                'data-placeholder': '',  # Allows clearing of the input.
                ...
            })
            return attrs
    

You can patch AutocompleteSelect widget to restore data-placeholder:

from django.contrib.admin import options


class AutocompleteSelectWithPlaceholder(options.AutocompleteSelect):
    def build_attrs(self, base_attrs, extra_attrs=None):
        attrs = super().build_attrs(base_attrs, extra_attrs=extra_attrs)
        if 'data-placeholder' in base_attrs:
            attrs['data-placeholder'] = base_attrs['data-placeholder']
        return attrs


options.AutocompleteSelect = AutocompleteSelectWithPlaceholder

Usage:

@admin.register(Appointment)
class AppointmentAdmin(admin.ModelAdmin):
    autocomplete_fields = ['customer']

    def get_form(self, request, obj=None, **kwargs):
        form = super().get_form(request, obj, **kwargs)
        form.base_fields['customer'].widget.attrs['data-placeholder'] = 'Type here'
        return form

Upvotes: 1

Related Questions