|
| 1 | +--- |
| 2 | +title: Sections (Expandable rows) |
| 3 | +order: 8 |
| 4 | +description: |
| 5 | +--- |
| 6 | + |
| 7 | +# Sections - expandable changelist rows |
| 8 | + |
| 9 | +Unfold implements special functionality for handling expandable rows in changelists called sections. Once the `list_sections` attribute is configured, rows in the changelist will display an arrow button at the beginning of the row which can be used to show additional content. |
| 10 | + |
| 11 | +The `list_sections` attribute consists of Python classes inheriting from `TableSection` or `TemplateSection` defined in `unfold.sections`. These classes are responsible for rendering the content in the expandable area. |
| 12 | + |
| 13 | +```python |
| 14 | +from unfold.admin import ModelAdmin |
| 15 | +from unfold.sections import TableSection, TemplateSection |
| 16 | + |
| 17 | +from .models import SomeModel |
| 18 | + |
| 19 | +# Table for related records |
| 20 | +class CustomTableSection(TableSection): |
| 21 | + verbose_name = _("Table title") # Displays custom table title |
| 22 | + height = 300 # Force the table height. Ideal for large amount of records |
| 23 | + related_name = "related_name_set" # Related model field name |
| 24 | + fields = ["pk", "title", "custom_field"] # Fields from related model |
| 25 | + |
| 26 | + # Custom field |
| 27 | + def custom_field(self, instance): |
| 28 | + return instance.pk |
| 29 | + |
| 30 | +# Simple template with custom content |
| 31 | +class CardSection(TemplateSection): |
| 32 | + template_name = "your_app/some_template.html" |
| 33 | + |
| 34 | + |
| 35 | +@admin.register(SomeModel) |
| 36 | +class SomeAdmin(ModelAdmin): |
| 37 | + list_sections = [ |
| 38 | + CardSection, |
| 39 | + CustomTableSection, |
| 40 | + ] |
| 41 | +``` |
| 42 | + |
| 43 | +## Query optimisation |
| 44 | + |
| 45 | +When it comes to classes inheriting from `TableSection`, you may find a problem with an extraordinary amount of queries executed on changelist pages. This problem has two parts: |
| 46 | + |
| 47 | +1. `TableSection` works with related fields so another query is required to obtain data from the related table |
| 48 | +2. The default page size for changelist is configured to 100 - which is a pretty large number of records per page |
| 49 | + |
| 50 | +The easiest solution for this issue is to configure pagination to a smaller amount of records by setting `list_per_page = 20`. While this solution might work for you, it is not optimal. |
| 51 | + |
| 52 | +The optimal solution is using [`prefetch_related`](https://docs.djangoproject.com/en/5.1/ref/models/querysets/#prefetch-related): |
| 53 | + |
| 54 | +1. Install [django-debug-toolbar](https://github.com/django-commons/django-debug-toolbar) and check all SQL queries that are duplicating for each record in the changelist |
| 55 | +2. Override `get_queryset` and use `prefetch_related` on all related rows until you don't have any duplicated SQL queries in django-debug-toolbar output |
| 56 | + |
| 57 | +```python |
| 58 | +from unfold.admin import ModelAdmin |
| 59 | + |
| 60 | +from .models import SomeModel |
| 61 | + |
| 62 | + |
| 63 | +@admin.register(SomeModel) |
| 64 | +class SomeAdmin(ModelAdmin): |
| 65 | + list_per_page = 20 # Quick solution |
| 66 | + list_sections = [CustomTableSection] |
| 67 | + |
| 68 | + # Custom queryset prefetching related records |
| 69 | + def get_queryset(self, request): |
| 70 | + return ( |
| 71 | + super() |
| 72 | + .get_queryset(request) |
| 73 | + .prefetch_related( |
| 74 | + "related_field_set", |
| 75 | + "related_field__another_related_field", |
| 76 | + "related_field__another_related_field__even_more_related_field", |
| 77 | + ) |
| 78 | + ) |
| 79 | +``` |
| 80 | + |
| 81 | +## Multiple related tables |
| 82 | + |
| 83 | +Unfold supports multiple related tables in expandable rows. Specify a section class for each related field and put them into `list_sections`. For each class, you can add a `verbose_name` to display a custom title right above the table to distinguish between different related fields. |
| 84 | + |
| 85 | +```python |
| 86 | +from unfold.admin import ModelAdmin |
| 87 | + |
| 88 | +from .models import SomeModel |
| 89 | + |
| 90 | + |
| 91 | +@admin.register(SomeModel) |
| 92 | +class SomeAdmin(ModelAdmin): |
| 93 | + list_sections = [ |
| 94 | + CustomTableSection, OtherCustomTable |
| 95 | + ] |
| 96 | +``` |
0 commit comments