Skip to content

Commit 0f8f02b

Browse files
authored
Merge pull request #1232 from syucream/feature/store-prefetched-entries
Store prefetched entries in context to avoid N+1 on JobListAPI
2 parents afa7e62 + a29f1f3 commit 0f8f02b

File tree

2 files changed

+31
-5
lines changed

2 files changed

+31
-5
lines changed

job/api_v2/serializers.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class JobSerializers(serializers.ModelSerializer):
2828
target = serializers.SerializerMethodField(method_name="get_target")
2929
passed_time = serializers.SerializerMethodField(method_name="get_passed_time")
3030

31+
PREFETCHED_ENTRIES_KEY = "__prefetched_entries"
32+
3133
class Meta:
3234
model = Job
3335
fields = ["id", "text", "status", "operation", "created_at", "target", "passed_time"]
@@ -36,12 +38,19 @@ class Meta:
3638
def get_target(self, obj: Job) -> JobTarget | None:
3739
if obj.target is not None:
3840
if obj.target.objtype == ACLObjType.Entry:
39-
sub = Entry.objects.filter(id=obj.target.id).select_related("schema").first()
41+
sub: dict = self.context.get(self.PREFETCHED_ENTRIES_KEY, {}).get(obj.target.id)
42+
if not sub:
43+
sub = (
44+
Entry.objects.filter(id=obj.target.id)
45+
.select_related("schema")
46+
.values("id", "name", "schema__id", "schema__name")
47+
.first()
48+
)
4049
return {
41-
"id": sub.id,
42-
"name": sub.name,
43-
"schema_id": sub.schema.id,
44-
"schema_name": sub.schema.name,
50+
"id": sub["id"],
51+
"name": sub["name"],
52+
"schema_id": sub["schema__id"],
53+
"schema_name": sub["schema__name"],
4554
}
4655
else:
4756
return {

job/api_v2/views.py

+17
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
from rest_framework.request import Request
1010
from rest_framework.response import Response
1111

12+
from airone.lib.acl import ACLObjType
1213
from airone.lib.drf import FileIsNotExistsError, InvalidValueError, JobIsNotDoneError
1314
from airone.lib.http import get_download_response
15+
from entry.models import Entry
1416
from job.api_v2.serializers import JobSerializers
1517
from job.models import Job, JobOperation, JobStatus
1618

@@ -110,6 +112,21 @@ def get_queryset(self) -> QuerySet:
110112

111113
return Job.objects.filter(query).select_related("target").order_by("-created_at")
112114

115+
def get_serializer_context(self):
116+
context = super().get_serializer_context()
117+
118+
# prefetch target entries, then pass it via context manually to avoid N+1 in serializer
119+
qs = self.paginate_queryset(self.get_queryset().values("target__id", "target__objtype"))
120+
target_ids = [int(r["target__id"]) for r in qs if r["target__objtype"] == ACLObjType.Entry]
121+
entries = (
122+
Entry.objects.filter(id__in=target_ids)
123+
.select_related("schema")
124+
.values("id", "name", "schema__id", "schema__name")
125+
)
126+
context[JobSerializers.PREFETCHED_ENTRIES_KEY] = {e["id"]: e for e in entries}
127+
128+
return context
129+
113130

114131
class JobRerunAPI(generics.UpdateAPIView):
115132
serializer_class = serializers.Serializer

0 commit comments

Comments
 (0)