简体   繁体   中英

Can't override queryset() in ModelAdmin

I have the following models which describe a painting with an optional number of images:

class Painting(Base, Seo, Timestamp):
    artist = models.ForeignKey(Artist, related_name='paintings')
    medium = models.ForeignKey(Medium)

    def thumbnail(self):
        thumbnail = self.images.filter(position=0)
        if thumbnail.exists():
            return thumbnail[0].thumbnail_html()
        else:
            return ''
    thumbnail.allow_tags = True

class PaintingImage(models.Model):
    painting = models.ForeignKey(Painting, related_name='images')
    alt = models.CharField(max_length=100)
    position = models.PositiveSmallIntegerField("Position", default=0)

    # use pi.image = 'path/to/file' for direct access to the underlying image filename
    image = models.ImageField(upload_to='paintings')
    thumbnail = models.ImageField(upload_to='paintings')

This all works fine but the admin list view is slow and doing lots of queries because it is calling thumbnail() on Painting, which is doing a separate query to PaintingImage for each painting.

I tried to override queryset() in ModelAdmin to attach the image to each instance manually, in an efficient manner. 'thumbnail' in list_display now points to thumbnail(self, obj) in PaintingAdmin which reads the stored attribute, but it isn't working - obj.thumb can't be read in thumbnail(self, obj) below:

class PaintingAdmin(admin.ModelAdmin):
    fields = [('title', 'slug', 'display'),
              'artist', ('categories', 'medium'), 'price', 'description', 'note',
              ('sold', 'reserved', ),
              ('height', 'width', 'frame_height', 'frame_width')
    ]
    prepopulated_fields = {"slug": ("title",)}
    inlines = [ImageInline, InvoiceInline]
    list_display = ('title', 'artist', 'slug', 'medium', 'price',
                    'sold', 'reserved', 'display', 'created', 'thumbnail', 'invoice_link')
    list_filter = ['display', 'sold', 'reserved', 'medium', PriceFilter]
    search_fields = ['title', 'description', 'note', 'artist__last_name', 'artist__first_name']

    date_hierarchy = 'created'

    def thumbnail(self, obj):
        return obj.thumb     # error because it doesn't exist

    def queryset(self, request):
        """make it more efficient by not getting painting image every time
        """
        paintings = super(PaintingAdmin, self).queryset(request)

        # get all images which are thumbnails
        images = PaintingImage.objects.filter(
            painting__in=paintings,
            position=0
        )

        painting_id_to_thumbnail = {}
        for image in images:
            painting_id_to_thumbnail[image.painting_id] = image.thumbnail_html()

        # attach thumbnail to each instance
        for painting in paintings:
            painting.thumb = painting_id_to_thumbnail.get(painting.id, '')

        return paintings

I'm using django 1.5.2 and python 2.7.

You can use prefetch_related on reverse relationships, so the above code in queryset() can be replaced with:

paintings = super(PaintingAdmin, self).queryset(request)
return paintings.prefetch_related('images')

The problem was with this method on Painting:

def thumbnail(self):
        thumbnail = self.images.filter(position=0)
        if thumbnail.exists():
            return thumbnail[0].thumbnail_html()
        else:
            return ''

Even when using prefetch_related, it will do extra queries. So the solution was to replace it with:

def thumbnail(self):
        if self.images.count():
            return self.images.all()[0].thumbnail_html()
        else:
            return ''

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM