Unfortunately the ORM doesn’t allow you to define relationship between models that don’t involve foreign keys. If it was the case you could possibly do something like
class Location(models.Model):
area = models.PolygonField()
class Plant(models.Model):
position = models.PointField()
locations = Relation(
Location, Q(position__within=F("locations__area"))
)
Knowing that it’s important to understand that prefetch related is really just a glorified __in
lookup that performs assignments. In other words
Author.objects.prefetch_related("books")
Is equivalent to
authors = Author.objects.all()
authors_books = defaultdict(list)
for book in Book.objects.filter(author__in=authors):
authors_books[book.author_id].append(book)
for author in authors:
author._prefetch_related_cache["books"] = authors_books[author.id]
This means that in order to emulate what prefetch_related('locations')
does you need to craft a query that can return all the associated Location
annotated with plant_id
within their defined areas from a set of Plant
.
Something like the following should work
from django.contrib.postgres.expressions import ArraySubquery
from django.db.models import OuterRef, Exists
plant_ids = [plant.id for plant in plants]
locations = Location.objects.filter(
Exists(
Plant.objects.filter(
id__in=plant_ids,
position__within=OuterRef("area"),
)
)
).annotate(
plant_ids=ArraySubquery(
Plant.objects.filter(
position__within=OuterRef("area")
).values("id")
)
)
plant_locations = defaultdict(list)
for location in locations:
for plant_id in location.plant_ids:
plant_locations[plant_id].append(location)
Which should perform a single SQL query of the form
SELECT
location.*,
ARRAY(
SELECT id
FROM plant
WHERE ST_Within(plant.position, location.area)
) plant_ids
FROM location
WHERE EXISTS (
SELECT 1
FROM plant
WHERE ST_Within(plant.position, location.area)
LIMIT 1
)
Which should be roughly equivalent to the more concise but hard to express through the ORM and likely more performant
SELECT
location.*,
array_agg(plant.id) plant_ids
FROM location
INNER JOIN plant ON (ST_Within(plant.position, location.area))
GROUP BY location.id