Browse Source

WIP Added a draft for how a more advanced search or customizable feed might be implemented.

Related to #359
merge-requests/149/head
Daniel Woznicki 2 years ago
parent
commit
caedb9368a
  1. 42
      tildes/tildes/models/topic/topic_query.py
  2. 1
      tildes/tildes/request_methods.py
  3. 1
      tildes/tildes/routes.py
  4. 8
      tildes/tildes/schemas/listing.py
  5. 97
      tildes/tildes/views/topic.py

42
tildes/tildes/models/topic/topic_query.py

@ -193,6 +193,14 @@ class TopicQuery(PaginatedQuery):
return self.filter(Topic.group_id.in_(group_ids)) # type: ignore return self.filter(Topic.group_id.in_(group_ids)) # type: ignore
def has_any_groups(self, group_ids: list[int]) -> TopicQuery:
"""Restrict the topics to those with any of the given group IDs (generative)."""
return self.filter(Topic.group_id.in_(group_ids)) # type: ignore
def not_has_any_groups(self, group_ids: list[int]) -> TopicQuery:
"""Restrict to topics those given group IDs (generative)."""
return self.filter(~Topic.group_id.in_(group_ids)) # type: ignore
def inside_time_period(self, period: SimpleHoursPeriod) -> TopicQuery: def inside_time_period(self, period: SimpleHoursPeriod) -> TopicQuery:
"""Restrict the topics to inside a time period (generative).""" """Restrict the topics to inside a time period (generative)."""
# if the time period is too long, this will crash by creating a datetime outside # if the time period is too long, this will crash by creating a datetime outside
@ -216,6 +224,40 @@ class TopicQuery(PaginatedQuery):
# pylint: disable=protected-access # pylint: disable=protected-access
return self.filter(Topic.tags.lquery(query)) # type: ignore return self.filter(Topic.tags.lquery(query)) # type: ignore
def has_any_tags(self, tags: list[str]) -> TopicQuery:
"""Restrict the topics to ones with any of the specified tags (generative).
Tags can be any valid ltree, including wildcards, e.g. 'ask.*'.
"""
return self.filter(Topic.tags.lquery(tags)) # type: ignore
def has_all_tags(self, tags: list[str]) -> TopicQuery:
"""Restrict the topics to ones with all of the specified tags (generative).
Tags can be any valid ltree, including wildcards, e.g. 'ask.*'.
"""
tag_queries = [Topic.tags.lquery(tag) for tag in tags] # type: ignore
# For example,
# ["ask.*", "japanese"]
# produces (rough) SQL
# (topics.tags ~ 'ask.*' AND topic.tags ~ 'japanese')
return self.filter(and_(*tag_queries))
def not_has_any_tags(self, tags: list[str]) -> TopicQuery:
"""Restrict the topics to ones without any of the specified tags (generative).
Tags can be any valid ltree, including wildcards, e.g. 'ask.*'.
"""
return self.filter(~Topic.tags.lquery(tags)) # type: ignore
def posted_by_any_users(self, user_ids: list[int]) -> TopicQuery:
"""Restrict the topics to ones posted by the specified user (generative)."""
return self.filter(Topic.user_id.in_(user_ids)) # type: ignore
def search(self, query: str) -> TopicQuery: def search(self, query: str) -> TopicQuery:
"""Restrict the topics to ones that match a search query (generative).""" """Restrict the topics to ones that match a search query (generative)."""
# Replace "." with space, since tags are stored as space-separated strings # Replace "." with space, since tags are stored as space-separated strings

1
tildes/tildes/request_methods.py

@ -124,6 +124,7 @@ def current_listing_base_url(
The `query` argument allows adding query variables to the generated url. The `query` argument allows adding query variables to the generated url.
""" """
base_vars_by_route: dict[str, tuple[str, ...]] = { base_vars_by_route: dict[str, tuple[str, ...]] = {
"advanced_search": ("order", "period", "per_page"),
"bookmarks": ("per_page", "type"), "bookmarks": ("per_page", "type"),
"group": ("order", "period", "per_page", "tag", "unfiltered"), "group": ("order", "period", "per_page", "tag", "unfiltered"),
"group_search": ("order", "period", "per_page", "q"), "group_search": ("order", "period", "per_page", "q"),

1
tildes/tildes/routes.py

@ -23,6 +23,7 @@ def includeme(config: Configurator) -> None:
config.add_route("home_rss", "/topics.rss") config.add_route("home_rss", "/topics.rss")
config.add_route("search", "/search") config.add_route("search", "/search")
config.add_route("advanced_search", "/advanced_search")
config.add_route("financials", "/financials") config.add_route("financials", "/financials")

8
tildes/tildes/schemas/listing.py

@ -6,7 +6,7 @@
from typing import Any from typing import Any
from marshmallow import pre_load, Schema, validates_schema, ValidationError from marshmallow import pre_load, Schema, validates_schema, ValidationError
from marshmallow.fields import Boolean, Integer
from marshmallow.fields import Boolean, Integer, String, List
from marshmallow.validate import Range from marshmallow.validate import Range
from tildes.enums import TopicSortOption from tildes.enums import TopicSortOption
@ -36,6 +36,12 @@ class TopicListingSchema(PaginatedListingSchema):
tag = Ltree(missing=None) tag = Ltree(missing=None)
unfiltered = Boolean(missing=False) unfiltered = Boolean(missing=False)
rank_start = Integer(data_key="n", validate=Range(min=1), missing=None) rank_start = Integer(data_key="n", validate=Range(min=1), missing=None)
group_ids = List(Integer(), missing=None)
not_group_ids = List(Integer(), missing=None)
tags = List(String(), missing=None)
all_tags = List(String(), missing=None)
not_tags = List(String(), missing=None)
user_ids = List(Integer(), missing=None)
@pre_load @pre_load
def reset_rank_start_on_first_page( def reset_rank_start_on_first_page(

97
tildes/tildes/views/topic.py

@ -157,7 +157,7 @@ def get_group_topics( # noqa
rank_start: Optional[int], rank_start: Optional[int],
tag: Optional[Ltree], tag: Optional[Ltree],
unfiltered: bool, unfiltered: bool,
**kwargs: Any
**kwargs: Any,
) -> dict: ) -> dict:
"""Get a listing of topics in the group.""" """Get a listing of topics in the group."""
# period needs special treatment so we can distinguish between missing and None # period needs special treatment so we can distinguish between missing and None
@ -336,7 +336,7 @@ def get_search(
before: Optional[str], before: Optional[str],
per_page: int, per_page: int,
search: str, search: str,
**kwargs: Any
**kwargs: Any,
) -> dict: ) -> dict:
"""Get a list of search results.""" """Get a list of search results."""
# period needs special treatment so we can distinguish between missing and None # period needs special treatment so we can distinguish between missing and None
@ -394,6 +394,99 @@ def get_search(
} }
@view_config(route_name="advanced_search", renderer="search.jinja2")
@use_kwargs(
TopicListingSchema(
only=(
"after",
"before",
"order",
"per_page",
"period",
"group_ids",
"not_group_ids",
"tags",
"all_tags",
"not_tags",
"user_ids",
)
)
)
def get_advanced_search( # noqa
request: Request,
group_ids: Optional[list[int]],
not_group_ids: Optional[list[int]],
tags: Optional[list[str]],
all_tags: Optional[list[str]],
not_tags: Optional[list[str]],
user_ids: Optional[list[int]],
order: Optional[TopicSortOption],
after: Optional[str],
before: Optional[str],
per_page: int,
**kwargs: Any,
) -> dict:
"""Get a list of advanced search results."""
# period needs special treatment so we can distinguish between missing and None
period = kwargs.get("period", missing)
if not order:
order = TopicSortOption.NEW
if period is missing:
period = None
query = request.query(Topic).join_all_relationships().apply_sort_option(order)
if group_ids:
query = query.has_any_groups(group_ids)
if not_group_ids:
query = query.not_has_any_groups(not_group_ids)
if tags:
query = query.has_any_tags(tags)
if all_tags:
query = query.has_all_tags(all_tags)
if not_tags:
query = query.not_has_any_tags(not_tags)
if user_ids:
query = query.posted_by_any_users(user_ids)
# restrict the time period, if not set to "all time"
if period:
query = query.inside_time_period(period)
# apply before/after pagination restrictions if relevant
if before:
query = query.before_id36(before)
if after:
query = query.after_id36(after)
topics = query.get_page(per_page)
period_options = [SimpleHoursPeriod(hours) for hours in (1, 12, 24, 72)]
# add the current period to the bottom of the dropdown if it's not one of the
# "standard" ones
if period and period not in period_options:
period_options.append(period)
return {
"search": "",
"topics": topics,
"group": None,
"order": order,
"order_options": TopicSortOption,
"period": period,
"period_options": period_options,
}
@view_config( @view_config(
route_name="new_topic", renderer="new_topic.jinja2", permission="topic.post" route_name="new_topic", renderer="new_topic.jinja2", permission="topic.post"
) )

Loading…
Cancel
Save