in kitsune/wiki/facets.py [0:0]
def _documents_for(user, locale, topics=None, products=None):
"""Returns a list of articles that apply to passed in locale, topics and products."""
cache_key = _cache_key(locale, topics, products)
if not user.is_authenticated:
# For anonymous users, first check the cache.
documents_cache_key = f"documents_for_v2:{cache_key}"
documents = cache.get(documents_cache_key)
if documents is not None:
return documents
qs = Document.objects.visible(
user,
locale=locale,
is_archived=False,
current_revision__isnull=False,
category__in=settings.IA_DEFAULT_CATEGORIES,
)
# speed up query by removing any ordering, since we're doing it in python:
qs = qs.select_related("current_revision", "parent").order_by()
if topics:
topic_ids = [t.id for t in topics]
# For parent documents: include if they have the requested topics
# For translations: include ONLY if their parent has the requested topics,
# completely ignoring any topics directly assigned to the translation
qs = qs.filter(
# Either this is a parent document with matching topics
(Q(parent__isnull=True) & Q(topics__in=topic_ids))
# OR this is a translation and its parent has matching topics
| (Q(parent__isnull=False) & Q(parent__topics__in=topic_ids))
)
for product in products or []:
# we need to filter against parent products for localized articles
qs = qs.filter(Q(products=product) | Q(parent__products=product))
qs = qs.distinct()
votes_cache_key = f"votes_for:{cache_key}"
votes_dict = cache.get(votes_cache_key)
if votes_dict is None:
# NOTE: It's important to use "created__range" rather than "created__gt"
# with Postgres, otherwise it won't use the index on the "created"
# field, and the "HelpfulVote" query will be massively slower.
votes_query = (
HelpfulVote.objects.filter(
revision_id__in=qs.values_list("current_revision_id", flat=True),
created__range=(Now() - timedelta(days=30), Now()),
helpful=True,
)
.values("revision_id")
.annotate(count=Count("*"))
.values("revision_id", "count")
)
votes_dict = {row["revision_id"]: row["count"] for row in votes_query}
# the votes query is rather expensive, and only used for ordering,
# so we can cache it rather aggressively
cache.set(votes_cache_key, votes_dict, timeout=settings.CACHE_LONG_TIMEOUT)
# Annotate each of the documents with its string of product titles. This must
# be a sub-query in order to free itself from the product filter(s) above.
qs = qs.annotate(
product_titles=Subquery(
Document.objects.filter(pk=OuterRef("pk"))
.annotate(
product_titles=Case(
When(
parent__isnull=False,
then=StringAgg(
"parent__products__title",
delimiter=", ",
ordering="parent__products__title",
),
),
default=StringAgg(
"products__title", delimiter=", ", ordering="products__title"
),
),
)
.values("product_titles")
)
)
doc_dicts = []
for d in qs:
doc_dicts.append(
dict(
id=d.id,
document_title=d.title,
url=d.get_absolute_url(),
document_parent_id=d.parent_id,
created=d.current_revision.created,
product_titles=d.product_titles,
document_summary=d.current_revision.summary,
display_order=d.original.display_order,
helpful_votes=votes_dict.get(d.current_revision_id, 0),
)
)
# sort the results by ascending display_order and descending votes
doc_dicts.sort(key=lambda x: (x["display_order"], -x["helpful_votes"]))
if not user.is_authenticated:
cache.set(documents_cache_key, doc_dicts)
return doc_dicts