cache get_alt_graph for 1 hour

pull/83/head
Aevann 2022-12-22 22:44:37 +02:00
parent 8554317588
commit 92cbe64ac0
9 changed files with 54 additions and 54 deletions

View File

@ -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()

View File

@ -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('/@<username>/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

View File

@ -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
}

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -49,7 +49,7 @@
{% if v.admin_level >= PERMS['USER_LINK'] %}
<h2>Link Accounts</h2>
{% if u2 in u1.get_alt_graph(g.db) %}
{% if u2 in get_alt_graph(u.id) %}
<p>Accounts are <a href="/@{{u1.username}}/alts">known alts</a> of each other.</p>
{% else %}

View File

@ -225,7 +225,7 @@
<span id="profile--alts">Alts:</span>
{% endif %}
<ul id="profile--alts-list">
{% for account in u.get_alt_graph(g.db) if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
{% for account in get_alt_graph(u.id) if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
<li><a href="{{account.url}}">@{{account.username}}</a>{% if account._is_manual %} [m]{% endif %}</li>
{% endfor %}
</ul>
@ -489,7 +489,7 @@
<span id="profile-mobile--alts">Alts:</span>
{% endif %}
<ul id="profile-mobile--alts-list">
{% for account in u.get_alt_graph(g.db) if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
{% for account in get_alt_graph(u.id) if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
<li><a href="{{account.url}}">@{{account.username}}</a>{% if account._is_manual %} [m]{% endif %}</li>
{% endfor %}
</ul>

View File

@ -30,7 +30,7 @@
{% endif %}
{% if v %}
<div id='tax' class="d-none">{% if v.patron or u.patron or v.alts_patron or u.alts_patron %}0{% else %}0.03{% endif %}</div>
<div id='tax' class="d-none">{% if v.patron or u.patron %}0{% else %}0.03{% endif %}</div>
<script defer src="{{'js/userpage_v.js' | asset}}"></script>
<div id="username" class="d-none">{{u.username}}</div>
{% endif %}