Browse Source

Add user-page sorting

merge-requests/68/head
Jedi Burrell 6 years ago
committed by Deimos
parent
commit
45968d76ed
  1. 4
      tildes/tests/test_comment.py
  2. 4
      tildes/tildes/__init__.py
  3. 22
      tildes/tildes/enums.py
  4. 14
      tildes/tildes/models/comment/comment_query.py
  5. 16
      tildes/tildes/models/comment/comment_tree.py
  6. 24
      tildes/tildes/templates/user.jinja2
  7. 8
      tildes/tildes/views/topic.py
  8. 31
      tildes/tildes/views/user.py

4
tildes/tests/test_comment.py

@ -6,7 +6,7 @@ from datetime import timedelta
from freezegun import freeze_time from freezegun import freeze_time
from pyramid.security import Authenticated, Everyone, principals_allowed_by_permission from pyramid.security import Authenticated, Everyone, principals_allowed_by_permission
from tildes.enums import CommentSortOption
from tildes.enums import CommentTreeSortOption
from tildes.lib.datetime import utc_now from tildes.lib.datetime import utc_now
from tildes.models.comment import Comment, CommentTree, EDIT_GRACE_PERIOD from tildes.models.comment import Comment, CommentTree, EDIT_GRACE_PERIOD
from tildes.schemas.comment import CommentSchema from tildes.schemas.comment import CommentSchema
@ -134,7 +134,7 @@ def test_comment_tree(db, topic, session_user):
"""Ensure that building and pruning a comment tree works.""" """Ensure that building and pruning a comment tree works."""
all_comments = [] all_comments = []
sort = CommentSortOption.POSTED
sort = CommentTreeSortOption.POSTED
# add two root comments # add two root comments
root = Comment(topic, session_user, "root") root = Comment(topic, session_user, "root")

4
tildes/tildes/__init__.py

@ -139,7 +139,7 @@ def current_listing_base_url(
"group": ("order", "period", "per_page", "tag", "unfiltered"), "group": ("order", "period", "per_page", "tag", "unfiltered"),
"home": ("order", "period", "per_page", "tag", "unfiltered"), "home": ("order", "period", "per_page", "tag", "unfiltered"),
"search": ("order", "period", "per_page", "q"), "search": ("order", "period", "per_page", "q"),
"user": ("per_page", "type"),
"user": ("order", "per_page", "type"),
} }
try: try:
@ -172,7 +172,7 @@ def current_listing_normal_url(
"home": ("order", "period", "per_page"), "home": ("order", "period", "per_page"),
"notifications": ("per_page",), "notifications": ("per_page",),
"search": ("order", "period", "per_page", "q"), "search": ("order", "period", "per_page", "q"),
"user": ("per_page",),
"user": ("order", "per_page"),
} }
try: try:

22
tildes/tildes/enums.py

@ -17,7 +17,27 @@ class CommentNotificationType(enum.Enum):
class CommentSortOption(enum.Enum): class CommentSortOption(enum.Enum):
"""Enum for the different methods comments can be sorted by."""
"""Enum for the different methods out-of-context comments can be sorted by."""
VOTES = enum.auto()
NEW = enum.auto()
@property
def descending_description(self) -> str:
"""Describe this sort option when used in a "descending" order.
For example, the "votes" sort has a description of "most votes", since using
that sort in descending order means that topics with the most votes will be
listed first.
"""
if self.name == "NEW":
return "newest"
return "most {}".format(self.name.lower())
class CommentTreeSortOption(enum.Enum):
"""Enum for the different methods comment trees can be sorted by."""
VOTES = enum.auto() VOTES = enum.auto()
NEWEST = enum.auto() NEWEST = enum.auto()

14
tildes/tildes/models/comment/comment_query.py

@ -8,6 +8,7 @@ from typing import Any
from pyramid.request import Request from pyramid.request import Request
from sqlalchemy.sql.expression import and_ from sqlalchemy.sql.expression import and_
from tildes.enums import CommentSortOption
from tildes.models.pagination import PaginatedQuery from tildes.models.pagination import PaginatedQuery
from .comment import Comment from .comment import Comment
from .comment_bookmark import CommentBookmark from .comment_bookmark import CommentBookmark
@ -78,6 +79,19 @@ class CommentQuery(PaginatedQuery):
return comment return comment
def apply_sort_option(
self, sort: CommentSortOption, desc: bool = True
) -> "CommentQuery":
"""Apply a CommentSortOption sorting method (generative)."""
if sort == CommentSortOption.VOTES:
self._sort_column = Comment.num_votes
elif sort == CommentSortOption.NEW:
self._sort_column = Comment.created_time
self.sort_desc = desc
return self
def only_bookmarked(self) -> "CommentQuery": def only_bookmarked(self) -> "CommentQuery":
"""Restrict the comments to ones that the user has bookmarked (generative).""" """Restrict the comments to ones that the user has bookmarked (generative)."""
self._only_bookmarked = True self._only_bookmarked = True

16
tildes/tildes/models/comment/comment_tree.py

@ -9,7 +9,7 @@ from typing import Iterator, List, Optional, Sequence, Tuple
from prometheus_client import Histogram from prometheus_client import Histogram
from wrapt import ObjectProxy from wrapt import ObjectProxy
from tildes.enums import CommentSortOption
from tildes.enums import CommentTreeSortOption
from tildes.metrics import get_histogram from tildes.metrics import get_histogram
from tildes.models.user import User from tildes.models.user import User
from .comment import Comment from .comment import Comment
@ -21,7 +21,7 @@ class CommentTree:
def __init__( def __init__(
self, self,
comments: Sequence[Comment], comments: Sequence[Comment],
sort: CommentSortOption,
sort: CommentTreeSortOption,
viewer: Optional[User] = None, viewer: Optional[User] = None,
): ):
"""Create a sorted CommentTree from a flat list of Comments.""" """Create a sorted CommentTree from a flat list of Comments."""
@ -44,7 +44,7 @@ class CommentTree:
# no need to sort again if that's the desired sorting. Note also that because # no need to sort again if that's the desired sorting. Note also that because
# _sort_tree() uses sorted() which is a stable sort, this means that the # _sort_tree() uses sorted() which is a stable sort, this means that the
# "secondary sort" will always be by posting time as well. # "secondary sort" will always be by posting time as well.
if self.tree and sort != CommentSortOption.POSTED:
if self.tree and sort != CommentTreeSortOption.POSTED:
with self._sorting_histogram().time(): with self._sorting_histogram().time():
self.tree = self._sort_tree(self.tree, self.sort) self.tree = self._sort_tree(self.tree, self.sort)
@ -85,20 +85,20 @@ class CommentTree:
self.tree.append(comment) self.tree.append(comment)
@staticmethod @staticmethod
def _sort_tree(tree: List[Comment], sort: CommentSortOption) -> List[Comment]:
def _sort_tree(tree: List[Comment], sort: CommentTreeSortOption) -> List[Comment]:
"""Sort the tree by the desired ordering (recursively). """Sort the tree by the desired ordering (recursively).
Because Python's sorted() function is stable, the ordering of any comments that Because Python's sorted() function is stable, the ordering of any comments that
compare equal on the sorting method will be the same as the order that they were compare equal on the sorting method will be the same as the order that they were
originally in when passed to this function. originally in when passed to this function.
""" """
if sort == CommentSortOption.NEWEST:
if sort == CommentTreeSortOption.NEWEST:
tree = sorted(tree, key=lambda c: c.created_time, reverse=True) tree = sorted(tree, key=lambda c: c.created_time, reverse=True)
elif sort == CommentSortOption.POSTED:
elif sort == CommentTreeSortOption.POSTED:
tree = sorted(tree, key=lambda c: c.created_time) tree = sorted(tree, key=lambda c: c.created_time)
elif sort == CommentSortOption.VOTES:
elif sort == CommentTreeSortOption.VOTES:
tree = sorted(tree, key=lambda c: c.num_votes, reverse=True) tree = sorted(tree, key=lambda c: c.num_votes, reverse=True)
elif sort == CommentSortOption.RELEVANCE:
elif sort == CommentTreeSortOption.RELEVANCE:
tree = sorted(tree, key=lambda c: c.relevance_sorting_value, reverse=True) tree = sorted(tree, key=lambda c: c.relevance_sorting_value, reverse=True)
for comment in tree: for comment in tree:

24
tildes/tildes/templates/user.jinja2

@ -45,6 +45,30 @@
<a href="{{ request.current_listing_normal_url({'type': 'comment'}) }}">Comments</a> <a href="{{ request.current_listing_normal_url({'type': 'comment'}) }}">Comments</a>
</li> </li>
</menu> </menu>
{% if order_options %}
<form class="form-listing-options" method="get">
<input type="hidden" name="type" value="{{ post_type }}">
<div class="form-group">
<label for="order">sorted by</label>
<select id="order" name="order" class="form-select" data-js-autosubmit-on-change>
{% for option in order_options %}
<option value="{{ option.name.lower() }}"
{% if option == order %}
selected
{% endif %}
>{{ option.descending_description }}</option>
{% endfor %}
</select>
{# add a submit button for people with js disabled so this is still usable #}
<noscript>
<button type="submit" class="btn btn-primary btn-sm">OK</button>
</noscript>
</div>
</form>
{% endif %}
</div> </div>
{% endif %} {% endif %}

8
tildes/tildes/views/topic.py

@ -20,7 +20,7 @@ from zope.sqlalchemy import mark_changed
from tildes.enums import ( from tildes.enums import (
CommentLabelOption, CommentLabelOption,
CommentNotificationType, CommentNotificationType,
CommentSortOption,
CommentTreeSortOption,
LogEventType, LogEventType,
TopicSortOption, TopicSortOption,
) )
@ -255,8 +255,8 @@ def get_new_topic_form(request: Request) -> dict:
@view_config(route_name="topic", renderer="topic.jinja2") @view_config(route_name="topic", renderer="topic.jinja2")
@use_kwargs({"comment_order": Enum(CommentSortOption, missing="relevance")})
def get_topic(request: Request, comment_order: CommentSortOption) -> dict:
@use_kwargs({"comment_order": Enum(CommentTreeSortOption, missing="relevance")})
def get_topic(request: Request, comment_order: CommentTreeSortOption) -> dict:
"""View a single topic.""" """View a single topic."""
topic = request.context topic = request.context
@ -310,7 +310,7 @@ def get_topic(request: Request, comment_order: CommentSortOption) -> dict:
"log": log, "log": log,
"comments": tree, "comments": tree,
"comment_order": comment_order, "comment_order": comment_order,
"comment_order_options": CommentSortOption,
"comment_order_options": CommentTreeSortOption,
"comment_label_options": CommentLabelOption, "comment_label_options": CommentLabelOption,
} }

31
tildes/tildes/views/user.py

@ -5,12 +5,13 @@
from typing import List, Optional, Type, Union from typing import List, Optional, Type, Union
from marshmallow.fields import String
from pyramid.request import Request from pyramid.request import Request
from pyramid.view import view_config from pyramid.view import view_config
from sqlalchemy.sql.expression import desc from sqlalchemy.sql.expression import desc
from webargs.pyramidparser import use_kwargs from webargs.pyramidparser import use_kwargs
from tildes.enums import CommentLabelOption
from tildes.enums import CommentLabelOption, CommentSortOption, TopicSortOption
from tildes.models.comment import Comment from tildes.models.comment import Comment
from tildes.models.pagination import MixedPaginatedResults from tildes.models.pagination import MixedPaginatedResults
from tildes.models.topic import Topic from tildes.models.topic import Topic
@ -19,15 +20,21 @@ from tildes.schemas.fields import PostType
from tildes.schemas.listing import MixedListingSchema from tildes.schemas.listing import MixedListingSchema
@view_config(route_name="user", renderer="user.jinja2")
@view_config(route_name="user", renderer="user.jinja2") # noqa
@use_kwargs(MixedListingSchema()) @use_kwargs(MixedListingSchema())
@use_kwargs({"post_type": PostType(load_from="type")})
@use_kwargs(
{
"post_type": PostType(load_from="type"),
"order_name": String(load_from="order", missing="new"),
} # noqa
)
def get_user( def get_user(
request: Request, request: Request,
after: Optional[str], after: Optional[str],
before: Optional[str], before: Optional[str],
per_page: int, per_page: int,
anchor_type: Optional[str], anchor_type: Optional[str],
order_name: str,
post_type: Optional[str] = None, post_type: Optional[str] = None,
) -> dict: ) -> dict:
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
@ -44,14 +51,27 @@ def get_user(
per_page = 20 per_page = 20
types_to_query: List[Union[Type[Topic], Type[Comment]]] types_to_query: List[Union[Type[Topic], Type[Comment]]]
order_options: Optional[Union[Type[TopicSortOption], Type[CommentSortOption]]]
if post_type == "topic": if post_type == "topic":
types_to_query = [Topic] types_to_query = [Topic]
order_options = TopicSortOption
elif post_type == "comment": elif post_type == "comment":
types_to_query = [Comment] types_to_query = [Comment]
order_options = CommentSortOption
else: else:
# the order here is important so items are in the right order when the results # the order here is important so items are in the right order when the results
# are merged at the end (we want topics to come first when times match) # are merged at the end (we want topics to come first when times match)
types_to_query = [Comment, Topic] types_to_query = [Comment, Topic]
order_options = None
order = None
if order_options:
# try to get the specified order, but fall back to "newest"
try:
order = order_options[order_name.upper()]
except KeyError:
order = order_options["NEW"]
result_sets = [] result_sets = []
for type_to_query in types_to_query: for type_to_query in types_to_query:
@ -66,6 +86,9 @@ def get_user(
if after: if after:
query = query.after_id36(after) query = query.after_id36(after)
if order:
query = query.apply_sort_option(order)
query = query.join_all_relationships() query = query.join_all_relationships()
# include removed posts if the user's looking at their own page or is an admin # include removed posts if the user's looking at their own page or is an admin
@ -83,6 +106,8 @@ def get_user(
"user": user, "user": user,
"posts": posts, "posts": posts,
"post_type": post_type, "post_type": post_type,
"order": order,
"order_options": order_options,
"comment_label_options": CommentLabelOption, "comment_label_options": CommentLabelOption,
} }

Loading…
Cancel
Save