Skip to content

Commit

Permalink
Merge branch 'main' into 1749-add_subsamples_api
Browse files Browse the repository at this point in the history
  • Loading branch information
GresilleSiffle committed Oct 9, 2023
2 parents 7242715 + 932a97a commit 5410405
Show file tree
Hide file tree
Showing 69 changed files with 937 additions and 766 deletions.
3 changes: 2 additions & 1 deletion backend/.pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ disable=raw-checker-failed,
too-many-ancestors,
too-few-public-methods,
use-maxsplit-arg,
cyclic-import
cyclic-import,
duplicate-code

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
Expand Down
2 changes: 1 addition & 1 deletion backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ djangorestframework==3.14.0
# See https://github.com/tfranzel/drf-spectacular
drf-spectacular==0.26.4
# Pillow is used to generate the Previews for links shared on social media
Pillow==9.5.0
Pillow==10.0.1
# Needed for postgres database
psycopg2-binary==2.9.7
# PyYAML is used for reading the settings
Expand Down
15 changes: 10 additions & 5 deletions backend/tournesol/models/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,21 @@ def with_prefetched_scores(self, poll_name, mode=ScoreMode.DEFAULT):
)
)

def with_prefetched_contributor_ratings(self, poll, user):
def with_prefetched_contributor_ratings(self, poll, user, prefetch_criteria_scores=False):
# pylint: disable=import-outside-toplevel
from tournesol.models.ratings import ContributorRating

contributor_ratings = (
ContributorRating.objects.filter(poll=poll, user=user)
.annotate_n_comparisons()
)
if prefetch_criteria_scores:
contributor_ratings = contributor_ratings.prefetch_related("criteria_scores")

return self.prefetch_related(
Prefetch(
"contributorvideoratings",
queryset=ContributorRating.objects.filter(
poll=poll,
user=user,
).annotate_n_comparisons(),
queryset=contributor_ratings,
to_attr="_prefetched_contributor_ratings",
)
)
Expand Down
44 changes: 29 additions & 15 deletions backend/tournesol/models/ratings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,47 @@
"""

from django.db import models
from django.db.models import Func, OuterRef, Q, Subquery
from django.db.models import Count, F, FilteredRelation, Max, OuterRef, Q, Subquery

from core.models import User

from .comparisons import Comparison
from .entity import Entity
from .poll import Poll


class ContributorRatingQueryset(models.QuerySet):
_related_comparisons_is_annotated = False

def _annotate_related_comparisons(self):
if self._related_comparisons_is_annotated:
return self
self._related_comparisons_is_annotated = True
return self.annotate(
related_comparisons=FilteredRelation(
"user__comparisons",
condition=(
Q(user__comparisons__poll=F("poll"))
& (
Q(user__comparisons__entity_1=F("entity"))
| Q(user__comparisons__entity_2=F("entity"))
)
),
)
)

def annotate_n_comparisons(self):
n_comparisons = (
Comparison.objects.filter(poll=OuterRef("poll"), user=OuterRef("user"))
.filter(Q(entity_1=OuterRef("entity")) | Q(entity_2=OuterRef("entity")))
.annotate(count=Func("id", function="Count"))
.values("count")
return (
self
._annotate_related_comparisons()
.annotate(n_comparisons=Count("related_comparisons"))
)
return self.annotate(n_comparisons=Subquery(n_comparisons))

def annotate_last_compared_at(self):
last_compared_at = (
Comparison.objects.filter(poll=OuterRef("poll"), user=OuterRef("user"))
.filter(Q(entity_1=OuterRef("entity")) | Q(entity_2=OuterRef("entity")))
.values("datetime_lastedit")
.order_by("-datetime_lastedit")
)[:1]
return self.annotate(last_compared_at=Subquery(last_compared_at))
return (
self
._annotate_related_comparisons()
.annotate(last_compared_at=Max("related_comparisons__datetime_lastedit"))
)

def annotate_collective_score(self):
# pylint: disable=import-outside-toplevel
Expand Down
57 changes: 40 additions & 17 deletions backend/tournesol/serializers/contributor_recommendations.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,55 @@
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.utils import extend_schema_field, extend_schema_serializer
from rest_framework.serializers import SerializerMethodField

from tournesol.serializers.poll import RecommendationSerializer
from tournesol.serializers.rating import ContributorCriteriaScore


from tournesol.models.ratings import ContributorRating
from tournesol.serializers.criteria_score import ContributorCriteriaScoreSerializer
from tournesol.serializers.poll import IndividualRatingSerializer, RecommendationSerializer


class IndividualRatingWithScoresSerializer(IndividualRatingSerializer):
criteria_scores = ContributorCriteriaScoreSerializer(many=True, read_only=True)

class Meta:
model = ContributorRating
fields = IndividualRatingSerializer.Meta.fields + ["criteria_scores"]
read_only_fields = fields


@extend_schema_serializer(
exclude_fields=[
# legacy fields have been moved to "entity", "invidual_rating", "collective_rating", etc.
"uid",
"type",
"n_comparisons",
"n_contributors",
"metadata",
"total_score",
"tournesol_score",
"criteria_scores",
"unsafe",
"is_public",
]
)
class ContributorRecommendationsSerializer(RecommendationSerializer):
"""
An entity recommended by a user.
In addition to the fields inherited from `RecommendationSerializer`, this
serializer also display the public status of the `ContributorRating`
related to the trio poll / entity / user.
Note that the fields `n_comparisons` and `n_contributors` contain
the collective values, and are not specific to the user.
"""

is_public = SerializerMethodField()
criteria_scores = SerializerMethodField()
individual_rating = IndividualRatingWithScoresSerializer(
source="single_contributor_rating",
read_only=True,
)

class Meta(RecommendationSerializer.Meta):
fields = RecommendationSerializer.Meta.fields + ["is_public"]
fields = RecommendationSerializer.Meta.fields + ["is_public", "individual_rating"]

@extend_schema_field(ContributorCriteriaScore(many=True))
@extend_schema_field(ContributorCriteriaScoreSerializer(many=True))
def get_criteria_scores(self, obj):
return ContributorCriteriaScore(
obj.contributorvideoratings.all()[0].criteria_scores.all(), many=True
return ContributorCriteriaScoreSerializer(
obj.single_contributor_rating.criteria_scores, many=True
).data

def get_is_public(self, obj) -> bool:
return obj.contributorvideoratings.all()[0].is_public
return obj.single_contributor_rating.is_public
9 changes: 9 additions & 0 deletions backend/tournesol/serializers/criteria_score.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from rest_framework.serializers import ModelSerializer

from tournesol.models import ContributorRatingCriteriaScore


class ContributorCriteriaScoreSerializer(ModelSerializer):
class Meta:
model = ContributorRatingCriteriaScore
fields = ["criteria", "score", "uncertainty"]
7 changes: 1 addition & 6 deletions backend/tournesol/serializers/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,7 @@ class Meta:
"tournesol_score",
"polls",
]
read_only_fields = [
# XXX: the `tournesol_score` field is available directly in the
# Entity model for now, but will be moved in an n-n relation
# between Entity and Poll
"tournesol_score"
]
read_only_fields = fields

@extend_schema_field(EntityPollSerializer(many=True))
def get_polls(self, obj):
Expand Down
41 changes: 40 additions & 1 deletion backend/tournesol/serializers/poll.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from drf_spectacular.utils import extend_schema_serializer
from rest_framework import serializers
from rest_framework.serializers import IntegerField, ModelSerializer

from tournesol.models import ContributorRating, CriteriaRank, Entity, EntityPollRating, Poll
from tournesol.models.entity_poll_rating import UNSAFE_REASONS
from tournesol.serializers.entity import EntityCriteriaScoreSerializer
from tournesol.serializers.entity import EntityCriteriaScoreSerializer, RelatedEntitySerializer


class PollCriteriaSerializer(ModelSerializer):
Expand Down Expand Up @@ -52,6 +53,15 @@ class Meta:
read_only_fields = fields


class ExtendedCollectiveRatingSerializer(CollectiveRatingSerializer):
criteria_scores = EntityCriteriaScoreSerializer(source="entity.criteria_scores", many=True)

class Meta:
model = CollectiveRatingSerializer.Meta.model
fields = CollectiveRatingSerializer.Meta.fields + ["criteria_scores"]
read_only_fields = fields


class IndividualRatingSerializer(ModelSerializer):
n_comparisons = IntegerField(read_only=True, default=0)

Expand All @@ -64,6 +74,24 @@ class Meta:
read_only_fields = fields


class RecommendationMetadataSerializer(serializers.Serializer):
total_score = serializers.FloatField(read_only=True, allow_null=True)


@extend_schema_serializer(
exclude_fields=[
# legacy fields have been moved to "entity", "collective_rating", etc.
"uid",
"type",
"n_comparisons",
"n_contributors",
"metadata",
"total_score",
"tournesol_score",
"criteria_scores",
"unsafe",
]
)
class RecommendationSerializer(ModelSerializer):
# pylint: disable=duplicate-code
n_comparisons = serializers.IntegerField(source="rating_n_ratings")
Expand All @@ -76,6 +104,14 @@ class RecommendationSerializer(ModelSerializer):
source="single_poll_rating", allow_null=True, default=None, read_only=True
)

entity = RelatedEntitySerializer(source="*", read_only=True)
collective_rating = ExtendedCollectiveRatingSerializer(
source="single_poll_rating",
read_only=True,
allow_null=True,
)
recommendation_metadata = RecommendationMetadataSerializer(source="*", read_only=True)

class Meta:
model = Entity
fields = [
Expand All @@ -88,6 +124,9 @@ class Meta:
"tournesol_score",
"criteria_scores",
"unsafe",
"entity",
"collective_rating",
"recommendation_metadata",
]
read_only_fields = fields

Expand Down
11 changes: 3 additions & 8 deletions backend/tournesol/serializers/rating.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@
from rest_framework.fields import BooleanField, CharField, DateTimeField
from rest_framework.serializers import ModelSerializer, Serializer

from tournesol.models import ContributorRating, ContributorRatingCriteriaScore
from tournesol.models import ContributorRating
from tournesol.serializers.criteria_score import ContributorCriteriaScoreSerializer
from tournesol.serializers.entity import EntityNoExtraFieldSerializer, RelatedEntitySerializer
from tournesol.serializers.poll import CollectiveRatingSerializer, IndividualRatingSerializer


class ContributorCriteriaScore(ModelSerializer):
class Meta:
model = ContributorRatingCriteriaScore
fields = ["criteria", "score", "uncertainty"]


class ExtendedInvididualRatingSerializer(IndividualRatingSerializer):
criteria_scores = ContributorCriteriaScore(many=True, read_only=True)
criteria_scores = ContributorCriteriaScoreSerializer(many=True, read_only=True)
last_compared_at = DateTimeField(read_only=True, allow_null=True)

class Meta:
Expand Down
1 change: 1 addition & 0 deletions backend/tournesol/tests/factories/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class VideoMetadataFactory(factory.DictFactory):
language = "aa"
uploader = factory.Sequence(lambda n: "Uploader %s" % n)
publication_date = factory.LazyFunction(lambda: datetime.date.today().isoformat())
views = 1000


class VideoFactory(EntityFactory):
Expand Down
Loading

0 comments on commit 5410405

Please sign in to comment.