python manage.py startapp inventoryINSTALLED_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 migratefrom 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/cssCreate 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: