or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

admin-extensions.mdindex.mdmanagement-commands.mdmodel-base-classes.mdmodel-fields.mdtemplate-tags.mdvalidators.md

admin-extensions.mddocs/

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

```