From 92cbe64ac00df4a5714743497057aff5eca0a451 Mon Sep 17 00:00:00 2001 From: Aevann Date: Thu, 22 Dec 2022 22:44:37 +0200 Subject: [PATCH] cache get_alt_graph for 1 hour --- files/classes/user.py | 42 +------------------------- files/routes/admin.py | 11 ++++--- files/routes/jinja2.py | 3 +- files/routes/routehelpers.py | 40 ++++++++++++++++++++++-- files/routes/users.py | 2 +- files/routes/votes.py | 2 ++ files/templates/admin/alt_votes.html | 2 +- files/templates/userpage/banner.html | 4 +-- files/templates/userpage/userpage.html | 2 +- 9 files changed, 54 insertions(+), 54 deletions(-) diff --git a/files/classes/user.py b/files/classes/user.py index 76171d87b..666c79f94 100644 --- a/files/classes/user.py +++ b/files/classes/user.py @@ -1,6 +1,6 @@ import random from operator import * -from typing import Callable, Union +from typing import Union import pyotp from sqlalchemy import Column, ForeignKey @@ -470,46 +470,6 @@ class User(Base): def age(self): return int(time.time()) - self.created_utc - @lazy - def get_alt_graph(self, db:scoped_session, alt_filter:Optional[Callable[[Query], Query]]=None, **kwargs) -> Query: - ''' - Gets the full graph of alts (optionally filtering `Alt` objects by criteria using a callable, - such as by a date to only get alts from a certain date) as a query of users that can be filtered - further. This function filters alts marked as deleted by default, pass `include_deleted=True` to - disable this behavior and include delinked alts. - ''' - if not alt_filter: - alt_filter = lambda q:q - - if not kwargs.get('include_deleted', False): - deleted_filter = lambda q:q.filter(Alt.deleted == False) - else: - deleted_filter = lambda q:q - - combined_filter = lambda q:deleted_filter(alt_filter(q)) - - alt_graph_cte = db.query(literal(self.id).label('user_id')).select_from(Alt).cte('alt_graph', recursive=True) - - alt_graph_cte_inner = combined_filter(db.query( - case( - (Alt.user1 == alt_graph_cte.c.user_id, Alt.user2), - (Alt.user2 == alt_graph_cte.c.user_id, Alt.user1), - ) - ).select_from(Alt, alt_graph_cte).filter( - or_(alt_graph_cte.c.user_id == Alt.user1, alt_graph_cte.c.user_id == Alt.user2) - )) - - alt_graph_cte = alt_graph_cte.union(alt_graph_cte_inner) - return db.query(User).filter(User.id == alt_graph_cte.c.user_id) - - @property - @lazy - def alts_patron(self): - for u in self.get_alt_graph(g.db): - if not u._deleted and u.patron: return True - return False - - @property @lazy def follow_count(self): return g.db.query(Follow).filter_by(user_id=self.id).count() diff --git a/files/routes/admin.py b/files/routes/admin.py index b4f039a0b..7a9fe12f2 100644 --- a/files/routes/admin.py +++ b/files/routes/admin.py @@ -17,6 +17,7 @@ from files.helpers.settings import get_settings, toggle_setting from files.helpers.useractions import * from files.routes.routehelpers import check_for_alts from files.routes.wrappers import * +from files.routes.routehelpers import get_alt_graph from .front import frontlist @@ -261,7 +262,7 @@ def revert_actions(v:User, username): send_repeatable_notification(user.id, f"@{v.username} (a site admin) has unbanned you!") g.db.add(user) - for u in user.get_alt_graph(g.db): + for u in get_alt_graph(user.id): u.shadowbanned = None u.unban_utc = 0 u.ban_reason = None @@ -678,7 +679,7 @@ def alt_votes_get(v): @admin_level_required(PERMS['USER_LINK']) def admin_view_alts(v:User, username=None): u = get_user(username or request.values.get('username'), graceful=True) - return render_template('admin/alts.html', v=v, u=u, alts=u.get_alt_graph(g.db) if u else None) + return render_template('admin/alts.html', v=v, u=u, alts=get_alt_graph(u.id) if u else None) @app.post('/@/alts/') @limiter.limit(DEFAULT_RATELIMIT_SLOWER) @@ -843,7 +844,7 @@ def unshadowban(user_id, v): if not user.is_banned: user.ban_reason = None g.db.add(user) - for alt in user.get_alt_graph(g.db): + for alt in get_alt_graph(user.id): alt.shadowbanned = None if not alt.is_banned: alt.ban_reason = None g.db.add(alt) @@ -920,7 +921,7 @@ def ban_user(user_id, v): user.ban(admin=v, reason=reason, days=days) if request.values.get("alts"): - for x in user.get_alt_graph(g.db): + for x in get_alt_graph(user.id): if x.admin_level > v.admin_level: continue x.ban(admin=v, reason=reason, days=days) @@ -1052,7 +1053,7 @@ def unban_user(user_id, v): send_repeatable_notification(user.id, f"@{v.username} (a site admin) has unbanned you!") g.db.add(user) - for x in user.get_alt_graph(g.db): + for x in get_alt_graph(user.id): if x.is_banned: send_repeatable_notification(x.id, f"@{v.username} (a site admin) has unbanned you!") x.is_banned = None x.unban_utc = 0 diff --git a/files/routes/jinja2.py b/files/routes/jinja2.py index bd884ca2a..7ffe92f0d 100644 --- a/files/routes/jinja2.py +++ b/files/routes/jinja2.py @@ -12,6 +12,7 @@ from files.helpers.assetcache import assetcache_path from files.helpers.config.const import * from files.helpers.settings import get_settings from files.helpers.sorting_and_time import make_age_string +from files.routes.routehelpers import get_alt_graph from files.routes.routehelpers import get_formkey from files.__main__ import app, cache @@ -95,5 +96,5 @@ def inject_constants(): "HOUSE_JOIN_COST":HOUSE_JOIN_COST, "HOUSE_SWITCH_COST":HOUSE_SWITCH_COST, "IMAGE_FORMATS":IMAGE_FORMATS, "PAGE_SIZES":PAGE_SIZES, "THEMES":THEMES, "COMMENT_SORTS":COMMENT_SORTS, "SORTS":SORTS, "TIME_FILTERS":TIME_FILTERS, "HOUSES":HOUSES, "TIERS_ID_TO_NAME":TIERS_ID_TO_NAME, - "DEFAULT_CONFIG_VALUE":DEFAULT_CONFIG_VALUE, "IS_LOCALHOST":IS_LOCALHOST, "BACKGROUND_CATEGORIES":BACKGROUND_CATEGORIES, "PAGE_SIZE":PAGE_SIZE, "TAGLINE":TAGLINE, "HOLIDAY_EVENT":HOLIDAY_EVENT + "DEFAULT_CONFIG_VALUE":DEFAULT_CONFIG_VALUE, "IS_LOCALHOST":IS_LOCALHOST, "BACKGROUND_CATEGORIES":BACKGROUND_CATEGORIES, "PAGE_SIZE":PAGE_SIZE, "TAGLINE":TAGLINE, "HOLIDAY_EVENT":HOLIDAY_EVENT, "get_alt_graph":get_alt_graph } diff --git a/files/routes/routehelpers.py b/files/routes/routehelpers.py index 7d1cc6a99..0be87da13 100644 --- a/files/routes/routehelpers.py +++ b/files/routes/routehelpers.py @@ -2,13 +2,17 @@ import time import secrets from random import randint -from typing import Optional, Union +from typing import Optional, Union, Callable +from sqlalchemy.orm import aliased, deferred, Query +from sqlalchemy.sql import case, literal +from sqlalchemy.sql.expression import or_ from flask import g, session from files.classes import Alt, Comment, User, Submission from files.helpers.config.const import * from files.helpers.security import generate_hash, validate_hash +from files.__main__ import cache def get_raw_formkey(u:User): if not session.get("session_id"): @@ -25,6 +29,38 @@ def validate_formkey(u:User, formkey:Optional[str]) -> bool: if not formkey: return False return validate_hash(get_raw_formkey(u), formkey) +@cache.memoize(timeout=3600) +def get_alt_graph(uid:int, alt_filter:Optional[Callable[[Query], Query]]=None, **kwargs) -> Query: + ''' + Gets the full graph of alts (optionally filtering `Alt` objects by criteria using a callable, + such as by a date to only get alts from a certain date) as a query of users that can be filtered + further. This function filters alts marked as deleted by default, pass `include_deleted=True` to + disable this behavior and include delinked alts. + ''' + if not alt_filter: + alt_filter = lambda q:q + + if not kwargs.get('include_deleted', False): + deleted_filter = lambda q:q.filter(Alt.deleted == False) + else: + deleted_filter = lambda q:q + + combined_filter = lambda q:deleted_filter(alt_filter(q)) + + alt_graph_cte = g.db.query(literal(uid).label('user_id')).select_from(Alt).cte('alt_graph', recursive=True) + + alt_graph_cte_inner = combined_filter(g.db.query( + case( + (Alt.user1 == alt_graph_cte.c.user_id, Alt.user2), + (Alt.user2 == alt_graph_cte.c.user_id, Alt.user1), + ) + ).select_from(Alt, alt_graph_cte).filter( + or_(alt_graph_cte.c.user_id == Alt.user1, alt_graph_cte.c.user_id == Alt.user2) + )) + + alt_graph_cte = alt_graph_cte.union(alt_graph_cte_inner) + return g.db.query(User).filter(User.id == alt_graph_cte.c.user_id, User.id != uid).order_by(User.username).all() + def check_for_alts(current:User, include_current_session=True): current_id = current.id ids = [x[0] for x in g.db.query(User.id).all()] @@ -65,7 +101,7 @@ def check_for_alts(current:User, include_current_session=True): if include_current_session: session["history"] = list(past_accs) g.db.flush() - for u in current.get_alt_graph(g.db): + for u in get_alt_graph(current.id): if u._alt_deleted: continue if u.shadowbanned and current.id not in DONT_SHADOWBAN: current.shadowbanned = u.shadowbanned diff --git a/files/routes/users.py b/files/routes/users.py index 37da76139..dcef6667b 100644 --- a/files/routes/users.py +++ b/files/routes/users.py @@ -312,7 +312,7 @@ def transfer_currency(v:User, username:str, currency_name:Literal['coins', 'mars if amount is None or amount <= 0: abort(400, f"Invalid number of {currency_name}") if amount < MIN_CURRENCY_TRANSFER: abort(400, f"You have to gift at least {MIN_CURRENCY_TRANSFER} {currency_name}") tax = 0 - if apply_tax and not v.patron and not receiver.patron and not v.alts_patron and not receiver.alts_patron: + if apply_tax and not v.patron and not receiver.patron: tax = math.ceil(amount*TAX_PCT) reason = request.values.get("reason", "").strip() diff --git a/files/routes/votes.py b/files/routes/votes.py index 4034b8e00..a39f21113 100644 --- a/files/routes/votes.py +++ b/files/routes/votes.py @@ -89,6 +89,8 @@ def vote_post_comment(target_id, new, v, cls, vote_cls): if v.id == target.author.id: coin_delta = 0 + v_alts_id = [x.id for x in v.get_alt_graph(self.id) if not x._alt_deleted] + alt = False if target.author.id in v.alt_ids or v.id in target.author.alt_ids: coin_delta = -1 diff --git a/files/templates/admin/alt_votes.html b/files/templates/admin/alt_votes.html index 8a54e26a6..a56c0bd2f 100644 --- a/files/templates/admin/alt_votes.html +++ b/files/templates/admin/alt_votes.html @@ -49,7 +49,7 @@ {% if v.admin_level >= PERMS['USER_LINK'] %}

Link Accounts

- {% if u2 in u1.get_alt_graph(g.db) %} + {% if u2 in get_alt_graph(u.id) %}

Accounts are known alts of each other.

{% else %} diff --git a/files/templates/userpage/banner.html b/files/templates/userpage/banner.html index 3b964b9a3..91f1cc54b 100644 --- a/files/templates/userpage/banner.html +++ b/files/templates/userpage/banner.html @@ -225,7 +225,7 @@ Alts: {% endif %} @@ -489,7 +489,7 @@ Alts: {% endif %} diff --git a/files/templates/userpage/userpage.html b/files/templates/userpage/userpage.html index 758168b96..198468fe2 100644 --- a/files/templates/userpage/userpage.html +++ b/files/templates/userpage/userpage.html @@ -30,7 +30,7 @@ {% endif %} {% if v %} -
{% if v.patron or u.patron or v.alts_patron or u.alts_patron %}0{% else %}0.03{% endif %}
+
{% if v.patron or u.patron %}0{% else %}0.03{% endif %}
{{u.username}}
{% endif %}