Repository: CTFd/CTFd

Issue:
get_standings should return more flexible data
get_standings is a little inflexible because the data is constrained. 


Discussion:
I think so long as this doesn't break any templates it could be done in 2.2. 
This is a difficult problem because the sqlalchemy mapper gets confused if we have both `account_id` and `account` being requested. `account.id` is being remapped to `account_id` which confuses everyone. 

There doesn't appear to be a good way to override sqlalchemy to get the desired behavior so the best solution seems to be moving `account_id` to `account.id`. 

One solution is to drop `account_id` and access it via `account.id` but this will require template changes (albeit minor. )
I think the right way to approach this is to have an argument where we can pass in additional fields that we want to load in and additional filters to run. 

Resolving patch:
diff --git a/CTFd/utils/scores/__init__.py b/CTFd/utils/scores/__init__.py
--- a/CTFd/utils/scores/__init__.py
+++ b/CTFd/utils/scores/__init__.py
@@ -8,7 +8,7 @@
 
 
 @cache.memoize(timeout=60)
-def get_standings(count=None, admin=False):
+def get_standings(count=None, admin=False, fields=[]):
     """
     Get standings as a list of tuples containing account_id, name, and score e.g. [(account_id, team_name, score)].
 
@@ -86,6 +86,7 @@ def get_standings(count=None, admin=False):
                 Model.hidden,
                 Model.banned,
                 sumscores.columns.score,
+                *fields,
             )
             .join(sumscores, Model.id == sumscores.columns.account_id)
             .order_by(sumscores.columns.score.desc(), sumscores.columns.id)
@@ -97,6 +98,7 @@ def get_standings(count=None, admin=False):
                 Model.oauth_id.label("oauth_id"),
                 Model.name.label("name"),
                 sumscores.columns.score,
+                *fields,
             )
             .join(sumscores, Model.id == sumscores.columns.account_id)
             .filter(Model.banned == False, Model.hidden == False)
@@ -115,7 +117,7 @@ def get_standings(count=None, admin=False):
 
 
 @cache.memoize(timeout=60)
-def get_team_standings(count=None, admin=False):
+def get_team_standings(count=None, admin=False, fields=[]):
     scores = (
         db.session.query(
             Solves.team_id.label("team_id"),
@@ -166,6 +168,7 @@ def get_team_standings(count=None, admin=False):
                 Teams.hidden,
                 Teams.banned,
                 sumscores.columns.score,
+                *fields,
             )
             .join(sumscores, Teams.id == sumscores.columns.team_id)
             .order_by(sumscores.columns.score.desc(), sumscores.columns.id)
@@ -177,6 +180,7 @@ def get_team_standings(count=None, admin=False):
                 Teams.oauth_id.label("oauth_id"),
                 Teams.name.label("name"),
                 sumscores.columns.score,
+                *fields,
             )
             .join(sumscores, Teams.id == sumscores.columns.team_id)
             .filter(Teams.banned == False)
@@ -193,7 +197,7 @@ def get_team_standings(count=None, admin=False):
 
 
 @cache.memoize(timeout=60)
-def get_user_standings(count=None, admin=False):
+def get_user_standings(count=None, admin=False, fields=[]):
     scores = (
         db.session.query(
             Solves.user_id.label("user_id"),
@@ -244,6 +248,7 @@ def get_user_standings(count=None, admin=False):
                 Users.hidden,
                 Users.banned,
                 sumscores.columns.score,
+                *fields,
             )
             .join(sumscores, Users.id == sumscores.columns.user_id)
             .order_by(sumscores.columns.score.desc(), sumscores.columns.id)
@@ -255,6 +260,7 @@ def get_user_standings(count=None, admin=False):
                 Users.oauth_id.label("oauth_id"),
                 Users.name.label("name"),
                 sumscores.columns.score,
+                *fields,
             )
             .join(sumscores, Users.id == sumscores.columns.user_id)
             .filter(Users.banned == False, Users.hidden == False)


Test patch: