def ordering_queryset(queryset, ordering):
order_items_qs = OrderItem.objects.filter(
product_id=OuterRef("id")
).values('product_id').annotate(sales_count=Sum("amount")).values("sales_count")
"""쿼리셋 정렬 함수"""
orderings = {
"popularity": queryset.annotate(num_wishlists=Count("wish_lists")).order_by(
"-num_wishlists"
),
"stars": queryset.annotate(stars=Avg("product_reviews__star")).order_by("-stars"),
"expensive": queryset.order_by("-price"),
"cheap": queryset.order_by("price"),
"sales": queryset.annotate(
sales_count=Subquery(order_items_qs, output_field=models.IntegerField())
).order_by("-sales_count"),
}
return orderings[ordering]
order_items_qs = OrderItem.objects.filter(
product_id=OuterRef("id")
).values('product_id').annotate(sales_count=Sum("amount")).values("sales_count")
"sales": queryset.annotate(
sales_count=Subquery(order_items_qs, output_field=models.IntegerField())
).order_by("-sales_count"),
}
1) OuterRef :OuterRef 클래스는 Django의 F 클래스를 상속받고, F 클래스는 쿼리셋의 필터 조건에서 데이터베이스 필드를 참조할 때 사용
OuterRef 클래스
class OuterRef(F):
contains_aggregate = False
def resolve_expression(self, *args, **kwargs):
if isinstance(self.name, self.__class__):
return self.name
return ResolvedOuterRef(self.name)
def relabeled_clone(self, relabels):
return self
isinstanceof가 아닐때 실행되는 ResolvedOuterRef 클래스
class ResolvedOuterRef(F):
"""
An object that contains a reference to an outer query.
In this case, the reference to the outer query has been resolved because
the inner query has been used as a subquery.
"""
contains_aggregate = False
contains_over_clause = False
def as_sql(self, *args, **kwargs):
raise ValueError(
"This queryset contains a reference to an outer query and may "
"only be used in a subquery."
)
def resolve_expression(self, *args, **kwargs):
col = super().resolve_expression(*args, **kwargs)
if col.contains_over_clause:
raise NotSupportedError(
f"Referencing outer query window expression is not supported: "
f"{self.name}."
)
# FIXME: Rename possibly_multivalued to multivalued and fix detection
# for non-multivalued JOINs (e.g. foreign key fields). This should take
# into account only many-to-many and one-to-many relationships.
col.possibly_multivalued = LOOKUP_SEP in self.name
return col
def relabeled_clone(self, relabels):
return self
def get_group_by_cols(self):
return []
OuterRef와 ResolvedOuterRef에서 상속하는 F클래스
@deconstructible(path="django.db.models.F")
class F(Combinable):
"""An object capable of resolving references to existing query objects."""
def __init__(self, name):
"""
Arguments:
* name: the name of the field this expression references
"""
self.name = name
def __repr__(self):
return "{}({})".format(self.__class__.__name__, self.name)
def resolve_expression(
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
):
return query.resolve_ref(self.name, allow_joins, reuse, summarize)
def replace_expressions(self, replacements):
return replacements.get(self, self)
def asc(self, **kwargs):
return OrderBy(self, **kwargs)
def desc(self, **kwargs):
return OrderBy(self, descending=True, **kwargs)
def __eq__(self, other):
return self.__class__ == other.__class__ and self.name == other.name
def __hash__(self):
return hash(self.name)
def copy(self):
return copy.copy(self)
2) Subquery 클래스
class Subquery(BaseExpression, Combinable):
"""
An explicit subquery. It may contain OuterRef() references to the outer
query which will be resolved when it is applied to that query.
"""
template = "(%(subquery)s)"
contains_aggregate = False
empty_result_set_value = None
subquery = True
def __init__(self, queryset, output_field=None, **extra):
# Allow the usage of both QuerySet and sql.Query objects.
self.query = getattr(queryset, "query", queryset).clone()
self.query.subquery = True
self.extra = extra
super().__init__(output_field)
def get_source_expressions(self):
return [self.query]
def set_source_expressions(self, exprs):
self.query = exprs[0]
def _resolve_output_field(self):
return self.query.output_field
def copy(self):
clone = super().copy()
clone.query = clone.query.clone()
return clone
@property
def external_aliases(self):
return self.query.external_aliases
def get_external_cols(self):
return self.query.get_external_cols()
def as_sql(self, compiler, connection, template=None, **extra_context):
connection.ops.check_expression_support(self)
template_params = {**self.extra, **extra_context}
subquery_sql, sql_params = self.query.as_sql(compiler, connection)
template_params["subquery"] = subquery_sql[1:-1]
template = template or template_params.get("template", self.template)
sql = template % template_params
return sql, sql_params
def get_group_by_cols(self):
return self.query.get_group_by_cols(wrapper=self)
따라서,
"sales": queryset.annotate(
sales_count=Subquery(order_items_qs, output_field=models.IntegerField())
).order_by("-sales_count"),
1) queryset.annotate는 전 단계에서 그룹화한 주문 상품들의 수량(amount)을 합산하여 새로운 필드인 sales_count에 저장한다는 뜻
2) 그런데 order_items_qs와 output_filed의 관계는 BaseExpression, Combinable 기본표현과 융합 가능한 객체(?)의 관계임,
3) ("-sales_count") 에 의해서 정렬되는데, 여기서 order_by는 queryset의 내장 메소드이고 말 그대로 정렬된다는 뜻임
def order_by(self, *field_names):
"""Return a new QuerySet instance with the ordering changed."""
if self.query.is_sliced:
raise TypeError("Cannot reorder a query once a slice has been taken.")
obj = self._chain()
obj.query.clear_ordering(force=True, clear_default=False)
obj.query.add_ordering(*field_names)
return obj
4) 따라서 판매수에 의해서 정렬된 Subquery의 내용이 sales_count라는 새로운 변수에 저장이 됨 , 그 Subquery의 내용은 order_item_qs이며, 쿼리의 외부 필드에서 합쳐진 내용
5) 그렇다면 order_item_qs란?
users의 OrderItem의 객체에서 상품 id에 의해 필터링된 product_id라는 값의 합들을 sales_count에 저장함
여기서 OuterRef는 F클래스를 상속받은 데이터베이스 필드를 참조하는데, F클래스 자리에 id값이 들어갔으니까, id값을 가진 데이터베이스 필드를 외부참조해오는 것임 또한 그 값을 sales_count라는 변수에 OrderItem에서 product_id 수량의 총합(Sum)을 저장하기로 한것