0
# Admin Extensions
1
2
Django Extensions provides enhanced Django admin functionality including custom filters and widgets for improved administrative interfaces. These extensions integrate seamlessly with Django's admin system to provide better user experience and additional functionality.
3
4
## Capabilities
5
6
### Admin Filters
7
8
Custom filter classes that enhance Django admin list views with specialized filtering options.
9
10
```python { .api }
11
class NullFieldListFilter(admin.SimpleListFilter):
12
title: str = 'has value'
13
parameter_name: str = 'has_value'
14
15
def __init__(self, request, params, model, model_admin):
16
"""
17
Admin filter for handling null/non-null field filtering.
18
19
Provides filtering options for fields that can be null:
20
- "Has value" (field is not null)
21
- "No value" (field is null)
22
23
Usage in ModelAdmin:
24
list_filter = [('field_name', NullFieldListFilter)]
25
"""
26
27
def lookups(self, request, model_admin):
28
"""
29
Return filter options.
30
31
Returns:
32
- tuple: (('1', 'Has value'), ('0', 'No value'))
33
"""
34
35
def queryset(self, request, queryset):
36
"""
37
Filter the queryset based on selection.
38
39
Parameters:
40
- request: HttpRequest object
41
- queryset: QuerySet to filter
42
43
Returns:
44
- QuerySet: Filtered queryset
45
"""
46
```
47
48
Usage examples:
49
50
```python
51
from django.contrib import admin
52
from django_extensions.admin import NullFieldListFilter
53
from myapp.models import Article
54
55
@admin.register(Article)
56
class ArticleAdmin(admin.ModelAdmin):
57
list_display = ['title', 'author', 'published_date', 'featured_image']
58
59
# Filter by fields that can be null
60
list_filter = [
61
('published_date', NullFieldListFilter),
62
('featured_image', NullFieldListFilter),
63
('author', NullFieldListFilter),
64
]
65
66
# Standard filters can be mixed with custom filters
67
list_filter = [
68
'category', # Regular field filter
69
('published_date', NullFieldListFilter), # Custom null filter
70
'is_featured', # Boolean field filter
71
]
72
73
# Custom implementation for specific fields
74
class CustomNullFilter(NullFieldListFilter):
75
title = 'has thumbnail'
76
parameter_name = 'has_thumbnail'
77
78
@admin.register(Photo)
79
class PhotoAdmin(admin.ModelAdmin):
80
list_filter = [('thumbnail', CustomNullFilter)]
81
```
82
83
### ForeignKey Autocomplete System
84
85
Complete system for ForeignKey autocomplete functionality including admin mixins, ModelAdmin classes, and inline classes.
86
87
```python { .api }
88
class ForeignKeyAutocompleteAdminMixin:
89
related_search_fields: dict[str, tuple[str, ...]] # Field mapping to search fields
90
related_string_functions: dict[str, Callable] # Custom string representations
91
autocomplete_limit: int | None # Limit autocomplete results
92
93
def __init__(self):
94
"""
95
Admin mixin for models using the autocomplete feature.
96
97
Configuration:
98
- related_search_fields: Maps field names to searchable target fields
99
- related_string_functions: Custom string representation functions
100
- autocomplete_limit: Maximum number of results (default from settings)
101
"""
102
103
def foreignkey_autocomplete(self, request):
104
"""
105
AJAX endpoint for autocomplete search functionality.
106
107
Parameters:
108
- request: HttpRequest with query parameters (q, app_label, model_name, etc.)
109
110
Returns:
111
- HttpResponse: Text response with autocomplete results
112
"""
113
114
def get_related_filter(self, model, request):
115
"""
116
Hook for additional filtering in autocomplete queries.
117
118
Parameters:
119
- model: Model class being searched
120
- request: Current HttpRequest
121
122
Returns:
123
- Q object or None: Additional filter for queryset
124
"""
125
126
def get_help_text(self, field_name, model_name):
127
"""Generate help text for autocomplete fields."""
128
129
class ForeignKeyAutocompleteAdmin(ForeignKeyAutocompleteAdminMixin, admin.ModelAdmin):
130
"""ModelAdmin with ForeignKey autocomplete functionality."""
131
132
class ForeignKeyAutocompleteTabularInline(ForeignKeyAutocompleteAdminMixin, admin.TabularInline):
133
"""TabularInline with ForeignKey autocomplete functionality."""
134
135
class ForeignKeyAutocompleteStackedInline(ForeignKeyAutocompleteAdminMixin, admin.StackedInline):
136
"""StackedInline with ForeignKey autocomplete functionality."""
137
```
138
139
Usage examples:
140
141
```python
142
from django.contrib import admin
143
from django_extensions.admin import ForeignKeyAutocompleteAdmin
144
from myapp.models import Article, Author, Category
145
146
class ArticleAdmin(ForeignKeyAutocompleteAdmin):
147
# Configure autocomplete for specific fields
148
related_search_fields = {
149
'author': ('first_name', 'last_name', 'email'),
150
'category': ('name', 'description'),
151
}
152
153
# Optional: Custom string representations
154
related_string_functions = {
155
'author': lambda obj: f"{obj.first_name} {obj.last_name} ({obj.email})",
156
}
157
158
# Optional: Limit autocomplete results
159
autocomplete_limit = 20
160
161
admin.site.register(Article, ArticleAdmin)
162
163
# Using with inlines
164
class CommentInline(ForeignKeyAutocompleteTabularInline):
165
model = Comment
166
related_search_fields = {
167
'author': ('username', 'email'),
168
}
169
170
class ArticleWithCommentsAdmin(admin.ModelAdmin):
171
inlines = [CommentInline]
172
```
173
174
### Admin Widgets
175
176
Enhanced widgets for Django admin forms that improve user experience and provide additional functionality.
177
178
```python { .api }
179
class ForeignKeySearchInput(widgets.Input):
180
input_type: str = 'text'
181
template_name: str = 'django_extensions/widgets/foreignkey_searchinput.html'
182
183
def __init__(self, rel, admin_site, attrs=None, using=None):
184
"""
185
Widget for displaying ForeignKeys in autocomplete search input.
186
187
Provides enhanced foreign key selection with search functionality
188
instead of dropdown menus for better performance with large datasets.
189
190
Parameters:
191
- rel: ForeignKey relationship
192
- admin_site: AdminSite instance
193
- attrs: HTML attributes for the widget
194
- using: Database alias to use
195
"""
196
197
def format_value(self, value):
198
"""
199
Format the value for display in the input field.
200
201
Parameters:
202
- value: The foreign key value
203
204
Returns:
205
- str: Formatted value for display
206
"""
207
208
def build_attrs(self, base_attrs, extra_attrs=None):
209
"""
210
Build HTML attributes for the widget.
211
212
Returns:
213
- dict: HTML attributes dictionary
214
"""
215
```
216
217
Usage examples:
218
219
```python
220
from django.contrib import admin
221
from django.db import models
222
from django_extensions.admin import ForeignKeySearchInput
223
224
class Article(models.Model):
225
title = models.CharField(max_length=200)
226
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
227
category = models.ForeignKey('Category', on_delete=models.CASCADE)
228
229
class Category(models.Model):
230
name = models.CharField(max_length=100)
231
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE)
232
233
@admin.register(Article)
234
class ArticleAdmin(admin.ModelAdmin):
235
# Use search input for foreign key fields
236
def formfield_for_foreignkey(self, db_field, request, **kwargs):
237
if db_field.name in ['author', 'category']:
238
kwargs['widget'] = ForeignKeySearchInput(
239
db_field.remote_field,
240
admin.site
241
)
242
return super().formfield_for_foreignkey(db_field, request, **kwargs)
243
244
# Alternative: Custom form with search widgets
245
from django import forms
246
247
class ArticleAdminForm(forms.ModelForm):
248
class Meta:
249
model = Article
250
fields = '__all__'
251
widgets = {
252
'author': ForeignKeySearchInput(
253
Article._meta.get_field('author').remote_field,
254
admin.site
255
),
256
'category': ForeignKeySearchInput(
257
Article._meta.get_field('category').remote_field,
258
admin.site
259
),
260
}
261
262
@admin.register(Article)
263
class ArticleAdmin(admin.ModelAdmin):
264
form = ArticleAdminForm
265
```
266
267
## Template Integration
268
269
The admin extensions include custom templates that integrate with Django's admin interface:
270
271
```html
272
<!-- django_extensions/widgets/foreignkey_searchinput.html -->
273
<input type="{{ widget.type }}"
274
name="{{ widget.name }}"
275
value="{{ widget.value|default:'' }}"
276
{% if widget.attrs.id %} id="{{ widget.attrs.id }}"{% endif %}
277
{% for name, value in widget.attrs.items %}
278
{% if value is not False %} {{ name }}{% if value is not True %}="{{ value }}"{% endif %}{% endif %}
279
{% endfor %}>
280
281
<!-- Include JavaScript for search functionality -->
282
<script type="text/javascript">
283
// Enhanced search and autocomplete functionality
284
// Integrates with Django admin's existing JavaScript
285
</script>
286
```
287
288
## Advanced Admin Customization
289
290
```python
291
from django.contrib import admin
292
from django.db import models
293
from django_extensions.admin import NullFieldListFilter, ForeignKeySearchInput
294
295
class BaseAdmin(admin.ModelAdmin):
296
"""Base admin class with common django-extensions functionality."""
297
298
def formfield_for_foreignkey(self, db_field, request, **kwargs):
299
# Use search input for all foreign key fields
300
kwargs['widget'] = ForeignKeySearchInput(
301
db_field.remote_field,
302
admin.site
303
)
304
return super().formfield_for_foreignkey(db_field, request, **kwargs)
305
306
class TimestampedAdmin(BaseAdmin):
307
"""Admin for models with timestamp fields."""
308
309
readonly_fields = ['created', 'modified']
310
list_display = ['__str__', 'created', 'modified']
311
list_filter = [
312
('created', admin.DateFieldListFilter),
313
('modified', admin.DateFieldListFilter),
314
]
315
316
class PublishableAdmin(BaseAdmin):
317
"""Admin for models with publication status."""
318
319
list_display = ['title', 'is_published', 'publish_date']
320
list_filter = [
321
'is_published',
322
('publish_date', NullFieldListFilter),
323
('publish_date', admin.DateFieldListFilter),
324
]
325
326
actions = ['mark_published', 'mark_unpublished']
327
328
def mark_published(self, request, queryset):
329
queryset.update(is_published=True)
330
mark_published.short_description = "Mark selected items as published"
331
332
def mark_unpublished(self, request, queryset):
333
queryset.update(is_published=False)
334
mark_unpublished.short_description = "Mark selected items as unpublished"
335
336
# Usage with django-extensions model base classes
337
from django_extensions.db.models import TimeStampedModel, TitleSlugDescriptionModel
338
339
class BlogPost(TimeStampedModel, TitleSlugDescriptionModel):
340
content = models.TextField()
341
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
342
category = models.ForeignKey('Category', null=True, blank=True, on_delete=models.SET_NULL)
343
is_published = models.BooleanField(default=False)
344
publish_date = models.DateTimeField(null=True, blank=True)
345
346
@admin.register(BlogPost)
347
class BlogPostAdmin(TimestampedAdmin, PublishableAdmin):
348
list_display = ['title', 'author', 'category', 'is_published', 'created']
349
list_filter = [
350
'is_published',
351
('author', NullFieldListFilter),
352
('category', NullFieldListFilter),
353
('publish_date', NullFieldListFilter),
354
('created', admin.DateFieldListFilter),
355
]
356
search_fields = ['title', 'content', 'author__username']
357
prepopulated_fields = {'slug': ('title',)}
358
```
359
360
## Custom Filter Development
361
362
```python
363
from django.contrib import admin
364
from django_extensions.admin import NullFieldListFilter
365
366
class CustomDateRangeFilter(admin.SimpleListFilter):
367
"""Custom filter extending NullFieldListFilter pattern."""
368
369
title = 'date range'
370
parameter_name = 'date_range'
371
372
def lookups(self, request, model_admin):
373
return (
374
('today', 'Today'),
375
('week', 'This Week'),
376
('month', 'This Month'),
377
('year', 'This Year'),
378
('no_date', 'No Date'),
379
)
380
381
def queryset(self, request, queryset):
382
from django.utils import timezone
383
from datetime import timedelta
384
385
if self.value() == 'today':
386
return queryset.filter(created__date=timezone.now().date())
387
elif self.value() == 'week':
388
week_ago = timezone.now() - timedelta(days=7)
389
return queryset.filter(created__gte=week_ago)
390
elif self.value() == 'month':
391
month_ago = timezone.now() - timedelta(days=30)
392
return queryset.filter(created__gte=month_ago)
393
elif self.value() == 'year':
394
year_ago = timezone.now() - timedelta(days=365)
395
return queryset.filter(created__gte=year_ago)
396
elif self.value() == 'no_date':
397
return queryset.filter(created__isnull=True)
398
return queryset
399
400
# Combined filtering approach
401
class AdvancedAdmin(admin.ModelAdmin):
402
list_filter = [
403
('created', CustomDateRangeFilter),
404
('author', NullFieldListFilter),
405
'status',
406
]
407
```
408
409
## Widget Customization
410
411
```python
412
from django_extensions.admin import ForeignKeySearchInput
413
from django.contrib import admin
414
415
class CustomSearchInput(ForeignKeySearchInput):
416
"""Customized foreign key search input with additional features."""
417
418
template_name = 'myapp/widgets/custom_search_input.html'
419
420
def build_attrs(self, base_attrs, extra_attrs=None):
421
attrs = super().build_attrs(base_attrs, extra_attrs)
422
attrs.update({
423
'class': 'custom-search-input',
424
'data-search-url': '/admin/api/search/',
425
'placeholder': 'Type to search...'
426
})
427
return attrs
428
429
class EnhancedAdmin(admin.ModelAdmin):
430
def formfield_for_foreignkey(self, db_field, request, **kwargs):
431
if db_field.name in ['author', 'category']:
432
kwargs['widget'] = CustomSearchInput(
433
db_field.remote_field,
434
admin.site,
435
attrs={'class': 'enhanced-search'}
436
)
437
return super().formfield_for_foreignkey(db_field, request, **kwargs)
438
```
439
440
## Best Practices
441
442
```python
443
# 1. Use null filters for optional relationships
444
class ArticleAdmin(admin.ModelAdmin):
445
list_filter = [
446
('featured_image', NullFieldListFilter), # Optional image
447
('publish_date', NullFieldListFilter), # Optional publish date
448
('category', NullFieldListFilter), # Optional category
449
]
450
451
# 2. Combine with search for large datasets
452
class UserAdmin(admin.ModelAdmin):
453
search_fields = ['username', 'email', 'first_name', 'last_name']
454
list_filter = [
455
('last_login', NullFieldListFilter),
456
('date_joined', admin.DateFieldListFilter),
457
]
458
459
# 3. Use search inputs for performance with large FK sets
460
class OrderAdmin(admin.ModelAdmin):
461
def formfield_for_foreignkey(self, db_field, request, **kwargs):
462
# Use search input for customer selection (many customers)
463
if db_field.name == 'customer':
464
kwargs['widget'] = ForeignKeySearchInput(
465
db_field.remote_field,
466
admin.site
467
)
468
return super().formfield_for_foreignkey(db_field, request, **kwargs)
469
470
# 4. Custom admin with multiple extensions
471
class ComprehensiveAdmin(admin.ModelAdmin):
472
list_per_page = 50
473
list_max_show_all = 200
474
475
def get_list_filter(self, request):
476
# Dynamic filter assignment
477
filters = list(self.list_filter)
478
479
# Add null filters for nullable foreign keys
480
for field in self.model._meta.get_fields():
481
if (field.is_relation and
482
getattr(field, 'null', False) and
483
hasattr(field, 'remote_field')):
484
filters.append((field.name, NullFieldListFilter))
485
486
return filters
487
```