1. Home
  2. Django Admin Unfold
  3. Installation And Configur...
  4. 05. AutoComplete

05. AutoComplete

python manage.py startapp inventory

INSTALLED_APPS = [
    'unfold',  # Add this line
    'unfold.contrib.filters',  # Add this line
    'unfold.contrib.forms',  # Add this line
    'unfold.contrib.import_export',  # Add this line
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'inventory',  # Add this line
]

from django.db import models
from django.utils.translation import gettext_lazy as _

class Item(models.Model):
    item_code = models.CharField(max_length=20, unique=True)
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    unit_price = models.DecimalField(max_digits=10, decimal_places=2)
    unit_of_measure = models.CharField(max_length=20)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.item_code} - {self.name}"

class StockTransfer(models.Model):
    TRANSFER_STATUS = (
        ('DRAFT', _('Draft')),
        ('PENDING', _('Pending')),
        ('COMPLETED', _('Completed')),
        ('CANCELLED', _('Cancelled')),
    )

    transfer_number = models.CharField(max_length=20, unique=True)
    transfer_date = models.DateTimeField()
    status = models.CharField(max_length=10, choices=TRANSFER_STATUS, default='DRAFT')
    notes = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.transfer_number} - {self.status}"

class StockTransferItem(models.Model):
    stock_transfer = models.ForeignKey(StockTransfer, on_delete=models.CASCADE, related_name='items')
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    quantity = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return f"{self.stock_transfer.transfer_number} - {self.item.name} - {self.quantity}"

python manage.py makemigrations
python manage.py migrate

from django.contrib import admin
from unfold.admin import ModelAdmin
from .models import Item, StockTransfer, StockTransferItem

@admin.register(Item)
class ItemAdmin(ModelAdmin):
    list_display = ('item_code', 'name', 'unit_price', 'unit_of_measure', 'is_active')
    list_filter = ('is_active', 'unit_of_measure')
    search_fields = ('item_code', 'name', 'description')
    ordering = ('item_code',)

    def get_search_results(self, request, queryset, search_term):
        queryset, use_distinct = super().get_search_results(request, queryset, search_term)
        if not search_term:
            queryset = queryset[:10]  # Limit to 10 items when no search term
        return queryset, use_distinct

class StockTransferItemInline(admin.TabularInline):
    model = StockTransferItem
    extra = 1
    autocomplete_fields = ['item']

@admin.register(StockTransfer)
class StockTransferAdmin(ModelAdmin):
    list_display = ('transfer_number', 'transfer_date', 'status')
    list_filter = ('status',)
    search_fields = ('transfer_number', 'notes')
    ordering = ('-transfer_date',)
    inlines = [StockTransferItemInline]

    class Media:
        css = {
            'all': ('admin/css/admin-autocomplete.css',)
        }
        js = ('admin/js/admin-autocomplete.js',)

Open project/urls.py and update it as follows:

from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('admin/autocomplete/', admin.site.autocomplete_view),
]

Step 7: Create static files for autocomplete

Create a new directory structure for static files:

mkdir -p static/admin/js static/admin/css

Create static/admin/js/admin-autocomplete.js with the following content:

(function($) {
    $(document).ready(function() {
        function initializeAutocomplete(row) {
            $(row).find('.admin-autocomplete').select2({
                ajax: {
                    url: '/admin/autocomplete/',
                    dataType: 'json',
                    delay: 250,
                    data: function (params) {
                        return {
                            term: params.term || '',
                            page: params.page || 1,
                            app_label: 'inventory',
                            model_name: 'item',
                            field_name: 'name'
                        };
                    },
                    processResults: function (data) {
                        return {
                            results: data.results,
                            pagination: {
                                more: data.pagination.more
                            }
                        };
                    },
                    cache: true
                },
                minimumInputLength: 0,
                placeholder: 'Search for an item...',
            });
        }

        // Initialize autocomplete for existing rows
        $('.inline-related').each(function() {
            initializeAutocomplete(this);
        });

        // Initialize autocomplete for newly added rows
        $(document).on('formset:added', function(event, $row, formsetName) {
            initializeAutocomplete($row);
        });

        // Trigger initial load for empty dropdowns
        $('.admin-autocomplete').each(function() {
            var $select = $(this);
            var $dropdown = $select.next('.select2-container').find('.select2-selection');
            $dropdown.on('click', function() {
                if (!$select.data('select2').isOpen()) {
                    $select.select2('open');
                    $select.data('select2').dataAdapter.query({}, function() {});
                }
            });
        });
    });
})(django.jQuery);

Create static/admin/css/admin-autocomplete.css with the following content:

.select2-container--admin-autocomplete .select2-selection--single {
    height: 30px;
    padding: 2px 6px;
    font-size: 14px;
    line-height: 1.42857143;
    color: #555;
    background-color: #fff;
    background-image: none;
    border: 1px solid #ccc;
    border-radius: 4px;
}

.select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
    padding-left: 0;
}

.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
    height: 26px;
    position: absolute;
    top: 1px;
    right: 1px;
    width: 20px;
}

.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow b {
    border-color: #888 transparent transparent transparent;
    border-style: solid;
    border-width: 5px 4px 0 4px;
    height: 0;
    left: 50%;
    margin-left: -4px;
    margin-top: -2px;
    position: absolute;
    top: 50%;
    width: 0;
}

.select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
    border: 1px solid #ccc;
    border-radius: 4px;
}

.select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
    background-color: #5897fb;
    color: white;
}

Step 8: Collect static files

Run the following command to collect all static files:

How can we help?