forked from forks/microblog.pub
Pagination in the admin
This commit is contained in:
parent
489ed6cbe0
commit
d4c80dedeb
8 changed files with 112 additions and 29 deletions
50
app/admin.py
50
app/admin.py
|
@ -26,6 +26,7 @@ from app.config import verify_password
|
|||
from app.database import get_db
|
||||
from app.lookup import lookup
|
||||
from app.uploads import save_upload
|
||||
from app.utils import pagination
|
||||
from app.utils.emoji import EMOJIS_BY_NAME
|
||||
|
||||
|
||||
|
@ -165,10 +166,25 @@ def admin_bookmarks(
|
|||
def admin_inbox(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
filter_by: str | None = None,
|
||||
cursor: str | None = None,
|
||||
) -> templates.TemplateResponse:
|
||||
q = db.query(models.InboxObject).filter(
|
||||
models.InboxObject.ap_type.not_in(["Accept"])
|
||||
)
|
||||
|
||||
if filter_by:
|
||||
q = q.filter(models.InboxObject.ap_type == filter_by)
|
||||
if cursor:
|
||||
q = q.filter(
|
||||
models.InboxObject.ap_published_at < pagination.decode_cursor(cursor)
|
||||
)
|
||||
|
||||
page_size = 20
|
||||
remaining_count = q.count()
|
||||
|
||||
inbox = (
|
||||
db.query(models.InboxObject)
|
||||
.options(
|
||||
q.options(
|
||||
joinedload(models.InboxObject.relates_to_inbox_object),
|
||||
joinedload(models.InboxObject.relates_to_outbox_object),
|
||||
)
|
||||
|
@ -176,25 +192,43 @@ def admin_inbox(
|
|||
.limit(20)
|
||||
.all()
|
||||
)
|
||||
|
||||
next_cursor = (
|
||||
pagination.encode_cursor(inbox[-1].ap_published_at)
|
||||
if inbox and remaining_count > page_size
|
||||
else None
|
||||
)
|
||||
|
||||
return templates.render_template(
|
||||
db,
|
||||
request,
|
||||
"admin_inbox.html",
|
||||
{
|
||||
"inbox": inbox,
|
||||
"next_cursor": next_cursor,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/outbox")
|
||||
def admin_outbox(
|
||||
request: Request, db: Session = Depends(get_db), filter_by: str | None = None
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
filter_by: str | None = None,
|
||||
cursor: str | None = None,
|
||||
) -> templates.TemplateResponse:
|
||||
q = db.query(models.OutboxObject).filter(
|
||||
models.OutboxObject.ap_type.not_in(["Accept"])
|
||||
)
|
||||
if filter_by:
|
||||
q = q.filter(models.OutboxObject.ap_type == filter_by)
|
||||
if cursor:
|
||||
q = q.filter(
|
||||
models.OutboxObject.ap_published_at < pagination.decode_cursor(cursor)
|
||||
)
|
||||
|
||||
page_size = 20
|
||||
remaining_count = q.count()
|
||||
|
||||
outbox = (
|
||||
q.options(
|
||||
|
@ -203,9 +237,16 @@ def admin_outbox(
|
|||
joinedload(models.OutboxObject.relates_to_actor),
|
||||
)
|
||||
.order_by(models.OutboxObject.ap_published_at.desc())
|
||||
.limit(20)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
|
||||
next_cursor = (
|
||||
pagination.encode_cursor(outbox[-1].ap_published_at)
|
||||
if outbox and remaining_count > page_size
|
||||
else None
|
||||
)
|
||||
|
||||
actors_metadata = get_actors_metadata(
|
||||
db,
|
||||
[
|
||||
|
@ -222,6 +263,7 @@ def admin_outbox(
|
|||
{
|
||||
"actors_metadata": actors_metadata,
|
||||
"outbox": outbox,
|
||||
"next_cursor": next_cursor,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
23
app/main.py
23
app/main.py
|
@ -2,14 +2,12 @@ import base64
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
from typing import Type
|
||||
|
||||
import httpx
|
||||
from dateutil.parser import isoparse
|
||||
from fastapi import Depends
|
||||
from fastapi import FastAPI
|
||||
from fastapi import Form
|
||||
|
@ -52,6 +50,7 @@ from app.config import verify_csrf_token
|
|||
from app.database import get_db
|
||||
from app.templates import is_current_user_admin
|
||||
from app.uploads import UPLOAD_DIR
|
||||
from app.utils import pagination
|
||||
from app.utils.emoji import EMOJIS_BY_NAME
|
||||
from app.webfinger import get_remote_follow_template
|
||||
|
||||
|
@ -154,7 +153,7 @@ def index(
|
|||
models.OutboxObject.is_hidden_from_homepage.is_(False),
|
||||
)
|
||||
total_count = q.count()
|
||||
page_size = 2
|
||||
page_size = 20
|
||||
page_offset = (page - 1) * page_size
|
||||
|
||||
outbox_objects = (
|
||||
|
@ -203,7 +202,9 @@ def _build_followx_collection(
|
|||
|
||||
q = db.query(model_cls).order_by(model_cls.created_at.desc()) # type: ignore
|
||||
if next_cursor:
|
||||
q = q.filter(model_cls.created_at < _decode_cursor(next_cursor)) # type: ignore
|
||||
q = q.filter(
|
||||
model_cls.created_at < pagination.decode_cursor(next_cursor) # type: ignore
|
||||
)
|
||||
q = q.limit(20)
|
||||
|
||||
items = [followx for followx in q.all()]
|
||||
|
@ -215,7 +216,7 @@ def _build_followx_collection(
|
|||
.count()
|
||||
> 0
|
||||
):
|
||||
next_cursor = _encode_cursor(items[-1].created_at)
|
||||
next_cursor = pagination.encode_cursor(items[-1].created_at)
|
||||
|
||||
collection_page = {
|
||||
"@context": ap.AS_CTX,
|
||||
|
@ -234,14 +235,6 @@ def _build_followx_collection(
|
|||
return collection_page
|
||||
|
||||
|
||||
def _encode_cursor(val: datetime) -> str:
|
||||
return base64.urlsafe_b64encode(val.isoformat().encode()).decode()
|
||||
|
||||
|
||||
def _decode_cursor(cursor: str) -> datetime:
|
||||
return isoparse(base64.urlsafe_b64decode(cursor).decode())
|
||||
|
||||
|
||||
@app.get("/followers")
|
||||
def followers(
|
||||
request: Request,
|
||||
|
@ -262,6 +255,7 @@ def followers(
|
|||
)
|
||||
)
|
||||
|
||||
# We only show the most recent 20 followers on the public website
|
||||
followers = (
|
||||
db.query(models.Follower)
|
||||
.options(joinedload(models.Follower.actor))
|
||||
|
@ -270,7 +264,6 @@ def followers(
|
|||
.all()
|
||||
)
|
||||
|
||||
# TODO: support next_cursor/prev_cursor
|
||||
actors_metadata = {}
|
||||
if is_current_user_admin(request):
|
||||
actors_metadata = get_actors_metadata(
|
||||
|
@ -309,6 +302,7 @@ def following(
|
|||
)
|
||||
)
|
||||
|
||||
# We only show the most recent 20 follows on the public website
|
||||
q = (
|
||||
db.query(models.Following)
|
||||
.options(joinedload(models.Following.actor))
|
||||
|
@ -341,6 +335,7 @@ def outbox(
|
|||
db: Session = Depends(get_db),
|
||||
_: httpsig.HTTPSigInfo = Depends(httpsig.httpsig_checker),
|
||||
) -> ActivityPubResponse:
|
||||
# By design, we only show the last 20 public activities in the oubox
|
||||
outbox_objects = (
|
||||
db.query(models.OutboxObject)
|
||||
.filter(
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
|
||||
{{ utils.display_box_filters("admin_inbox") }}
|
||||
|
||||
{% for inbox_object in inbox %}
|
||||
{% if inbox_object.ap_type == "Announce" %}
|
||||
{{ utils.display_object(inbox_object.relates_to_anybox_object) }}
|
||||
|
@ -14,4 +16,8 @@
|
|||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if next_cursor %}
|
||||
<p><a href="{{ url_for("admin_inbox") }}?cursor={{ next_cursor }}{% if request.query_params.filter_by %}&filter_by={{ request.query_params.filter_by }}{% endif %}">See more</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,17 +2,7 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
|
||||
<p>Filter by
|
||||
{% for ap_type in ["Note", "Like", "Announce", "Follow"] %}
|
||||
<a style="margin-right:12px;" href="{{ url_for("admin_outbox") }}?filter_by={{ ap_type }}">
|
||||
{% if request.query_params.filter_by == ap_type %}
|
||||
<strong>{{ ap_type }}</strong>
|
||||
{% else %}
|
||||
{{ ap_type }}
|
||||
{% endif %}</a>
|
||||
{% endfor %}.
|
||||
{% if request.query_params.filter_by %}<a href="{{ url_for("admin_outbox") }}">Reset filter</a>{% endif %}</p>
|
||||
</p>
|
||||
{{ utils.display_box_filters("admin_outbox") }}
|
||||
|
||||
{% for outbox_object in outbox %}
|
||||
|
||||
|
@ -33,4 +23,8 @@
|
|||
|
||||
{% endfor %}
|
||||
|
||||
{% if next_cursor %}
|
||||
<p><a href="{{ url_for("admin_outbox") }}?cursor={{ next_cursor }}{% if request.query_params.filter_by %}&filter_by={{ request.query_params.filter_by }}{% endif %}">See more</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -8,5 +8,15 @@
|
|||
<li>{{ utils.display_actor(follower.actor, actors_metadata) }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% set x_more = followers_count - followers | length %}
|
||||
{% if x_more > 0 %}
|
||||
<p>And {{ x_more }} more.</p>
|
||||
{% endif %}
|
||||
|
||||
{% if is_admin %}
|
||||
<p><a href="{{ url_for("admin_inbox") }}?filter_by=Follow">Manage followers</a></p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -8,5 +8,15 @@
|
|||
<li>{{ utils.display_actor(follow.actor, actors_metadata) }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% set x_more = following_count - following | length %}
|
||||
{% if x_more > 0 %}
|
||||
<p>And {{ x_more }} more.</p>
|
||||
{% endif %}
|
||||
|
||||
{% if is_admin %}
|
||||
<p><a href="{{ url_for("admin_outbox") }}?filter_by=Follow">Manage follows</a></p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -109,6 +109,20 @@
|
|||
</form>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro display_box_filters(route) %}
|
||||
<p>Filter by
|
||||
{% for ap_type in ["Note", "Like", "Announce", "Follow"] %}
|
||||
<a style="margin-right:12px;" href="{{ url_for(route) }}?filter_by={{ ap_type }}">
|
||||
{% if request.query_params.filter_by == ap_type %}
|
||||
<strong>{{ ap_type }}</strong>
|
||||
{% else %}
|
||||
{{ ap_type }}
|
||||
{% endif %}</a>
|
||||
{% endfor %}.
|
||||
{% if request.query_params.filter_by %}<a href="{{ url_for(route) }}">Reset filter</a>{% endif %}</p>
|
||||
</p>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro display_actor(actor, actors_metadata) %}
|
||||
{% set metadata = actors_metadata.get(actor.ap_id) %}
|
||||
<div style="display: flex;column-gap: 20px;margin:20px 0 10px 0;" class="actor-box h-card p-author">
|
||||
|
|
12
app/utils/pagination.py
Normal file
12
app/utils/pagination.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
import base64
|
||||
from datetime import datetime
|
||||
|
||||
from dateutil.parser import isoparse
|
||||
|
||||
|
||||
def encode_cursor(val: datetime) -> str:
|
||||
return base64.urlsafe_b64encode(val.isoformat().encode()).decode()
|
||||
|
||||
|
||||
def decode_cursor(cursor: str) -> datetime:
|
||||
return isoparse(base64.urlsafe_b64decode(cursor).decode())
|
Loading…
Reference in a new issue