python-3.xdjangodjango-admindjango-filterdjango-select-related

__str__ method in model generates large amount of duplicated queries in django admin panel


I have a model Offer which has a ForeginKey to another model Category. And through this Category model I get a Brand associated with this Category and pass it to the Offer on admin page. But it generates a large amount of queries when I use this Brand field in dropdown filter on Offer admin page.

models.py

class Brand(BaseFields):
    name = models.CharField()
    ...
    def __str__(self):
        return self.name()

class Category(BaseFields):
    name = models.CharField()
    brand = models.ForeignKey(
        Brand,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    parents = models.ManyToManyField(
        'self',
        blank=True,
        verbose_name='Parent_category',
        related_name='children',
        symmetrical=False
    )
    
    def __str__(self):
        return str(self.brand) + '----' + self.name


class Offer(BaseFields):
    name = models.CharField()
    category = models.ForeignKey(
        Category,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='offer',
        verbose_name='Related_category'
    )

    def __str__(self):
        return self.name

admin.py

class OfferAdmin(admin.ModelAdmin):
    list_select_related = True
    list_display = (
        'name',
        'brand_name',
        'category',
        'place',
        'status'
    )
    list_editable = ('place', 'status')
    list_filter = (
        ('category__brand', RelatedOnlyDropdownFilter),
        ('category', RelatedOnlyDropdownFilter),
        'status'
    )
    fields = [
        'name',
        'description',
        'tech_info',
        'ctru',
        'category',
        'place',
        'status'
    ]
    autocomplete_fields = ['category']
    actions_on_bottom = True
    list_per_page = 25
    search_fields = ['name']

    def get_queryset(self, request):
        return super().get_queryset(request).select_related('category', 'category__brand')

    @admin.display(description='Brand', ordering='name')
    def brand_name(self, obj):
        return obj.category.brand.name

These two blocks are the main problem, as I understand

def __str__(self):
    return str(self.brand) + '----' + self.name
list_filter = (
        ('category__brand', RelatedOnlyDropdownFilter),
        ('category', RelatedOnlyDropdownFilter),
        'status'
    )

I need to reduce the amount of queries generated by calculating Brand for all offers. I do understand that I have to use select_related somewhere but I just don't get the exact method to use it.


Solution

  • There seems to be no neat framework-supported way of doing this. But there is a way, of sorts.

    You'll need to subclass RelatedOnlyDropdownFilter, overriding def field_choices. Something like this:

    class CategoryRelatedOnlyDropdownFilter(RelatedOnlyDropdownFilter):
        def field_choices(self, field, request, model_admin):
            return [("", "---------")] + [
                (category.pk, str(category))
                for category
                in Category.objects.select_related("brand")
            ]