Browse Source

Paginate previously-read notifications page

This has said that pagination is "coming soon" for quite a while. This
is a bit messy in a few ways, but should do the job for now.
PaginatedQuery/PaginatedResults might be good to refactor a little in
the future to make this kind of thing simpler.
merge-requests/40/head
Deimos 6 years ago
parent
commit
cd788d3018
  1. 1
      tildes/tildes/__init__.py
  2. 41
      tildes/tildes/models/comment/comment_notification_query.py
  3. 23
      tildes/tildes/models/pagination.py
  4. 5
      tildes/tildes/templates/notifications.jinja2
  5. 16
      tildes/tildes/templates/notifications_unread.jinja2
  6. 21
      tildes/tildes/views/notifications.py

1
tildes/tildes/__init__.py

@ -179,6 +179,7 @@ def current_listing_normal_url(
normal_vars_by_route: Dict[str, Tuple[str, ...]] = { normal_vars_by_route: Dict[str, Tuple[str, ...]] = {
"group": ("order", "period", "per_page"), "group": ("order", "period", "per_page"),
"home": ("order", "period", "per_page"), "home": ("order", "period", "per_page"),
"notifications": ("per_page",),
"search": ("order", "period", "per_page", "q"), "search": ("order", "period", "per_page", "q"),
"user": ("per_page",), "user": ("per_page",),
} }

41
tildes/tildes/models/comment/comment_notification_query.py

@ -8,18 +8,29 @@ from typing import Any
from pyramid.request import Request from pyramid.request import Request
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from tildes.models import ModelQuery
from tildes.lib.id import id_to_id36
from tildes.models.pagination import PaginatedQuery, PaginatedResults
from .comment_notification import CommentNotification from .comment_notification import CommentNotification
from .comment_vote import CommentVote from .comment_vote import CommentVote
class CommentNotificationQuery(ModelQuery):
"""Specialized ModelQuery for CommentNotifications."""
class CommentNotificationQuery(PaginatedQuery):
"""Specialized query class for CommentNotifications."""
def __init__(self, request: Request) -> None: def __init__(self, request: Request) -> None:
"""Initialize a CommentNotificationQuery for the request.""" """Initialize a CommentNotificationQuery for the request."""
super().__init__(CommentNotification, request) super().__init__(CommentNotification, request)
def _anchor_subquery(self, anchor_id: int) -> Any:
return (
self.request.db_session.query(*self.sorting_columns)
.filter(
CommentNotification.user == self.request.user,
CommentNotification.comment_id == anchor_id,
)
.subquery()
)
def _attach_extra_data(self) -> "CommentNotificationQuery": def _attach_extra_data(self) -> "CommentNotificationQuery":
"""Attach the user's comment votes to the query.""" """Attach the user's comment votes to the query."""
vote_subquery = ( vote_subquery = (
@ -56,3 +67,27 @@ class CommentNotificationQuery(ModelQuery):
notification.comment.user_voted = result.user_voted notification.comment.user_voted = result.user_voted
return notification return notification
def get_page(self, per_page: int) -> "CommentNotificationResults":
"""Get a page worth of results from the query (`per page` items)."""
return CommentNotificationResults(self, per_page)
class CommentNotificationResults(PaginatedResults):
"""Specialized results class for CommentNotifications."""
@property
def next_page_after_id36(self) -> str:
"""Return "after" ID36 that should be used to fetch the next page."""
if not self.has_next_page:
raise AttributeError
return id_to_id36(self.results[-1].comment_id)
@property
def prev_page_before_id36(self) -> str:
"""Return "before" ID36 that should be used to fetch the prev page."""
if not self.has_prev_page:
raise AttributeError
return id_to_id36(self.results[0].comment_id)

23
tildes/tildes/models/pagination.py

@ -20,9 +20,6 @@ class PaginatedQuery(ModelQuery):
def __init__(self, model_cls: Any, request: Request) -> None: def __init__(self, model_cls: Any, request: Request) -> None:
"""Initialize a PaginatedQuery for the specified model and request.""" """Initialize a PaginatedQuery for the specified model and request."""
if len(model_cls.__table__.primary_key) > 1:
raise TypeError("Only single-col primary key tables are supported")
super().__init__(model_cls, request) super().__init__(model_cls, request)
# default to sorting by created_time descending (newest first) # default to sorting by created_time descending (newest first)
@ -118,13 +115,7 @@ class PaginatedQuery(ModelQuery):
# an upper bound if the sort order is *ascending* # an upper bound if the sort order is *ascending*
is_anchor_upper_bound = not self.sort_desc is_anchor_upper_bound = not self.sort_desc
# create a subquery to get comparison values for the anchor item
id_column = list(self.model_cls.__table__.primary_key)[0]
subquery = (
self.request.db_session.query(*self.sorting_columns)
.filter(id_column == anchor_id)
.subquery()
)
subquery = self._anchor_subquery(anchor_id)
# restrict the results to items on the right "side" of the anchor item # restrict the results to items on the right "side" of the anchor item
if is_anchor_upper_bound: if is_anchor_upper_bound:
@ -134,6 +125,18 @@ class PaginatedQuery(ModelQuery):
return query return query
def _anchor_subquery(self, anchor_id: int) -> Any:
"""Return a subquery to get comparison values for the anchor item."""
if len(self.model_cls.__table__.primary_key) > 1:
raise TypeError("Only single-col primary key tables are supported")
id_column = list(self.model_cls.__table__.primary_key)[0]
return (
self.request.db_session.query(*self.sorting_columns)
.filter(id_column == anchor_id)
.subquery()
)
def _finalize(self) -> "PaginatedQuery": def _finalize(self) -> "PaginatedQuery":
"""Finalize the query before execution.""" """Finalize the query before execution."""
query = super()._finalize() query = super()._finalize()

5
tildes/tildes/templates/notifications.jinja2

@ -6,8 +6,3 @@
{% block title %}Previously read notifications{% endblock %} {% block title %}Previously read notifications{% endblock %}
{% block main_heading %}Previously read notifications{% endblock %} {% block main_heading %}Previously read notifications{% endblock %}
{% block content %}
<p class="text-italic text-small text-secondary">This page shows your most recent, previously read notifications (up to a max of 100, pagination coming soon)</p>
{{ super() }}
{% endblock %}

16
tildes/tildes/templates/notifications_unread.jinja2

@ -52,6 +52,22 @@
</li> </li>
{% endfor %} {% endfor %}
</ol> </ol>
{% if notifications.has_prev_page or notifications.has_next_page %}
<div class="pagination">
{% if notifications.has_prev_page %}
<a class="page-item btn" id="prev-page"
href="{{ request.current_listing_normal_url({'before': notifications.prev_page_before_id36}) }}"
>Prev</a>
{% endif %}
{% if notifications.has_next_page %}
<a class="page-item btn" id="next-page"
href="{{ request.current_listing_normal_url({'after': notifications.next_page_after_id36}) }}"
>Next</a>
{% endif %}
</div>
{% endif %}
{% else %} {% else %}
<p>No unread notifications.</p> <p>No unread notifications.</p>
<p><a href="/notifications">Go to previously read notifications</a></p> <p><a href="/notifications">Go to previously read notifications</a></p>

21
tildes/tildes/views/notifications.py

@ -6,9 +6,11 @@
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 tildes.enums import CommentLabelOption from tildes.enums import CommentLabelOption
from tildes.models.comment import CommentNotification from tildes.models.comment import CommentNotification
from tildes.schemas.topic_listing import TopicListingSchema
@view_config(route_name="notifications_unread", renderer="notifications_unread.jinja2") @view_config(route_name="notifications_unread", renderer="notifications_unread.jinja2")
@ -35,9 +37,12 @@ def get_user_unread_notifications(request: Request) -> dict:
@view_config(route_name="notifications", renderer="notifications.jinja2") @view_config(route_name="notifications", renderer="notifications.jinja2")
def get_user_notifications(request: Request) -> dict:
"""Show the most recent 100 of the logged-in user's read notifications."""
notifications = (
@use_kwargs(TopicListingSchema(only=("after", "before", "per_page")))
def get_user_notifications(
request: Request, after: str, before: str, per_page: int
) -> dict:
"""Show the logged-in user's previously-read notifications."""
query = (
request.query(CommentNotification) request.query(CommentNotification)
.join_all_relationships() .join_all_relationships()
.filter( .filter(
@ -45,8 +50,14 @@ def get_user_notifications(request: Request) -> dict:
CommentNotification.is_unread == False, # noqa CommentNotification.is_unread == False, # noqa
) )
.order_by(desc(CommentNotification.created_time)) .order_by(desc(CommentNotification.created_time))
.limit(100)
.all()
) )
if before:
query = query.before_id36(before)
if after:
query = query.after_id36(after)
notifications = query.get_page(per_page)
return {"notifications": notifications, "comment_label_options": CommentLabelOption} return {"notifications": notifications, "comment_label_options": CommentLabelOption}
Loading…
Cancel
Save