Django reverse lookup chaining queryset

2019-05-30 00:52发布

问题:

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!!

回答1:

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.