2

I have several models that I need to do reverse lookups in, then somehow chain the querysets together.

I got 3 models, (1) designers create (2) projects, and projects contain (3) uploaded images.

Models

class Designer(models.Model):
    ... 

class Project(models.Model):
    designer = models.ForeignKey('Designer')

class UploadedImage(models.Model):
    project = models.ForeignKey('Project')

So a designer opens up his page and wants to see all his projects and some images associated with his projects, I could do something like,

d = Designer.objects.get(id=2)
projects = d.project_set.all()

But then with images, I gotta do this thing many many times, each time hitting the database.

images = []
for p in projects:
    images.append(p.uploadedimage_set.all())

Now we have another problem, which is how do I connect images and projects?? I could do something stupid by building up a dictionary like this,

images = []
for p in projects:
    images.append( { p.id : p.uploadedimage_set.all() } )

Then when I iterate through projects, I could just use the id to figure out which images are associated with which project.

Isn't there something a lot more elegant that'll allow me to,

  1. Hit the database just once
  2. Allow me to do something that'll look like d.projects[0].images[0]??? Instead of building up some sort of a stupid custom dictionary??? Does Django have some sort of queryset building tool or whatever?

Thanks!!

reedvoid
  • 1,203
  • 3
  • 18
  • 34
  • 1
    I don't have access to a console to check, but iirc `UploadedImage(project__designer=designer)` should work. Similarly `UploadedImage(project__designer__in=listOfDesigners)` would let you query multiple at once. – sapi Aug 11 '14 at 05:18
  • @sapi I tried it out, came up with this which seems to do what I want: `images = UploadedImage.objects.filter(project__designer=d).prefetch_related()`, I added that last prefetch part because supposedly it caches the query. I would still need to get the projects together though since I can't exactly collapse `images` to get the projects (I could but it seems stupid to do it backwards this way). So net net I hit the DB twice to get what I want, which is a lot better than what I had going before. Thanks dude. – reedvoid Aug 11 '14 at 07:24
  • @sapi actually, just thought of something, is there some kind of a query I can write on `images` after it's cached locally to get all the unique projects out of it? This way it's just 1 DB hit. – reedvoid Aug 11 '14 at 07:26
  • 1
    You could use `distinct` (see http://stackoverflow.com/questions/3852104/select-distinct-individual-columns-in-django), but at this point I strongly suspect you are prematurely optimising. You should aim to write clear code first, and only optimse the number of database calls in the very unlikely event that it proves to be a bottleneck – sapi Aug 11 '14 at 07:50

1 Answers1

3

If you're just after the images associated with a particular designer, you can grab them with a single query:

designer = Designer.objects.get(id=id)
images = UploadedImage.objects.filter(product__designer=designer)

If you would like to associate them with the project, there's a couple of ways to do that.

If there aren't many different projects, and you're concerned about querying the database too much, you can use the images list from before, and perform the filtering in python:

images = list(images) # Crystallise the query
projectImages = [[img for img in images if img.project == p]
                 for p in designer.project_set.all()]

However, a better way to do it would probably be to let the database handle the filtering; for sets of a non-trivial size it will likely be faster.

That way, you can just query for the images directly, ie:

designer = Designer.objects.get(id=id)
projects = designer.project_set.all() # Project.objects.filter(designer=designer)
projectImages = [UploadedImage.objects.filter(project=p) for p in projects]

The big advantage of the second approach is that the elements in projectImages are still query sets, so can still be further filtered or annotated.

sapi
  • 9,944
  • 8
  • 41
  • 71
  • Given credit for the right answer but mostly because of sapi's answer in the comments on the question. For me the comments solved my problems. Thanks! – reedvoid Aug 11 '14 at 09:01