3

I have a couple of models: Review, which has fields for album reviews, Record, which is an album's name etc, and Band, which is a band's name. Record has a foreign key against Band and Review has a foreign key against Record.

In the admin forms for Review I want to show a dropdown select box for all Records. At the moment, the unicode method for Record is just:

def __unicode__(self):
    return self.record_name

This isn't very helpful so I changed it to this:

def __unicode__(self):
    return self.band.band_name + ' - ' + self.record_name  

This now adds a query for each Record (3000 or so), obviously not good.

Reading this answer, I tried adding this to my model admin for Record:

def queryset(self, request):
    return super(RecordAdmin, self).queryset(request).select_related('band')

This didn't make any difference, though.

Is it possible to use foreign key fields in the __unicode__ representation of a model without incurring n-squared queries?

UPDATE: here's the models (with unrelated fields removed):

class Review(models.Model):

    def __unicode__(self):
        # this is used in other places where we show review titles
        return self.record.band.band_name + ' - ' + self.record.record_name

    record = models.ForeignKey('Record')
    review_text = models.TextField()

class Record(models.Model):

    def __unicode__(self):
        # this generates a billion queries
        #return self.record_name
        return self.band.band_name + ' - ' + self.record_name  

    def band_and_title(self):
        return self.band.band_name + ' - ' + self.record_name

    band = models.ForeignKey('Band')
    label = models.ForeignKey('Label')
    record_name = models.CharField(max_length=175)

class Band(models.Model):

    def __unicode__(self):
        return self.band_name

    band_name = models.CharField(max_length=100)
Community
  • 1
  • 1
Matt Andrews
  • 2,868
  • 3
  • 31
  • 53
  • Do you have more information: Django version etc. I'm using almost exactly this code in several places and projects with great success. – Rob Osborne Jan 26 '13 at 16:49
  • Sure: version 1.4.1 locally (although server is using 1.2.3). Am I putting it on the right model? Technically I'm editing a `Review` not a `Record`, but I tried it there too and didn't see any difference. – Matt Andrews Jan 26 '13 at 17:23

2 Answers2

2

You need to inject the select_related at the time that the admin site creates the change form. You can do this by overriding ModelAdmin.formfield_for_foreignkey for your ModelAdmin. Something like this:

class ReviewAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "record":
            kwargs["queryset"] = Record.objects.all().select_related()

        return super(ReviewAdmin, self).formfield_for_foreignkey(
            db_field, request, **kwargs)
Shimon Rura
  • 439
  • 4
  • 13
1

Given your comment you may actually need to select related both the record and its band. Like this:

class ReviewAdmin(admin.ModelAdmin):
    model = Review
    def queryset(self, request):
        return super(ReviewAdmin, self).queryset(request).select_related('record', 'record__band')

class RecordAdmin(admin.ModelAdmin):
    model = Record
    def queryset(self, request):
        return super(RecordAdmin, self).queryset(request).select_related('band')

Using the Django Toolbar is a good way to discover what queries are being run.

Rob Osborne
  • 4,897
  • 4
  • 32
  • 43
  • Thanks -- this didn't make any difference, sadly :( -- was a bit unsure about the `model = mymodels.Review` line, is this a "magic" variable (since it's not referenced anywhere else in this code?). I just used `model = Review` (I already import `webzine.models.Review` above this). – Matt Andrews Jan 26 '13 at 22:38
  • It's a style thing, I always import just the models. Shouldn't matter. Can you add the stripped down objects to your question. Like I said, this works and we're just missing something. – Rob Osborne Jan 26 '13 at 23:10