Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions scoring/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from projects.permissions import ObjectPermission
from projects.services.common import get_site_main_project
from projects.views import get_projects_qs, get_project_permission_for_user
from questions.models import AggregationMethod
from scoring.constants import LeaderboardScoreTypes
from scoring.models import Leaderboard, LeaderboardEntry, LeaderboardsRanksEntry
from scoring.serializers import (
Expand All @@ -24,7 +23,7 @@
)
from scoring.utils import get_contributions, update_project_leaderboard
from users.models import User
from users.services.profile_stats import serialize_profile
from users.services.profile_stats import serialize_metaculus_stats


@api_view(["GET"])
Expand Down Expand Up @@ -352,13 +351,9 @@ def medal_contributions(
return Response(return_data)


@cache_page(60 * 60 * 24)
@api_view(["GET"])
@permission_classes([AllowAny])
def metaculus_track_record(
request: Request,
):
# TODO: make it "default"
return Response(
serialize_profile(aggregation_method=AggregationMethod.RECENCY_WEIGHTED)
)
return Response(serialize_metaculus_stats())
85 changes: 47 additions & 38 deletions users/services/profile_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
from questions.types import AggregationMethod
from scoring.constants import ScoreTypes
from scoring.models import Score
from users.models import User, UserSpamActivity
from users.serializers import UserPublicSerializer
from users.models import User
from utils.cache import cache_get_or_set


@dataclass(frozen=True)
Expand Down Expand Up @@ -376,57 +376,66 @@ def get_authoring_stats_data(
}


def get_user_profile_data(
user: User,
) -> dict:
return UserPublicSerializer(user).data
def _serialize_user_stats(user: User):
score_qs = Score.objects.filter(
question__related_posts__post__default_project__default_permission__isnull=False,
score_type=ScoreTypes.PEER,
)
score_qs = score_qs.filter(user=user)

scores = generate_question_scores(score_qs)
data = {}

data.update(get_score_scatter_plot_data(scores=scores, user=user))
data.update(get_score_histogram_data(scores=scores, user=user))
data.update(get_calibration_curve_data(user=user))
data.update(get_forecasting_stats_data(scores=scores, user=user))
data.update(get_authoring_stats_data(user))

return data


def serialize_user_stats(user: User):
return cache_get_or_set(
f"serialize_user_stats:{user.id}",
lambda: _serialize_user_stats(user),
# 1h
timeout=3600,
)


def _serialize_metaculus_stats() -> dict:
aggregation_method = AggregationMethod.RECENCY_WEIGHTED

def serialize_profile(
user: User | None = None,
aggregation_method: AggregationMethod | None = None,
score_type: ScoreTypes | None = None,
current_user: User | None = None,
) -> dict:
if (user is None and aggregation_method is None) or (
user is not None and aggregation_method is not None
):
raise ValueError("Either user or aggregation_method must be provided only")
if user is not None and score_type is None:
score_type = ScoreTypes.PEER
if aggregation_method is not None and score_type is None:
score_type = ScoreTypes.BASELINE
# TODO: support archived scores
score_qs = Score.objects.filter(
question__related_posts__post__default_project__default_permission__isnull=False,
score_type=score_type,
score_type=ScoreTypes.BASELINE,
)
if user is not None:
score_qs = score_qs.filter(user=user)
else:
score_qs = score_qs.filter(aggregation_method=aggregation_method)
score_qs = score_qs.filter(aggregation_method=aggregation_method)

scores = generate_question_scores(score_qs)
data = {}
data.update(
get_score_scatter_plot_data(
scores=scores, user=user, aggregation_method=aggregation_method
scores=scores, aggregation_method=aggregation_method
)
)
data.update(
get_score_histogram_data(
scores=scores, user=user, aggregation_method=aggregation_method
)
get_score_histogram_data(scores=scores, aggregation_method=aggregation_method)
)
data.update(get_calibration_curve_data(user, aggregation_method))
data.update(get_calibration_curve_data(aggregation_method=aggregation_method))
data.update(
get_forecasting_stats_data(
scores=scores, user=user, aggregation_method=aggregation_method
)
get_forecasting_stats_data(scores=scores, aggregation_method=aggregation_method)
)
if user is not None:
data.update(get_user_profile_data(user))
data.update(get_authoring_stats_data(user))
if current_user is not None and current_user.is_staff:
data.update({"spam_count": UserSpamActivity.objects.filter(user=user).count()})

return data


def serialize_metaculus_stats():
return cache_get_or_set(
f"serialize_metaculus_stats",
lambda: _serialize_metaculus_stats(),
# 24h
timeout=60 * 60 * 24,
)
24 changes: 18 additions & 6 deletions users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from django.contrib.auth.password_validation import validate_password
from django.utils import timezone
from django.views.decorators.cache import cache_page
from rest_framework import serializers, status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.exceptions import ValidationError
Expand All @@ -12,7 +11,7 @@
from rest_framework.request import Request
from rest_framework.response import Response

from users.models import User
from users.models import User, UserSpamActivity
from users.serializers import (
UserPrivateSerializer,
UserPublicSerializer,
Expand All @@ -32,7 +31,7 @@
)
from utils.paginator import LimitOffsetPagination
from utils.tasks import email_user_their_data_task
from .services.profile_stats import serialize_profile
from .services.profile_stats import serialize_user_stats
from .services.spam_detection import (
check_profile_update_for_spam,
send_deactivation_email,
Expand All @@ -59,16 +58,29 @@ def current_user_api_view(request):
return Response(UserPrivateSerializer(request.user).data)


@cache_page(60 * 60)
@api_view(["GET"])
@permission_classes([AllowAny])
def user_profile_api_view(request, pk: int):
current_user = request.user

qs = User.objects.all()
if not request.user.is_staff:
if not current_user.is_staff:
qs = qs.filter(is_active=True, is_spam=False)

user = get_object_or_404(qs, pk=pk)

return Response(serialize_profile(user, current_user=request.user))
# Basic profile data
profile = UserPublicSerializer(user).data

if current_user and current_user.is_staff:
profile.update(
{"spam_count": UserSpamActivity.objects.filter(user=user).count()}
)

# Performing slow but cached profile request
profile.update(serialize_user_stats(user))

return Response(profile)


@api_view(["GET"])
Expand Down
Loading