1"""
2The main QuerySet implementation. This provides the public API for the ORM.
3"""
4
5from __future__ import annotations
6
7import copy
8import operator
9import warnings
10from collections.abc import Callable, Iterator, Sequence
11from functools import cached_property
12from itertools import islice
13from typing import TYPE_CHECKING, Any, Never, Self, overload
14
15import plain.runtime
16import psycopg
17from plain.exceptions import ValidationError
18from plain.postgres import transaction
19from plain.postgres.constants import LOOKUP_SEP, OnConflict
20from plain.postgres.db import (
21 PLAIN_VERSION_PICKLE_KEY,
22 get_connection,
23)
24from plain.postgres.exceptions import (
25 FieldDoesNotExist,
26 FieldError,
27 ObjectDoesNotExist,
28)
29from plain.postgres.expressions import Case, F, ResolvableExpression, Value, When
30from plain.postgres.fields import (
31 Field,
32 PrimaryKeyField,
33)
34from plain.postgres.functions import Cast
35from plain.postgres.query_utils import Q
36from plain.postgres.sql import (
37 AND,
38 CURSOR,
39 OR,
40 DeleteQuery,
41 InsertQuery,
42 Query,
43 RawQuery,
44 UpdateQuery,
45)
46from plain.postgres.utils import resolve_callables
47from plain.utils.functional import partition
48
49# Re-exports for public API
50__all__ = ["F", "Prefetch", "Q", "QuerySet", "RawQuerySet"]
51
52if TYPE_CHECKING:
53 from plain.postgres import Model
54
55# The maximum number of results to fetch in a get() query.
56MAX_GET_RESULTS = 21
57
58# The maximum number of items to display in a QuerySet.__repr__
59REPR_OUTPUT_SIZE = 20
60
61
62class BaseIterable:
63 def __init__(
64 self,
65 queryset: QuerySet[Any],
66 chunked_fetch: bool = False,
67 ):
68 self.queryset = queryset
69 self.chunked_fetch = chunked_fetch
70
71 def __iter__(self) -> Iterator[Any]:
72 raise NotImplementedError(
73 "subclasses of BaseIterable must provide an __iter__() method"
74 )
75
76
77class ModelIterable(BaseIterable):
78 """Iterable that yields a model instance for each row."""
79
80 def __iter__(self) -> Iterator[Model]:
81 queryset = self.queryset
82 compiler = queryset.sql_query.get_compiler()
83 # Execute the query. This will also fill compiler.select, klass_info,
84 # and annotations.
85 results = compiler.execute_sql(chunked_fetch=self.chunked_fetch)
86 select, klass_info, annotation_col_map = (
87 compiler.select,
88 compiler.klass_info,
89 compiler.annotation_col_map,
90 )
91 # These are set by execute_sql() above
92 assert select is not None
93 assert klass_info is not None
94 model_cls = klass_info["model"]
95 select_fields = klass_info["select_fields"]
96 model_fields_start, model_fields_end = select_fields[0], select_fields[-1] + 1
97 init_list = [
98 f[0].target.name for f in select[model_fields_start:model_fields_end]
99 ]
100 related_populators = get_related_populators(klass_info, select)
101 known_related_objects = [
102 (
103 field,
104 related_objs,
105 # The raw key value, not the related object the descriptor
106 # would return -- the dict below is keyed by raw key.
107 field.value_from_object,
108 )
109 for field, related_objs in queryset._known_related_objects.items()
110 ]
111 for row in compiler.results_iter(results):
112 obj = model_cls.from_db(init_list, row[model_fields_start:model_fields_end])
113 for rel_populator in related_populators:
114 rel_populator.populate(row, obj)
115 if annotation_col_map:
116 for attr_name, col_pos in annotation_col_map.items():
117 setattr(obj, attr_name, row[col_pos])
118
119 # Add the known related objects to the model.
120 for field, rel_objs, rel_getter in known_related_objects:
121 # Avoid overwriting objects loaded by, e.g., select_related().
122 if field.is_cached(obj):
123 continue
124 rel_obj_id = rel_getter(obj)
125 try:
126 rel_obj = rel_objs[rel_obj_id]
127 except KeyError:
128 pass # May happen in qs1 | qs2 scenarios.
129 else:
130 setattr(obj, field.name, rel_obj)
131
132 yield obj
133
134
135class RawModelIterable(BaseIterable):
136 """
137 Iterable that yields a model instance for each row from a raw queryset.
138 """
139
140 queryset: RawQuerySet
141
142 def __iter__(self) -> Iterator[Model]:
143 from plain.postgres.sql.compiler import apply_converters, get_converters
144
145 query = self.queryset.sql_query
146 connection = get_connection()
147 query_iterator: Iterator[Any] = iter(query)
148
149 try:
150 (
151 model_init_names,
152 model_init_pos,
153 annotation_fields,
154 ) = self.queryset.resolve_model_init_order()
155 model_cls = self.queryset.model
156 assert model_cls is not None
157 if "id" not in model_init_names:
158 raise FieldDoesNotExist("Raw query must include the primary key")
159 fields = [self.queryset.model_fields.get(c) for c in self.queryset.columns]
160 converters = get_converters(
161 [
162 f.get_col(f.model.model_options.db_table) if f else None
163 for f in fields
164 ],
165 connection,
166 )
167 if converters:
168 query_iterator = apply_converters(
169 query_iterator, converters, connection
170 )
171 for values in query_iterator:
172 # Associate fields to values
173 model_init_values = [values[pos] for pos in model_init_pos]
174 instance = model_cls.from_db(model_init_names, model_init_values)
175 if annotation_fields:
176 for column, pos in annotation_fields:
177 setattr(instance, column, values[pos])
178 yield instance
179 finally:
180 # Done iterating the Query. If it has its own cursor, close it.
181 if hasattr(query, "cursor") and query.cursor:
182 query.cursor.close()
183
184
185class ValuesIterable(BaseIterable):
186 """
187 Iterable returned by QuerySet.values() that yields a dict for each row.
188 """
189
190 def __iter__(self) -> Iterator[dict[str, Any]]:
191 queryset = self.queryset
192 query = queryset.sql_query
193 compiler = query.get_compiler()
194
195 names = [
196 *query.values_select,
197 *query.annotation_select,
198 ]
199 indexes = range(len(names))
200 for row in compiler.results_iter(chunked_fetch=self.chunked_fetch):
201 yield {names[i]: row[i] for i in indexes}
202
203
204class ValuesListIterable(BaseIterable):
205 """
206 Iterable returned by QuerySet.values_list(flat=False) that yields a tuple
207 for each row.
208 """
209
210 def __iter__(self) -> Iterator[tuple[Any, ...]]:
211 queryset = self.queryset
212 query = queryset.sql_query
213 compiler = query.get_compiler()
214
215 if queryset._fields:
216 names = [
217 *query.values_select,
218 *query.annotation_select,
219 ]
220 fields = [
221 *queryset._fields,
222 *(f for f in query.annotation_select if f not in queryset._fields),
223 ]
224 if fields != names:
225 # Reorder according to fields.
226 index_map = {name: idx for idx, name in enumerate(names)}
227 rowfactory = operator.itemgetter(*[index_map[f] for f in fields])
228 return map(
229 rowfactory,
230 compiler.results_iter(chunked_fetch=self.chunked_fetch),
231 )
232 return iter(
233 compiler.results_iter(
234 tuple_expected=True,
235 chunked_fetch=self.chunked_fetch,
236 )
237 )
238
239
240class FlatValuesListIterable(BaseIterable):
241 """
242 Iterable returned by QuerySet.values_list(flat=True) that yields single
243 values.
244 """
245
246 def __iter__(self) -> Iterator[Any]:
247 queryset = self.queryset
248 compiler = queryset.sql_query.get_compiler()
249 for row in compiler.results_iter(chunked_fetch=self.chunked_fetch):
250 yield row[0]
251
252
253class QuerySet[T: "Model"]:
254 """
255 Represent a lazy database lookup for a set of objects.
256
257 Usage:
258 MyModel.query.filter(name="test").all()
259
260 Custom QuerySets:
261 from typing import Self
262
263 class TaskQuerySet(QuerySet["Task"]):
264 def active(self) -> Self:
265 return self.filter(is_active=True)
266
267 class Task(Model):
268 is_active = BooleanField(default=True)
269 query = TaskQuerySet()
270
271 Task.query.active().filter(name="test") # Full type inference
272
273 Custom methods should return `Self` to preserve type through method chaining.
274 """
275
276 # Instance attributes (set in from_model())
277 model: type[T]
278 _query: Query
279 _result_cache: list[T] | None
280 _sticky_filter: bool
281 _prefetch_related_lookups: tuple[Any, ...]
282 _prefetch_done: bool
283 _known_related_objects: dict[Any, dict[Any, Any]]
284 _iterable_class: type[BaseIterable]
285 _fields: tuple[str, ...] | None
286 _defer_next_filter: bool
287 _deferred_filter: tuple[bool, tuple[Any, ...], dict[str, Any]] | None
288
289 def __init__(self):
290 """Minimal init for descriptor mode. Use from_model() to create instances."""
291
292 @classmethod
293 def from_model(cls, model: type[T], query: Query | None = None) -> Self:
294 """Create a QuerySet instance bound to a model."""
295 instance = cls()
296 instance.model = model
297 instance._query = query or Query(model)
298 instance._result_cache = None
299 instance._sticky_filter = False
300 instance._prefetch_related_lookups = ()
301 instance._prefetch_done = False
302 instance._known_related_objects = {}
303 instance._iterable_class = ModelIterable
304 instance._fields = None
305 instance._defer_next_filter = False
306 instance._deferred_filter = None
307 return instance
308
309 @overload
310 def __get__(self, instance: None, owner: type[T]) -> Self: ...
311
312 @overload
313 def __get__(self, instance: Model, owner: type[T]) -> Never: ...
314
315 def __get__(self, instance: Any, owner: type[T]) -> Self:
316 """Descriptor protocol - return a new QuerySet bound to the model."""
317 if instance is not None:
318 raise AttributeError(
319 f"QuerySet is only accessible from the model class, not instances. "
320 f"Use {owner.__name__}.query instead."
321 )
322 return self.from_model(owner)
323
324 @property
325 def sql_query(self) -> Query:
326 if self._deferred_filter:
327 negate, args, kwargs = self._deferred_filter
328 self._filter_or_exclude_inplace(negate, args, kwargs)
329 self._deferred_filter = None
330 return self._query
331
332 @sql_query.setter
333 def sql_query(self, value: Query) -> None:
334 if value.values_select:
335 self._iterable_class = ValuesIterable
336 self._query = value
337
338 ########################
339 # PYTHON MAGIC METHODS #
340 ########################
341
342 def __deepcopy__(self, memo: dict[int, Any]) -> QuerySet[T]:
343 """Don't populate the QuerySet's cache."""
344 obj = self.__class__.from_model(self.model)
345 for k, v in self.__dict__.items():
346 if k == "_result_cache":
347 obj.__dict__[k] = None
348 else:
349 obj.__dict__[k] = copy.deepcopy(v, memo)
350 return obj
351
352 def __getstate__(self) -> dict[str, Any]:
353 # Force the cache to be fully populated.
354 self._fetch_all()
355 return {**self.__dict__, PLAIN_VERSION_PICKLE_KEY: plain.runtime.__version__}
356
357 def __setstate__(self, state: dict[str, Any]) -> None:
358 pickled_version = state.get(PLAIN_VERSION_PICKLE_KEY)
359 if pickled_version:
360 if pickled_version != plain.runtime.__version__:
361 warnings.warn(
362 f"Pickled queryset instance's Plain version {pickled_version} does not "
363 f"match the current version {plain.runtime.__version__}.",
364 RuntimeWarning,
365 stacklevel=2,
366 )
367 else:
368 warnings.warn(
369 "Pickled queryset instance's Plain version is not specified.",
370 RuntimeWarning,
371 stacklevel=2,
372 )
373 self.__dict__.update(state)
374
375 def __repr__(self) -> str:
376 # Don't run SQL from __repr__ — error reporters (Sentry, pdb,
377 # exception templates) call repr() on stack-frame locals to
378 # build error events. If the queryset hasn't been evaluated, a
379 # surprise SELECT inside an exception path is a known footgun.
380 if self._result_cache is None:
381 return f"<{self.__class__.__name__} [unevaluated]>"
382 data: list[Any] = list(self._result_cache[: REPR_OUTPUT_SIZE + 1])
383 if len(data) > REPR_OUTPUT_SIZE:
384 data[-1] = "...(remaining elements truncated)..."
385 return f"<{self.__class__.__name__} {data!r}>"
386
387 def __len__(self) -> int:
388 self._fetch_all()
389 assert self._result_cache is not None
390 return len(self._result_cache)
391
392 def __iter__(self) -> Iterator[T]:
393 """
394 The queryset iterator protocol uses three nested iterators in the
395 default case:
396 1. sql.compiler.execute_sql()
397 - Returns a flat iterable of rows: a list from fetchall()
398 for regular queries, or a streaming generator from
399 cursor.stream() when using .iterator().
400 2. sql.compiler.results_iter()
401 - Returns one row at a time. At this point the rows are still
402 just tuples. In some cases the return values are converted
403 to Python values at this location.
404 3. self.iterator()
405 - Responsible for turning the rows into model objects.
406 """
407 self._fetch_all()
408 assert self._result_cache is not None
409 return iter(self._result_cache)
410
411 def __bool__(self) -> bool:
412 self._fetch_all()
413 return bool(self._result_cache)
414
415 @overload
416 def __getitem__(self, k: int) -> T: ...
417
418 @overload
419 def __getitem__(self, k: slice) -> QuerySet[T]: ...
420
421 def __getitem__(self, k: int | slice) -> T | QuerySet[T]:
422 """Retrieve an item or slice from the set of results.
423
424 Slicing always returns a QuerySet, even when the results are
425 already cached. The returned QuerySet behaves exactly like one
426 sliced before evaluation — same allowed operations — except it
427 carries the sliced cache so iterating it won't re-query. The slice
428 is also applied as SQL limits, so any operation that re-chains the
429 QuerySet drops the cache and re-queries the correct rows.
430
431 Negative indexing and step slicing both raise.
432 """
433 if not isinstance(k, int | slice):
434 raise TypeError(
435 f"QuerySet indices must be integers or slices, not {type(k).__name__}."
436 )
437
438 if isinstance(k, slice):
439 if (k.start is not None and k.start < 0) or (
440 k.stop is not None and k.stop < 0
441 ):
442 raise ValueError("Negative indexing is not supported.")
443 if k.step is not None:
444 raise ValueError("Step slicing is not supported.")
445 qs = self._chain()
446 start = int(k.start) if k.start is not None else None
447 stop = int(k.stop) if k.stop is not None else None
448 qs.sql_query.set_limits(start, stop)
449 if self._result_cache is not None:
450 # Carry the sliced cache so the new QuerySet won't re-query on
451 # iteration. The SQL limits above keep it correct if it's later
452 # re-chained, which drops the cache.
453 self._attach_result_cache(qs, self._result_cache[k])
454 return qs
455
456 if k < 0:
457 raise ValueError("Negative indexing is not supported.")
458 if self._result_cache is not None:
459 return self._result_cache[k]
460
461 qs = self._chain()
462 qs.sql_query.set_limits(k, k + 1)
463 qs._fetch_all()
464 assert qs._result_cache is not None # _fetch_all guarantees this
465 return qs._result_cache[0]
466
467 def __class_getitem__(cls, *args: Any, **kwargs: Any) -> type[QuerySet[Any]]:
468 return cls
469
470 def __and__(self, other: QuerySet[T]) -> QuerySet[T]:
471 self._merge_sanity_check(other)
472 if isinstance(other, EmptyQuerySet):
473 return other
474 if isinstance(self, EmptyQuerySet):
475 return self
476 combined = self._chain()
477 combined._merge_known_related_objects(other)
478 combined.sql_query.combine(other.sql_query, AND)
479 return combined
480
481 def __or__(self, other: QuerySet[T]) -> QuerySet[T]:
482 self._merge_sanity_check(other)
483 if isinstance(self, EmptyQuerySet):
484 return other
485 if isinstance(other, EmptyQuerySet):
486 return self
487 query = (
488 self
489 if self.sql_query.can_filter()
490 else self.model._model_meta.base_queryset.filter(id__in=self.values("id"))
491 )
492 combined = query._chain()
493 combined._merge_known_related_objects(other)
494 if not other.sql_query.can_filter():
495 other = other.model._model_meta.base_queryset.filter(
496 id__in=other.values("id")
497 )
498 combined.sql_query.combine(other.sql_query, OR)
499 return combined
500
501 ####################################
502 # METHODS THAT DO DATABASE QUERIES #
503 ####################################
504
505 def _iterator(self, use_chunked_fetch: bool, chunk_size: int | None) -> Iterator[T]:
506 iterable = self._iterable_class(
507 self,
508 chunked_fetch=use_chunked_fetch,
509 )
510 if not self._prefetch_related_lookups or chunk_size is None:
511 yield from iterable
512 return
513
514 iterator = iter(iterable)
515 while results := list(islice(iterator, chunk_size)):
516 prefetch_related_objects(results, *self._prefetch_related_lookups)
517 yield from results
518
519 def iterator(self, chunk_size: int | None = None) -> Iterator[T]:
520 """
521 An iterator over the results from applying this QuerySet to the
522 database. chunk_size must be provided for QuerySets that prefetch
523 related objects. Otherwise, a default chunk_size of 2000 is supplied.
524 """
525 if chunk_size is None:
526 if self._prefetch_related_lookups:
527 raise ValueError(
528 "chunk_size must be provided when using QuerySet.iterator() after "
529 "prefetch_related()."
530 )
531 elif chunk_size <= 0:
532 raise ValueError("Chunk size must be strictly positive.")
533 # PostgreSQL always supports server-side cursors for chunked fetches
534 return self._iterator(use_chunked_fetch=True, chunk_size=chunk_size)
535
536 def aggregate(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
537 """
538 Return a dictionary containing the calculations (aggregation)
539 over the current queryset.
540
541 If args is present the expression is passed as a kwarg using
542 the Aggregate object's default alias.
543 """
544 if self.sql_query.distinct_fields:
545 raise NotImplementedError("aggregate() + distinct(fields) not implemented.")
546 self._validate_values_are_expressions(
547 (*args, *kwargs.values()), method_name="aggregate"
548 )
549 for arg in args:
550 # The default_alias property raises TypeError if default_alias
551 # can't be set automatically or AttributeError if it isn't an
552 # attribute.
553 try:
554 arg.default_alias # noqa: B018 — probe; raises for complex aggregates
555 except (AttributeError, TypeError):
556 raise TypeError("Complex aggregates require an alias")
557 kwargs[arg.default_alias] = arg
558
559 return self.sql_query.chain().get_aggregation(kwargs)
560
561 def count(self) -> int:
562 """
563 Perform a SELECT COUNT() and return the number of records as an
564 integer.
565
566 If the QuerySet is already fully cached, return the length of the
567 cached results set to avoid multiple SELECT COUNT(*) calls.
568 """
569 if self._result_cache is not None:
570 return len(self._result_cache)
571
572 return self.sql_query.get_count()
573
574 def get(self, *args: Any, **kwargs: Any) -> T:
575 """
576 Perform the query and return a single object matching the given
577 keyword arguments.
578 """
579 clone = self.filter(*args, **kwargs)
580 if self.sql_query.can_filter() and not self.sql_query.distinct_fields:
581 clone = clone.order_by()
582 limit = MAX_GET_RESULTS
583 clone.sql_query.set_limits(high=limit)
584 num = len(clone)
585 if num == 1:
586 assert clone._result_cache is not None # len() fetches results
587 return clone._result_cache[0]
588 if not num:
589 raise self.model.DoesNotExist(
590 f"{self.model.model_options.object_name} matching query does not exist."
591 )
592 raise self.model.MultipleObjectsReturned(
593 "get() returned more than one {} -- it returned {}!".format(
594 self.model.model_options.object_name,
595 num if not limit or num < limit else "more than %s" % (limit - 1),
596 )
597 )
598
599 def get_or_none(self, *args: Any, **kwargs: Any) -> T | None:
600 """
601 Perform the query and return a single object matching the given
602 keyword arguments, or None if no object is found.
603 """
604 try:
605 return self.get(*args, **kwargs)
606 except self.model.DoesNotExist:
607 return None
608
609 def create(self, **kwargs: Any) -> T:
610 """
611 Create a new object with the given kwargs, saving it to the database
612 and returning the created object.
613 """
614 obj = self.model(**kwargs)
615 obj.create()
616 return obj
617
618 def _prepare_for_bulk_create(self, objs: list[T]) -> None:
619 # The identity PK is the only PK type, so there's no literal Python
620 # default to materialize -- obj.id stays None and the INSERT takes the
621 # DB's DEFAULT path.
622 for obj in objs:
623 obj._prepare_related_fields_for_save(operation_name="bulk_create")
624
625 def _check_bulk_create_options(
626 self,
627 update_conflicts: bool,
628 update_fields: list[Field] | None,
629 unique_fields: list[Field] | None,
630 ) -> OnConflict | None:
631 if update_conflicts:
632 if not update_fields:
633 raise ValueError(
634 "Fields that will be updated when a row insertion fails "
635 "on conflicts must be provided."
636 )
637 if not unique_fields:
638 raise ValueError(
639 "Unique fields that can trigger the upsert must be provided."
640 )
641 # Updating primary keys and many-to-many fields is forbidden.
642 from plain.postgres.fields.related import ManyToManyField
643
644 if any(isinstance(f, ManyToManyField) for f in update_fields):
645 raise ValueError(
646 "bulk_create() cannot be used with many-to-many fields in "
647 "update_fields."
648 )
649 if any(f.primary_key for f in update_fields):
650 raise ValueError(
651 "bulk_create() cannot be used with primary keys in update_fields."
652 )
653 if unique_fields:
654 from plain.postgres.fields.related import ManyToManyField
655
656 if any(isinstance(f, ManyToManyField) for f in unique_fields):
657 raise ValueError(
658 "bulk_create() cannot be used with many-to-many fields "
659 "in unique_fields."
660 )
661 return OnConflict.UPDATE
662 return None
663
664 def bulk_create(
665 self,
666 objs: Sequence[T],
667 batch_size: int | None = None,
668 update_conflicts: bool = False,
669 update_fields: list[str] | None = None,
670 unique_fields: list[str] | None = None,
671 ) -> list[T]:
672 """
673 Insert each of the instances into the database. Do *not* call
674 save() on each of the instances. Primary keys are set on the objects
675 via the PostgreSQL RETURNING clause. Multi-table models are not supported.
676 """
677 if batch_size is not None and batch_size <= 0:
678 raise ValueError("Batch size must be a positive integer.")
679
680 objs = list(objs)
681 if not objs:
682 return objs
683 meta = self.model._model_meta
684 unique_fields_objs: list[Field] | None = None
685 update_fields_objs: list[Field] | None = None
686 if unique_fields:
687 unique_fields_objs = [
688 meta.get_forward_field(name) for name in unique_fields
689 ]
690 if update_fields:
691 update_fields_objs = [
692 meta.get_forward_field(name) for name in update_fields
693 ]
694 on_conflict = self._check_bulk_create_options(
695 update_conflicts,
696 update_fields_objs,
697 unique_fields_objs,
698 )
699 fields = meta.fields
700 self._prepare_for_bulk_create(objs)
701 with transaction.atomic(savepoint=False):
702 objs_with_id, objs_without_id = partition(lambda o: o.id is None, objs)
703 if objs_with_id:
704 returned_columns = self._batched_insert(
705 objs_with_id,
706 fields,
707 batch_size,
708 on_conflict=on_conflict,
709 update_fields=update_fields_objs,
710 unique_fields=unique_fields_objs,
711 )
712 id_field = meta.get_forward_field("id")
713 for obj_with_id, results in zip(objs_with_id, returned_columns):
714 for result, field in zip(results, meta.db_returning_fields):
715 if field != id_field:
716 setattr(obj_with_id, field.name, result)
717 for obj_with_id in objs_with_id:
718 obj_with_id._state.adding = False
719 if objs_without_id:
720 fields = [f for f in fields if not isinstance(f, PrimaryKeyField)]
721 returned_columns = self._batched_insert(
722 objs_without_id,
723 fields,
724 batch_size,
725 on_conflict=on_conflict,
726 update_fields=update_fields_objs,
727 unique_fields=unique_fields_objs,
728 )
729 if on_conflict is None:
730 assert len(returned_columns) == len(objs_without_id)
731 for obj_without_id, results in zip(objs_without_id, returned_columns):
732 for result, field in zip(results, meta.db_returning_fields):
733 setattr(obj_without_id, field.name, result)
734 obj_without_id._state.adding = False
735
736 return objs
737
738 def bulk_update(
739 self, objs: Sequence[T], fields: list[str], batch_size: int | None = None
740 ) -> int:
741 """
742 Update the given fields in each of the given objects in the database.
743 """
744 if batch_size is not None and batch_size <= 0:
745 raise ValueError("Batch size must be a positive integer.")
746 if not fields:
747 raise ValueError("Field names must be given to bulk_update().")
748 objs_tuple = tuple(objs)
749 if any(obj.id is None for obj in objs_tuple):
750 raise ValueError("All bulk_update() objects must have a primary key set.")
751 fields_list = [
752 self.model._model_meta.get_forward_field(name) for name in fields
753 ]
754 from plain.postgres.fields.related import ManyToManyField
755
756 if any(isinstance(f, ManyToManyField) for f in fields_list):
757 raise ValueError("bulk_update() cannot be used with many-to-many fields.")
758 if any(f.primary_key for f in fields_list):
759 raise ValueError("bulk_update() cannot be used with primary key fields.")
760 if not objs_tuple:
761 return 0
762 for obj in objs_tuple:
763 obj._prepare_related_fields_for_save(
764 operation_name="bulk_update", field_names=fields
765 )
766 # PK is used twice in the resulting update query, once in the filter
767 # and once in the WHEN. Each field will also have one CAST.
768 max_batch_size = len(objs_tuple)
769 batch_size = min(batch_size, max_batch_size) if batch_size else max_batch_size
770 batches = (
771 objs_tuple[i : i + batch_size]
772 for i in range(0, len(objs_tuple), batch_size)
773 )
774 updates = []
775 for batch_objs in batches:
776 update_kwargs = {}
777 for field in fields_list:
778 when_statements = []
779 for obj in batch_objs:
780 attr = field.value_from_object(obj)
781 if not isinstance(attr, ResolvableExpression):
782 attr = Value(attr, output_field=field)
783 when_statements.append(When(id=obj.id, then=attr))
784 case_statement = Case(*when_statements, output_field=field)
785 # PostgreSQL requires casted CASE in updates
786 case_statement = Cast(case_statement, output_field=field)
787 update_kwargs[field.name] = case_statement
788 updates.append(([obj.id for obj in batch_objs], update_kwargs))
789 rows_updated = 0
790 queryset = self._chain()
791 with transaction.atomic(savepoint=False):
792 for ids, update_kwargs in updates:
793 rows_updated += queryset.filter(id__in=ids).update(**update_kwargs)
794 return rows_updated
795
796 def get_or_create(
797 self, defaults: dict[str, Any] | None = None, **kwargs: Any
798 ) -> tuple[T, bool]:
799 """
800 Look up an object with the given kwargs, creating one if necessary.
801 Return a tuple of (object, created), where created is a boolean
802 specifying whether an object was created.
803 """
804 # The get() needs to be targeted at the write database in order
805 # to avoid potential transaction consistency problems.
806 try:
807 return self.get(**kwargs), False
808 except self.model.DoesNotExist:
809 params = self._extract_model_params(defaults, **kwargs)
810 # Try to create an object using passed params.
811 try:
812 with transaction.atomic():
813 params = dict(resolve_callables(params))
814 return self.create(**params), True
815 except (psycopg.IntegrityError, ValidationError):
816 # Since create() also validates by default,
817 # we can get any kind of ValidationError here,
818 # or it can flow through and get an IntegrityError from the database.
819 # The main thing we're concerned about is uniqueness failures,
820 # but ValidationError could include other things too.
821 # In all cases though it should be fine to try the get() again
822 # and return an existing object.
823 try:
824 return self.get(**kwargs), False
825 except self.model.DoesNotExist:
826 pass
827 raise
828
829 def update_or_create(
830 self,
831 defaults: dict[str, Any] | None = None,
832 create_defaults: dict[str, Any] | None = None,
833 **kwargs: Any,
834 ) -> tuple[T, bool]:
835 """
836 Look up an object with the given kwargs, updating one with defaults
837 if it exists, otherwise create a new one. Optionally, an object can
838 be created with different values than defaults by using
839 create_defaults.
840 Return a tuple (object, created), where created is a boolean
841 specifying whether an object was created.
842 """
843 if create_defaults is None:
844 update_defaults = create_defaults = defaults or {}
845 else:
846 update_defaults = defaults or {}
847 with transaction.atomic():
848 # Lock the row so that a concurrent update is blocked until
849 # update_or_create() has performed its save.
850 obj, created = self.select_for_update().get_or_create(
851 create_defaults, **kwargs
852 )
853 if created:
854 return obj, created
855 for k, v in resolve_callables(update_defaults):
856 setattr(obj, k, v)
857
858 update_fields = set(update_defaults)
859 field_names = self.model._model_meta._non_pk_field_names
860 # update_fields only supports column-backed fields.
861 if field_names.issuperset(update_fields):
862 # Add fields which are set on pre_save(), e.g. update_now fields.
863 # This is to maintain backward compatibility as these fields
864 # are not updated unless explicitly specified in the
865 # update_fields list.
866 for field in self.model._model_meta.fields:
867 if not (
868 field.primary_key or field.__class__.pre_save is Field.pre_save
869 ):
870 update_fields.add(field.name)
871 obj.update(fields=update_fields)
872 else:
873 obj.update()
874 return obj, False
875
876 def _extract_model_params(
877 self, defaults: dict[str, Any] | None, **kwargs: Any
878 ) -> dict[str, Any]:
879 """
880 Prepare `params` for creating a model instance based on the given
881 kwargs; for use by get_or_create().
882 """
883 defaults = defaults or {}
884 params = {k: v for k, v in kwargs.items() if LOOKUP_SEP not in k}
885 params.update(defaults)
886 property_names = self.model._model_meta._property_names
887 invalid_params = []
888 for param in params:
889 try:
890 self.model._model_meta.get_field(param)
891 except FieldDoesNotExist:
892 # It's okay to use a model's property if it has a setter.
893 if not (param in property_names and getattr(self.model, param).fset):
894 invalid_params.append(param)
895 if invalid_params:
896 raise FieldError(
897 "Invalid field name(s) for model {}: '{}'.".format(
898 self.model.model_options.object_name,
899 "', '".join(sorted(invalid_params)),
900 )
901 )
902 return params
903
904 def first(self) -> T | None:
905 """Return the first object of a query or None if no match is found."""
906 for obj in self[:1]:
907 return obj
908 return None
909
910 def last(self) -> T | None:
911 """Return the last object of a query or None if no match is found."""
912 queryset = self.reverse()
913 for obj in queryset[:1]:
914 return obj
915 return None
916
917 def delete(self) -> int:
918 """Delete the records in the current QuerySet.
919
920 Returns the number of parent rows deleted. Cascaded child rows are
921 handled by Postgres via the declared `on_delete` clauses and are not
922 included in the count.
923 """
924 if self.sql_query.is_sliced:
925 raise TypeError("Cannot use 'limit' or 'offset' with delete().")
926 if self.sql_query.distinct or self.sql_query.distinct_fields:
927 raise TypeError("Cannot call delete() after .distinct().")
928 if self._fields is not None:
929 raise TypeError("Cannot call delete() after .values() or .values_list()")
930
931 del_query = self._chain()
932 del_query.sql_query.select_for_update = False
933 del_query.sql_query.select_related = False
934 del_query.sql_query.clear_ordering(force=True)
935
936 # RESTRICT violations leave the DB transaction aborted. Mark the
937 # connection so outer atomic() blocks see the abort state even if the
938 # caller catches IntegrityError themselves.
939 with transaction.mark_for_rollback_on_error():
940 count = del_query._raw_delete()
941
942 # Clear the result cache, in case this QuerySet gets reused.
943 self._result_cache = None
944 return count
945
946 def _raw_delete(self) -> int:
947 """
948 Delete objects found from the given queryset in single direct SQL
949 query. No signals are sent and there is no protection for cascades.
950 """
951 query = self.sql_query.clone()
952 query.__class__ = DeleteQuery
953 cursor = query.get_compiler().execute_sql(CURSOR)
954 if cursor:
955 with cursor:
956 return cursor.rowcount
957 return 0
958
959 def update(self, **kwargs: Any) -> int:
960 """
961 Update all elements in the current QuerySet, setting all the given
962 fields to the appropriate values.
963 """
964 if self.sql_query.is_sliced:
965 raise TypeError("Cannot update a query once a slice has been taken.")
966 query = self.sql_query.chain(UpdateQuery)
967 query.add_update_values(kwargs)
968
969 # Inline annotations in order_by(), if possible.
970 new_order_by = []
971 for col in query.order_by:
972 alias = col
973 descending = False
974 if isinstance(alias, str) and alias.startswith("-"):
975 alias = alias.removeprefix("-")
976 descending = True
977 if annotation := query.annotations.get(alias):
978 if getattr(annotation, "contains_aggregate", False):
979 raise FieldError(
980 f"Cannot update when ordering by an aggregate: {annotation}"
981 )
982 if descending:
983 annotation = annotation.desc()
984 new_order_by.append(annotation)
985 else:
986 new_order_by.append(col)
987 query.order_by = tuple(new_order_by)
988
989 # Clear any annotations so that they won't be present in subqueries.
990 query.annotations = {}
991 with transaction.mark_for_rollback_on_error():
992 rows = query.get_compiler().execute_sql(CURSOR)
993 self._result_cache = None
994 return rows
995
996 def _update(self, values: Sequence[tuple[Field, Any]]) -> int:
997 """
998 A version of update() that accepts field objects instead of field names.
999 Used primarily for model saving and not intended for use by general
1000 code (it requires too much poking around at model internals to be
1001 useful at that level).
1002 """
1003 if self.sql_query.is_sliced:
1004 raise TypeError("Cannot update a query once a slice has been taken.")
1005 query = self.sql_query.chain(UpdateQuery)
1006 query.add_update_fields(values)
1007 # Clear any annotations so that they won't be present in subqueries.
1008 query.annotations = {}
1009 self._result_cache = None
1010 return query.get_compiler().execute_sql(CURSOR)
1011
1012 def exists(self) -> bool:
1013 """
1014 Return True if the QuerySet would have any results, False otherwise.
1015 """
1016 if self._result_cache is None:
1017 return self.sql_query.has_results()
1018 return bool(self._result_cache)
1019
1020 def _prefetch_related_objects(self) -> None:
1021 # This method can only be called once the result cache has been filled.
1022 assert self._result_cache is not None
1023 prefetch_related_objects(self._result_cache, *self._prefetch_related_lookups)
1024 self._prefetch_done = True
1025
1026 def explain(self, *, format: str | None = None, **options: Any) -> str:
1027 """
1028 Runs an EXPLAIN on the SQL query this QuerySet would perform, and
1029 returns the results.
1030 """
1031 return self.sql_query.explain(format=format, **options)
1032
1033 ##################################################
1034 # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
1035 ##################################################
1036
1037 def raw(
1038 self,
1039 raw_query: str,
1040 params: Sequence[Any] = (),
1041 translations: dict[str, str] | None = None,
1042 ) -> RawQuerySet:
1043 qs = RawQuerySet(
1044 raw_query,
1045 model=self.model,
1046 params=tuple(params),
1047 translations=translations,
1048 )
1049 qs._prefetch_related_lookups = self._prefetch_related_lookups[:]
1050 return qs
1051
1052 def _values(self, *fields: str, **expressions: Any) -> QuerySet[Any]:
1053 clone = self._chain()
1054 if expressions:
1055 clone = clone.annotate(**expressions)
1056 clone._fields = fields
1057 clone.sql_query.set_values(list(fields))
1058 return clone
1059
1060 def values(self, *fields: str, **expressions: Any) -> QuerySet[Any]:
1061 fields += tuple(expressions)
1062 clone = self._values(*fields, **expressions)
1063 clone._iterable_class = ValuesIterable
1064 return clone
1065
1066 def values_list(self, *fields: str, flat: bool = False) -> QuerySet[Any]:
1067 if flat and len(fields) > 1:
1068 raise TypeError(
1069 "'flat' is not valid when values_list is called with more than one "
1070 "field."
1071 )
1072
1073 field_names = {f for f in fields if not isinstance(f, ResolvableExpression)}
1074 _fields = []
1075 expressions = {}
1076 counter = 1
1077 for field in fields:
1078 if isinstance(field, ResolvableExpression):
1079 field_id_prefix = getattr(
1080 field, "default_alias", field.__class__.__name__.lower()
1081 )
1082 while True:
1083 field_id = field_id_prefix + str(counter)
1084 counter += 1
1085 if field_id not in field_names:
1086 break
1087 expressions[field_id] = field
1088 _fields.append(field_id)
1089 else:
1090 _fields.append(field)
1091
1092 clone = self._values(*_fields, **expressions)
1093 clone._iterable_class = FlatValuesListIterable if flat else ValuesListIterable
1094 return clone
1095
1096 def none(self) -> QuerySet[T]:
1097 """Return an empty QuerySet."""
1098 clone = self._chain()
1099 clone.sql_query.set_empty()
1100 return clone
1101
1102 ##################################################################
1103 # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
1104 ##################################################################
1105
1106 def all(self) -> Self:
1107 """
1108 Return a new QuerySet that is a copy of the current one. This allows a
1109 QuerySet to proxy for a model queryset in some cases.
1110 """
1111 obj = self._chain()
1112 # Preserve cache since all() doesn't modify the query.
1113 if self._result_cache is not None:
1114 self._attach_result_cache(obj, self._result_cache)
1115 return obj
1116
1117 def filter(self, *args: Any, **kwargs: Any) -> Self:
1118 """
1119 Return a new QuerySet instance with the args ANDed to the existing
1120 set.
1121 """
1122 return self._filter_or_exclude(False, args, kwargs)
1123
1124 def exclude(self, *args: Any, **kwargs: Any) -> Self:
1125 """
1126 Return a new QuerySet instance with NOT (args) ANDed to the existing
1127 set.
1128 """
1129 return self._filter_or_exclude(True, args, kwargs)
1130
1131 def _filter_or_exclude(
1132 self, negate: bool, args: tuple[Any, ...], kwargs: dict[str, Any]
1133 ) -> Self:
1134 if (args or kwargs) and self.sql_query.is_sliced:
1135 raise TypeError("Cannot filter a query once a slice has been taken.")
1136 clone = self._chain()
1137 if self._defer_next_filter:
1138 self._defer_next_filter = False
1139 clone._deferred_filter = negate, args, kwargs
1140 else:
1141 clone._filter_or_exclude_inplace(negate, args, kwargs)
1142 return clone
1143
1144 def _filter_or_exclude_inplace(
1145 self, negate: bool, args: tuple[Any, ...], kwargs: dict[str, Any]
1146 ) -> None:
1147 if negate:
1148 self._query.add_q(~Q(*args, **kwargs))
1149 else:
1150 self._query.add_q(Q(*args, **kwargs))
1151
1152 def select_for_update(
1153 self,
1154 nowait: bool = False,
1155 skip_locked: bool = False,
1156 of: tuple[str, ...] = (),
1157 no_key: bool = False,
1158 ) -> QuerySet[T]:
1159 """
1160 Return a new QuerySet instance that will select objects with a
1161 FOR UPDATE lock.
1162 """
1163 if nowait and skip_locked:
1164 raise ValueError("The nowait option cannot be used with skip_locked.")
1165 obj = self._chain()
1166 obj.sql_query.select_for_update = True
1167 obj.sql_query.select_for_update_nowait = nowait
1168 obj.sql_query.select_for_update_skip_locked = skip_locked
1169 obj.sql_query.select_for_update_of = of
1170 obj.sql_query.select_for_no_key_update = no_key
1171 return obj
1172
1173 def select_related(self, *fields: str | None) -> Self:
1174 """
1175 Return a new QuerySet instance that will select related objects.
1176
1177 If fields are specified, they must be ForeignKeyField fields and only those
1178 related objects are included in the selection.
1179
1180 If select_related(None) is called, clear the list.
1181 """
1182 if self._fields is not None:
1183 raise TypeError(
1184 "Cannot call select_related() after .values() or .values_list()"
1185 )
1186
1187 obj = self._chain()
1188 if fields == (None,):
1189 obj.sql_query.select_related = False
1190 elif fields:
1191 obj.sql_query.add_select_related(list(fields)) # ty: ignore[invalid-argument-type]
1192 else:
1193 obj.sql_query.select_related = True
1194 return obj
1195
1196 def prefetch_related(self, *lookups: str | Prefetch | None) -> Self:
1197 """
1198 Return a new QuerySet instance that will prefetch the specified
1199 Many-To-One and Many-To-Many related objects when the QuerySet is
1200 evaluated.
1201
1202 When prefetch_related() is called more than once, append to the list of
1203 prefetch lookups. If prefetch_related(None) is called, clear the list.
1204 """
1205 clone = self._chain()
1206 if lookups == (None,):
1207 clone._prefetch_related_lookups = ()
1208 else:
1209 clone._prefetch_related_lookups = clone._prefetch_related_lookups + lookups
1210 return clone
1211
1212 def annotate(self, *args: Any, **kwargs: Any) -> Self:
1213 """
1214 Return a query set in which the returned objects have been annotated
1215 with extra data or aggregations.
1216 """
1217 self._validate_values_are_expressions(
1218 args + tuple(kwargs.values()), method_name="annotate"
1219 )
1220 annotations = {}
1221 for arg in args:
1222 # The default_alias property may raise a TypeError.
1223 try:
1224 if arg.default_alias in kwargs:
1225 raise ValueError(
1226 f"The named annotation '{arg.default_alias}' conflicts with the "
1227 "default name for another annotation."
1228 )
1229 except TypeError:
1230 raise TypeError("Complex annotations require an alias")
1231 annotations[arg.default_alias] = arg
1232 annotations.update(kwargs)
1233
1234 clone = self._chain()
1235 names = self._fields
1236 if names is None:
1237 names = {field.name for field in self.model._model_meta.get_fields()}
1238
1239 for alias, annotation in annotations.items():
1240 if alias in names:
1241 raise ValueError(
1242 f"The annotation '{alias}' conflicts with a field on the model."
1243 )
1244 clone.sql_query.add_annotation(annotation, alias)
1245 for alias, annotation in clone.sql_query.annotations.items():
1246 if alias in annotations and annotation.contains_aggregate:
1247 if clone._fields is None:
1248 clone.sql_query.group_by = True
1249 else:
1250 clone.sql_query.set_group_by()
1251 break
1252
1253 return clone
1254
1255 def order_by(self, *field_names: str | ResolvableExpression) -> Self:
1256 """Return a new QuerySet instance with the ordering changed."""
1257 if self.sql_query.is_sliced:
1258 raise TypeError("Cannot reorder a query once a slice has been taken.")
1259 obj = self._chain()
1260 obj.sql_query.clear_ordering(force=True, clear_default=False)
1261 obj.sql_query.add_ordering(*field_names)
1262 return obj
1263
1264 def distinct(self, *field_names: str) -> Self:
1265 """
1266 Return a new QuerySet instance that will select only distinct results.
1267 """
1268 if self.sql_query.is_sliced:
1269 raise TypeError(
1270 "Cannot create distinct fields once a slice has been taken."
1271 )
1272 obj = self._chain()
1273 obj.sql_query.add_distinct_fields(*field_names)
1274 return obj
1275
1276 def reverse(self) -> QuerySet[T]:
1277 """Reverse the ordering of the QuerySet."""
1278 if self.sql_query.is_sliced:
1279 raise TypeError("Cannot reverse a query once a slice has been taken.")
1280 clone = self._chain()
1281 clone.sql_query.standard_ordering = not clone.sql_query.standard_ordering
1282 return clone
1283
1284 def defer(self, *fields: str | None) -> QuerySet[T]:
1285 """
1286 Defer the loading of data for certain fields until they are accessed.
1287 Add the set of deferred fields to any existing set of deferred fields.
1288 The only exception to this is if None is passed in as the only
1289 parameter, in which case removal all deferrals.
1290 """
1291 if self._fields is not None:
1292 raise TypeError("Cannot call defer() after .values() or .values_list()")
1293 clone = self._chain()
1294 if fields == (None,):
1295 clone.sql_query.clear_deferred_loading()
1296 else:
1297 clone.sql_query.add_deferred_loading(frozenset(fields)) # ty: ignore[invalid-argument-type]
1298 return clone
1299
1300 def only(self, *fields: str) -> QuerySet[T]:
1301 """
1302 Essentially, the opposite of defer(). Only the fields passed into this
1303 method and that are not already specified as deferred are loaded
1304 immediately when the queryset is evaluated.
1305 """
1306 if self._fields is not None:
1307 raise TypeError("Cannot call only() after .values() or .values_list()")
1308 if fields == (None,):
1309 # Can only pass None to defer(), not only(), as the rest option.
1310 # That won't stop people trying to do this, so let's be explicit.
1311 raise TypeError("Cannot pass None as an argument to only().")
1312 clone = self._chain()
1313 clone.sql_query.add_immediate_loading(set(fields))
1314 return clone
1315
1316 ###################################
1317 # PUBLIC INTROSPECTION ATTRIBUTES #
1318 ###################################
1319
1320 @property
1321 def ordered(self) -> bool:
1322 """
1323 Return True if the QuerySet is ordered -- i.e. has an order_by()
1324 clause or a default ordering on the model (or is empty).
1325 """
1326 if isinstance(self, EmptyQuerySet):
1327 return True
1328 return bool(
1329 self.sql_query.order_by
1330 or (
1331 self.sql_query.default_ordering
1332 and self.sql_query.model
1333 and self.sql_query.model.model_options.ordering
1334 and
1335 # A default ordering doesn't affect GROUP BY queries.
1336 not self.sql_query.group_by
1337 )
1338 )
1339
1340 ###################
1341 # PRIVATE METHODS #
1342 ###################
1343
1344 def _insert(
1345 self,
1346 objs: list[T],
1347 fields: Sequence[Field],
1348 returning_fields: list[Field] | None = None,
1349 on_conflict: OnConflict | None = None,
1350 update_fields: list[Field] | None = None,
1351 unique_fields: list[Field] | None = None,
1352 ) -> list[tuple[Any, ...]] | None:
1353 """
1354 Insert a new record for the given model. This provides an interface to
1355 the InsertQuery class and is how Model.create() is implemented.
1356 """
1357 query = InsertQuery(
1358 self.model,
1359 on_conflict=on_conflict if on_conflict else None,
1360 update_fields=update_fields,
1361 unique_fields=unique_fields,
1362 )
1363 query.insert_values(fields, objs)
1364 # InsertQuery returns SQLInsertCompiler which has different execute_sql signature
1365 return query.get_compiler().execute_sql(returning_fields)
1366
1367 def _batched_insert(
1368 self,
1369 objs: list[T],
1370 fields: Sequence[Field],
1371 batch_size: int | None,
1372 on_conflict: OnConflict | None = None,
1373 update_fields: list[Field] | None = None,
1374 unique_fields: list[Field] | None = None,
1375 ) -> list[tuple[Any, ...]]:
1376 """
1377 Helper method for bulk_create() to insert objs one batch at a time.
1378 """
1379 max_batch_size = max(len(objs), 1)
1380 batch_size = min(batch_size, max_batch_size) if batch_size else max_batch_size
1381 inserted_rows = []
1382 for item in [objs[i : i + batch_size] for i in range(0, len(objs), batch_size)]:
1383 if on_conflict is None:
1384 inserted_rows.extend(
1385 self._insert( # ty: ignore[invalid-argument-type]
1386 item,
1387 fields=fields,
1388 returning_fields=self.model._model_meta.db_returning_fields,
1389 )
1390 )
1391 else:
1392 self._insert(
1393 item,
1394 fields=fields,
1395 on_conflict=on_conflict,
1396 update_fields=update_fields,
1397 unique_fields=unique_fields,
1398 )
1399 return inserted_rows
1400
1401 def _chain(self) -> Self:
1402 """
1403 Return a copy of the current QuerySet that's ready for another
1404 operation.
1405 """
1406 obj = self._clone()
1407 if obj._sticky_filter:
1408 obj.sql_query.filter_is_sticky = True
1409 obj._sticky_filter = False
1410 return obj
1411
1412 def _clone(self) -> Self:
1413 """
1414 Return a copy of the current QuerySet. A lightweight alternative
1415 to deepcopy().
1416 """
1417 c = self.__class__.from_model(
1418 model=self.model,
1419 query=self.sql_query.chain(),
1420 )
1421 c._sticky_filter = self._sticky_filter
1422 c._prefetch_related_lookups = self._prefetch_related_lookups[:]
1423 c._known_related_objects = self._known_related_objects
1424 c._iterable_class = self._iterable_class
1425 c._fields = self._fields
1426 return c
1427
1428 def _attach_result_cache(self, obj: Self, cache: list[T]) -> None:
1429 """Carry a result cache onto a chained QuerySet.
1430
1431 Whenever a cache is moved onto a new QuerySet, the prefetch state
1432 must ride along with it — otherwise prefetch_related() would re-run
1433 (or be skipped). Keep both writes together here so callers can't
1434 forget the pairing.
1435 """
1436 obj._result_cache = cache
1437 obj._prefetch_done = self._prefetch_done
1438
1439 def _fetch_all(self) -> None:
1440 if self._result_cache is None:
1441 self._result_cache = list(self._iterable_class(self))
1442 if self._prefetch_related_lookups and not self._prefetch_done:
1443 self._prefetch_related_objects()
1444
1445 def _next_is_sticky(self) -> QuerySet[T]:
1446 """
1447 Indicate that the next filter call and the one following that should
1448 be treated as a single filter. This is only important when it comes to
1449 determining when to reuse tables for many-to-many filters. Required so
1450 that we can filter naturally on the results of related managers.
1451
1452 This doesn't return a clone of the current QuerySet (it returns
1453 "self"). The method is only used internally and should be immediately
1454 followed by a filter() that does create a clone.
1455 """
1456 self._sticky_filter = True
1457 return self
1458
1459 def _merge_sanity_check(self, other: QuerySet[T]) -> None:
1460 """Check that two QuerySet classes may be merged."""
1461 if self._fields is not None and (
1462 set(self.sql_query.values_select) != set(other.sql_query.values_select)
1463 or set(self.sql_query.annotation_select)
1464 != set(other.sql_query.annotation_select)
1465 ):
1466 raise TypeError(
1467 f"Merging '{self.__class__.__name__}' classes must involve the same values in each case."
1468 )
1469
1470 def _merge_known_related_objects(self, other: QuerySet[T]) -> None:
1471 """
1472 Keep track of all known related objects from either QuerySet instance.
1473 """
1474 for field, objects in other._known_related_objects.items():
1475 self._known_related_objects.setdefault(field, {}).update(objects)
1476
1477 def resolve_expression(self, *args: Any, **kwargs: Any) -> Query:
1478 if self._fields and len(self._fields) > 1:
1479 # values() queryset can only be used as nested queries
1480 # if they are set up to select only a single field.
1481 raise TypeError("Cannot use multi-field values as a filter value.")
1482 query = self.sql_query.resolve_expression(*args, **kwargs)
1483 return query
1484
1485 def _has_filters(self) -> bool:
1486 """
1487 Check if this QuerySet has any filtering going on. This isn't
1488 equivalent with checking if all objects are present in results, for
1489 example, qs[1:]._has_filters() -> False.
1490 """
1491 return self.sql_query.has_filters()
1492
1493 @staticmethod
1494 def _validate_values_are_expressions(
1495 values: tuple[Any, ...], method_name: str
1496 ) -> None:
1497 invalid_args = sorted(
1498 str(arg) for arg in values if not isinstance(arg, ResolvableExpression)
1499 )
1500 if invalid_args:
1501 raise TypeError(
1502 "QuerySet.{}() received non-expression(s): {}.".format(
1503 method_name,
1504 ", ".join(invalid_args),
1505 )
1506 )
1507
1508
1509class InstanceCheckMeta(type):
1510 def __instancecheck__(self, instance: object) -> bool:
1511 return isinstance(instance, QuerySet) and instance.sql_query.is_empty()
1512
1513
1514class EmptyQuerySet(metaclass=InstanceCheckMeta):
1515 """
1516 Marker class to checking if a queryset is empty by .none():
1517 isinstance(qs.none(), EmptyQuerySet) -> True
1518 """
1519
1520 def __init__(self, *args: Any, **kwargs: Any):
1521 raise TypeError("EmptyQuerySet can't be instantiated")
1522
1523
1524class RawQuerySet:
1525 """
1526 Provide an iterator which converts the results of raw SQL queries into
1527 annotated model instances.
1528 """
1529
1530 def __init__(
1531 self,
1532 raw_query: str,
1533 model: type[Model] | None = None,
1534 query: RawQuery | None = None,
1535 params: tuple[Any, ...] = (),
1536 translations: dict[str, str] | None = None,
1537 ):
1538 self.raw_query = raw_query
1539 self.model = model
1540 self.sql_query = query or RawQuery(sql=raw_query, params=params)
1541 self.params = params
1542 self.translations = translations or {}
1543 self._result_cache: list[Model] | None = None
1544 self._prefetch_related_lookups: tuple[Any, ...] = ()
1545 self._prefetch_done = False
1546
1547 def resolve_model_init_order(
1548 self,
1549 ) -> tuple[list[str], list[int], list[tuple[str, int]]]:
1550 """Resolve the init field names and value positions."""
1551 model = self.model
1552 assert model is not None
1553 model_init_fields = [
1554 f for f in model._model_meta.fields if f.column in self.columns
1555 ]
1556 annotation_fields = [
1557 (column, pos)
1558 for pos, column in enumerate(self.columns)
1559 if column not in self.model_fields
1560 ]
1561 model_init_order = [self.columns.index(f.column) for f in model_init_fields]
1562 model_init_names = [f.name for f in model_init_fields]
1563 return model_init_names, model_init_order, annotation_fields
1564
1565 def prefetch_related(self, *lookups: str | Prefetch | None) -> RawQuerySet:
1566 """Same as QuerySet.prefetch_related()"""
1567 clone = self._clone()
1568 if lookups == (None,):
1569 clone._prefetch_related_lookups = ()
1570 else:
1571 clone._prefetch_related_lookups = clone._prefetch_related_lookups + lookups
1572 return clone
1573
1574 def _prefetch_related_objects(self) -> None:
1575 assert self._result_cache is not None
1576 prefetch_related_objects(self._result_cache, *self._prefetch_related_lookups)
1577 self._prefetch_done = True
1578
1579 def _clone(self) -> RawQuerySet:
1580 """Same as QuerySet._clone()"""
1581 c = self.__class__(
1582 self.raw_query,
1583 model=self.model,
1584 query=self.sql_query,
1585 params=self.params,
1586 translations=self.translations,
1587 )
1588 c._prefetch_related_lookups = self._prefetch_related_lookups[:]
1589 return c
1590
1591 def _fetch_all(self) -> None:
1592 if self._result_cache is None:
1593 self._result_cache = list(self.iterator())
1594 if self._prefetch_related_lookups and not self._prefetch_done:
1595 self._prefetch_related_objects()
1596
1597 def __len__(self) -> int:
1598 self._fetch_all()
1599 assert self._result_cache is not None
1600 return len(self._result_cache)
1601
1602 def __bool__(self) -> bool:
1603 self._fetch_all()
1604 return bool(self._result_cache)
1605
1606 def __iter__(self) -> Iterator[Model]:
1607 self._fetch_all()
1608 assert self._result_cache is not None
1609 return iter(self._result_cache)
1610
1611 def iterator(self) -> Iterator[Model]:
1612 yield from RawModelIterable(self) # ty: ignore[invalid-argument-type]
1613
1614 def __repr__(self) -> str:
1615 return f"<{self.__class__.__name__}: {self.sql_query}>"
1616
1617 def __getitem__(self, k: int | slice) -> Model | list[Model]:
1618 # Unlike QuerySet, a RawQuerySet is always fully materialized — there's
1619 # no lazy query to push a slice into — so indexing keeps plain list
1620 # semantics: a slice returns a list (and step/negative slicing work).
1621 return list(self)[k]
1622
1623 @cached_property
1624 def columns(self) -> list[str]:
1625 """
1626 A list of model field names in the order they'll appear in the
1627 query results.
1628 """
1629 columns = self.sql_query.get_columns()
1630 # Adjust any column names which don't match field names
1631 for query_name, model_name in self.translations.items():
1632 # Ignore translations for nonexistent column names
1633 try:
1634 index = columns.index(query_name)
1635 except ValueError:
1636 pass
1637 else:
1638 columns[index] = model_name
1639 return columns
1640
1641 @cached_property
1642 def model_fields(self) -> dict[str, Field]:
1643 """A dict mapping column names to model field names."""
1644 model_fields = {}
1645 model = self.model
1646 assert model is not None
1647 for field in model._model_meta.fields:
1648 model_fields[field.column] = field
1649 return model_fields
1650
1651
1652class Prefetch:
1653 def __init__(
1654 self,
1655 lookup: str,
1656 queryset: QuerySet[Any] | None = None,
1657 to_attr: str | None = None,
1658 ):
1659 # `prefetch_through` is the path we traverse to perform the prefetch.
1660 self.prefetch_through = lookup
1661 # `prefetch_to` is the path to the attribute that stores the result.
1662 self.prefetch_to = lookup
1663 if queryset is not None and (
1664 isinstance(queryset, RawQuerySet)
1665 or (
1666 hasattr(queryset, "_iterable_class")
1667 and not issubclass(queryset._iterable_class, ModelIterable)
1668 )
1669 ):
1670 raise ValueError(
1671 "Prefetch querysets cannot use raw(), values(), and values_list()."
1672 )
1673 if to_attr:
1674 self.prefetch_to = LOOKUP_SEP.join(
1675 lookup.split(LOOKUP_SEP)[:-1] + [to_attr]
1676 )
1677
1678 self.queryset = queryset
1679 self.to_attr = to_attr
1680
1681 def __getstate__(self) -> dict[str, Any]:
1682 obj_dict = self.__dict__.copy()
1683 if self.queryset is not None:
1684 queryset = self.queryset._chain()
1685 # Prevent the QuerySet from being evaluated
1686 queryset._result_cache = []
1687 queryset._prefetch_done = True
1688 obj_dict["queryset"] = queryset
1689 return obj_dict
1690
1691 def add_prefix(self, prefix: str) -> None:
1692 self.prefetch_through = prefix + LOOKUP_SEP + self.prefetch_through
1693 self.prefetch_to = prefix + LOOKUP_SEP + self.prefetch_to
1694
1695 def get_current_prefetch_to(self, level: int) -> str:
1696 return LOOKUP_SEP.join(self.prefetch_to.split(LOOKUP_SEP)[: level + 1])
1697
1698 def get_current_to_attr(self, level: int) -> tuple[str, bool]:
1699 parts = self.prefetch_to.split(LOOKUP_SEP)
1700 to_attr = parts[level]
1701 as_attr = bool(self.to_attr and level == len(parts) - 1)
1702 return to_attr, as_attr
1703
1704 def get_current_queryset(self, level: int) -> QuerySet[Any] | None:
1705 if self.get_current_prefetch_to(level) == self.prefetch_to:
1706 return self.queryset
1707 return None
1708
1709 def __eq__(self, other: object) -> bool:
1710 if not isinstance(other, Prefetch):
1711 return NotImplemented
1712 return self.prefetch_to == other.prefetch_to
1713
1714 def __hash__(self) -> int:
1715 return hash((self.__class__, self.prefetch_to))
1716
1717
1718def normalize_prefetch_lookups(
1719 lookups: tuple[str | Prefetch, ...] | list[str | Prefetch],
1720 prefix: str | None = None,
1721) -> list[Prefetch]:
1722 """Normalize lookups into Prefetch objects."""
1723 ret = []
1724 for lookup in lookups:
1725 if not isinstance(lookup, Prefetch):
1726 lookup = Prefetch(lookup)
1727 if prefix:
1728 lookup.add_prefix(prefix)
1729 ret.append(lookup)
1730 return ret
1731
1732
1733def prefetch_related_objects(
1734 model_instances: Sequence[Model], *related_lookups: str | Prefetch
1735) -> None:
1736 """
1737 Populate prefetched object caches for a list of model instances based on
1738 the lookups/Prefetch instances given.
1739 """
1740 if not model_instances:
1741 return # nothing to do
1742
1743 # We need to be able to dynamically add to the list of prefetch_related
1744 # lookups that we look up (see below). So we need some book keeping to
1745 # ensure we don't do duplicate work.
1746 done_queries = {} # dictionary of things like 'foo__bar': [results]
1747
1748 auto_lookups = set() # we add to this as we go through.
1749 followed_descriptors = set() # recursion protection
1750
1751 all_lookups = normalize_prefetch_lookups(list(reversed(related_lookups)))
1752 while all_lookups:
1753 lookup = all_lookups.pop()
1754 if lookup.prefetch_to in done_queries:
1755 if lookup.queryset is not None:
1756 raise ValueError(
1757 f"'{lookup.prefetch_to}' lookup was already seen with a different queryset. "
1758 "You may need to adjust the ordering of your lookups."
1759 )
1760
1761 continue
1762
1763 # Top level, the list of objects to decorate is the result cache
1764 # from the primary QuerySet. It won't be for deeper levels.
1765 obj_list = model_instances
1766
1767 through_attrs = lookup.prefetch_through.split(LOOKUP_SEP)
1768 for level, through_attr in enumerate(through_attrs):
1769 # Prepare main instances
1770 if not obj_list:
1771 break
1772
1773 prefetch_to = lookup.get_current_prefetch_to(level)
1774 if prefetch_to in done_queries:
1775 # Skip any prefetching, and any object preparation
1776 obj_list = done_queries[prefetch_to]
1777 continue
1778
1779 # Prepare objects:
1780 good_objects = True
1781 for obj in obj_list:
1782 # Since prefetching can re-use instances, it is possible to have
1783 # the same instance multiple times in obj_list, so obj might
1784 # already be prepared.
1785 if not hasattr(obj, "_prefetched_objects_cache"):
1786 try:
1787 obj._prefetched_objects_cache = {}
1788 except (AttributeError, TypeError):
1789 # Must be an immutable object from
1790 # values_list(flat=True), for example (TypeError) or
1791 # a QuerySet subclass that isn't returning Model
1792 # instances (AttributeError), either in Plain or a 3rd
1793 # party. prefetch_related() doesn't make sense, so quit.
1794 good_objects = False
1795 break
1796 if not good_objects:
1797 break
1798
1799 # Descend down tree
1800
1801 # We assume that objects retrieved are homogeneous (which is the premise
1802 # of prefetch_related), so what applies to first object applies to all.
1803 first_obj = obj_list[0]
1804 to_attr = lookup.get_current_to_attr(level)[0]
1805 prefetcher, descriptor, attr_found, is_fetched = get_prefetcher(
1806 first_obj, through_attr, to_attr
1807 )
1808
1809 if not attr_found:
1810 raise AttributeError(
1811 f"Cannot find '{through_attr}' on {first_obj.__class__.__name__} object, '{lookup.prefetch_through}' is an invalid "
1812 "parameter to prefetch_related()"
1813 )
1814
1815 if level == len(through_attrs) - 1 and prefetcher is None:
1816 # Last one, this *must* resolve to something that supports
1817 # prefetching, otherwise there is no point adding it and the
1818 # developer asking for it has made a mistake.
1819 raise ValueError(
1820 f"'{lookup.prefetch_through}' does not resolve to an item that supports "
1821 "prefetching - this is an invalid parameter to "
1822 "prefetch_related()."
1823 )
1824
1825 obj_to_fetch = None
1826 if prefetcher is not None:
1827 obj_to_fetch = [obj for obj in obj_list if not is_fetched(obj)]
1828
1829 if obj_to_fetch:
1830 obj_list, additional_lookups = prefetch_one_level(
1831 obj_to_fetch,
1832 prefetcher,
1833 lookup,
1834 level,
1835 )
1836 # We need to ensure we don't keep adding lookups from the
1837 # same relationships to stop infinite recursion. So, if we
1838 # are already on an automatically added lookup, don't add
1839 # the new lookups from relationships we've seen already.
1840 if not (
1841 prefetch_to in done_queries
1842 and lookup in auto_lookups
1843 and descriptor in followed_descriptors
1844 ):
1845 done_queries[prefetch_to] = obj_list
1846 new_lookups = normalize_prefetch_lookups(
1847 list(reversed(additional_lookups)),
1848 prefetch_to,
1849 )
1850 auto_lookups.update(new_lookups)
1851 all_lookups.extend(new_lookups)
1852 followed_descriptors.add(descriptor)
1853 else:
1854 # Either a singly related object that has already been fetched
1855 # (e.g. via select_related), or hopefully some other property
1856 # that doesn't support prefetching but needs to be traversed.
1857
1858 # We replace the current list of parent objects with the list
1859 # of related objects, filtering out empty or missing values so
1860 # that we can continue with nullable or reverse relations.
1861 new_obj_list = []
1862 for obj in obj_list:
1863 if through_attr in getattr(obj, "_prefetched_objects_cache", ()):
1864 # If related objects have been prefetched, use the
1865 # cache rather than the object's through_attr.
1866 new_obj = list(obj._prefetched_objects_cache.get(through_attr)) # ty: ignore[invalid-argument-type]
1867 else:
1868 try:
1869 new_obj = getattr(obj, through_attr)
1870 except ObjectDoesNotExist:
1871 continue
1872 if new_obj is None:
1873 continue
1874 # We special-case `list` rather than something more generic
1875 # like `Iterable` because we don't want to accidentally match
1876 # user models that define __iter__.
1877 if isinstance(new_obj, list):
1878 new_obj_list.extend(new_obj)
1879 else:
1880 new_obj_list.append(new_obj)
1881 obj_list = new_obj_list
1882
1883
1884def get_prefetcher(
1885 instance: Model, through_attr: str, to_attr: str
1886) -> tuple[Any, Any, bool, Callable[[Model], bool]]:
1887 """
1888 For the attribute 'through_attr' on the given instance, find
1889 an object that has a get_prefetch_queryset().
1890 Return a 4 tuple containing:
1891 (the object with get_prefetch_queryset (or None),
1892 the descriptor object representing this relationship (or None),
1893 a boolean that is False if the attribute was not found at all,
1894 a function that takes an instance and returns a boolean that is True if
1895 the attribute has already been fetched for that instance)
1896 """
1897
1898 def has_to_attr_attribute(instance: Model) -> bool:
1899 return hasattr(instance, to_attr)
1900
1901 prefetcher = None
1902 is_fetched: Callable[[Model], bool] = has_to_attr_attribute
1903
1904 # For singly related objects, we have to avoid getting the attribute
1905 # from the object, as this will trigger the query. So we first try
1906 # on the class, in order to get the descriptor object.
1907 rel_obj_descriptor = getattr(instance.__class__, through_attr, None)
1908 if rel_obj_descriptor is None:
1909 attr_found = hasattr(instance, through_attr)
1910 else:
1911 attr_found = True
1912 if rel_obj_descriptor:
1913 # singly related object, descriptor object has the
1914 # get_prefetch_queryset() method.
1915 if hasattr(rel_obj_descriptor, "get_prefetch_queryset"):
1916 prefetcher = rel_obj_descriptor
1917 is_fetched = rel_obj_descriptor.is_cached
1918 else:
1919 # descriptor doesn't support prefetching, so we go ahead and get
1920 # the attribute on the instance rather than the class to
1921 # support many related managers
1922 rel_obj = getattr(instance, through_attr)
1923 if hasattr(rel_obj, "get_prefetch_queryset"):
1924 prefetcher = rel_obj
1925 if through_attr != to_attr:
1926 # Special case cached_property instances because hasattr
1927 # triggers attribute computation and assignment.
1928 if isinstance(
1929 getattr(instance.__class__, to_attr, None), cached_property
1930 ):
1931
1932 def has_cached_property(instance: Model) -> bool:
1933 return to_attr in instance.__dict__
1934
1935 is_fetched = has_cached_property
1936 else:
1937
1938 def in_prefetched_cache(instance: Model) -> bool:
1939 return through_attr in instance._prefetched_objects_cache
1940
1941 is_fetched = in_prefetched_cache
1942 return prefetcher, rel_obj_descriptor, attr_found, is_fetched
1943
1944
1945def prefetch_one_level(
1946 instances: list[Model], prefetcher: Any, lookup: Prefetch, level: int
1947) -> tuple[list[Model], list[Prefetch]]:
1948 """
1949 Helper function for prefetch_related_objects().
1950
1951 Run prefetches on all instances using the prefetcher object,
1952 assigning results to relevant caches in instance.
1953
1954 Return the prefetched objects along with any additional prefetches that
1955 must be done due to prefetch_related lookups found from default managers.
1956 """
1957 # prefetcher must have a method get_prefetch_queryset() which takes a list
1958 # of instances, and returns a tuple:
1959
1960 # (queryset of instances of self.model that are related to passed in instances,
1961 # callable that gets value to be matched for returned instances,
1962 # callable that gets value to be matched for passed in instances,
1963 # boolean that is True for singly related objects,
1964 # cache or field name to assign to,
1965 # boolean that is True when the previous argument is a cache name vs a field name).
1966
1967 # The 'values to be matched' must be hashable as they will be used
1968 # in a dictionary.
1969
1970 (
1971 rel_qs,
1972 rel_obj_attr,
1973 instance_attr,
1974 single,
1975 cache_name,
1976 is_descriptor,
1977 ) = prefetcher.get_prefetch_queryset(instances, lookup.get_current_queryset(level))
1978 # We have to handle the possibility that the QuerySet we just got back
1979 # contains some prefetch_related lookups. We don't want to trigger the
1980 # prefetch_related functionality by evaluating the query. Rather, we need
1981 # to merge in the prefetch_related lookups.
1982 # Copy the lookups in case it is a Prefetch object which could be reused
1983 # later (happens in nested prefetch_related).
1984 additional_lookups = [
1985 copy.copy(additional_lookup)
1986 for additional_lookup in getattr(rel_qs, "_prefetch_related_lookups", ())
1987 ]
1988 if additional_lookups:
1989 # Don't need to clone because the queryset should have given us a fresh
1990 # instance, so we access an internal instead of using public interface
1991 # for performance reasons.
1992 rel_qs._prefetch_related_lookups = ()
1993
1994 all_related_objects = list(rel_qs)
1995
1996 rel_obj_cache = {}
1997 for rel_obj in all_related_objects:
1998 rel_attr_val = rel_obj_attr(rel_obj)
1999 rel_obj_cache.setdefault(rel_attr_val, []).append(rel_obj)
2000
2001 to_attr, as_attr = lookup.get_current_to_attr(level)
2002 # Make sure `to_attr` does not conflict with a field.
2003 if as_attr and instances:
2004 # We assume that objects retrieved are homogeneous (which is the premise
2005 # of prefetch_related), so what applies to first object applies to all.
2006 model = instances[0].__class__
2007 try:
2008 model._model_meta.get_field(to_attr)
2009 except FieldDoesNotExist:
2010 pass
2011 else:
2012 msg = "to_attr={} conflicts with a field on the {} model."
2013 raise ValueError(msg.format(to_attr, model.__name__))
2014
2015 # Whether or not we're prefetching the last part of the lookup.
2016 leaf = len(lookup.prefetch_through.split(LOOKUP_SEP)) - 1 == level
2017
2018 for obj in instances:
2019 instance_attr_val = instance_attr(obj)
2020 vals = rel_obj_cache.get(instance_attr_val, [])
2021
2022 if single:
2023 val = vals[0] if vals else None
2024 if as_attr:
2025 # A to_attr has been given for the prefetch.
2026 setattr(obj, to_attr, val)
2027 elif is_descriptor:
2028 # cache_name points to a field name in obj.
2029 # This field is a descriptor for a related object.
2030 setattr(obj, cache_name, val)
2031 else:
2032 # No to_attr has been given for this prefetch operation and the
2033 # cache_name does not point to a descriptor. Store the value of
2034 # the field in the object's field cache.
2035 obj._state.fields_cache[cache_name] = val
2036 else:
2037 if as_attr:
2038 setattr(obj, to_attr, vals)
2039 else:
2040 queryset = getattr(obj, to_attr)
2041 if leaf and lookup.queryset is not None:
2042 qs = queryset._apply_rel_filters(lookup.queryset)
2043 else:
2044 # Check if queryset is a QuerySet or a related manager
2045 # We need a QuerySet instance to cache the prefetched values
2046 if isinstance(queryset, QuerySet):
2047 # It's already a QuerySet, create a new instance
2048 qs = queryset.__class__.from_model(queryset.model)
2049 else:
2050 # It's a related manager, get its QuerySet
2051 # The manager's query property returns a properly filtered QuerySet
2052 qs = queryset.query
2053 qs._result_cache = vals
2054 # We don't want the individual qs doing prefetch_related now,
2055 # since we have merged this into the current work.
2056 qs._prefetch_done = True
2057 obj._prefetched_objects_cache[cache_name] = qs
2058 return all_related_objects, additional_lookups
2059
2060
2061class RelatedPopulator:
2062 """
2063 RelatedPopulator is used for select_related() object instantiation.
2064
2065 The idea is that each select_related() model will be populated by a
2066 different RelatedPopulator instance. The RelatedPopulator instances get
2067 klass_info and select (computed in SQLCompiler) plus the used db as
2068 input for initialization. That data is used to compute which columns
2069 to use, how to instantiate the model, and how to populate the links
2070 between the objects.
2071
2072 The actual creation of the objects is done in populate() method. This
2073 method gets row and from_obj as input and populates the select_related()
2074 model instance.
2075 """
2076
2077 def __init__(self, klass_info: dict[str, Any], select: list[Any]):
2078 # Pre-compute needed attributes. The attributes are:
2079 # - model_cls: the possibly deferred model class to instantiate
2080 # - either:
2081 # - cols_start, cols_end: usually the columns in the row are
2082 # in the same order model_cls.__init__ expects them, so we
2083 # can instantiate by model_cls(*row[cols_start:cols_end])
2084 # - reorder_for_init: When select_related descends to a child
2085 # class, then we want to reuse the already selected parent
2086 # data. However, in this case the parent data isn't necessarily
2087 # in the same order that Model.__init__ expects it to be, so
2088 # we have to reorder the parent data. The reorder_for_init
2089 # attribute contains a function used to reorder the field data
2090 # in the order __init__ expects it.
2091 # - id_idx: the index of the primary key field in the reordered
2092 # model data. Used to check if a related object exists at all.
2093 # - init_list: the field names fetched from the database. For
2094 # deferred models this isn't the same as all names of the
2095 # model's fields.
2096 # - related_populators: a list of RelatedPopulator instances if
2097 # select_related() descends to related models from this model.
2098 # - local_setter, remote_setter: Methods to set cached values on
2099 # the object being populated and on the remote object. Usually
2100 # these are Field.set_cached_value() methods.
2101 select_fields = klass_info["select_fields"]
2102
2103 self.cols_start = select_fields[0]
2104 self.cols_end = select_fields[-1] + 1
2105 self.init_list = [
2106 f[0].target.name for f in select[self.cols_start : self.cols_end]
2107 ]
2108 self.reorder_for_init = None
2109
2110 self.model_cls = klass_info["model"]
2111 self.id_idx = self.init_list.index("id")
2112 self.related_populators = get_related_populators(klass_info, select)
2113 self.local_setter = klass_info["local_setter"]
2114 self.remote_setter = klass_info["remote_setter"]
2115
2116 def populate(self, row: tuple[Any, ...], from_obj: Model) -> None:
2117 if self.reorder_for_init:
2118 obj_data = self.reorder_for_init(row)
2119 else:
2120 obj_data = row[self.cols_start : self.cols_end]
2121 if obj_data[self.id_idx] is None:
2122 obj = None
2123 else:
2124 obj = self.model_cls.from_db(self.init_list, obj_data)
2125 for rel_iter in self.related_populators:
2126 rel_iter.populate(row, obj)
2127 self.local_setter(from_obj, obj)
2128 if obj is not None:
2129 self.remote_setter(obj, from_obj)
2130
2131
2132def get_related_populators(
2133 klass_info: dict[str, Any], select: list[Any]
2134) -> list[RelatedPopulator]:
2135 iterators = []
2136 related_klass_infos = klass_info.get("related_klass_infos", [])
2137 for rel_klass_info in related_klass_infos:
2138 rel_cls = RelatedPopulator(rel_klass_info, select)
2139 iterators.append(rel_cls)
2140 return iterators