From d7547a877eeac0dc6bb6ab4c8f002bb9bd567ac6 Mon Sep 17 00:00:00 2001
From: Nicolas Joyard <joyard.nicolas@gmail.com>
Date: Sun, 29 May 2016 19:48:30 +0200
Subject: [PATCH] Replace ScoredVotes model with view-based VoteScore model

---
 memopol/api.py                                |  4 +-
 memopol/views.py                              |  4 +-
 representatives_recommendations/api.py        | 10 ++--
 .../migrations/0003_votescore.py              | 50 +++++++++++++++++++
 representatives_recommendations/models.py     | 33 ++++++------
 .../serializers.py                            | 16 ++----
 .../representative_detail.haml                |  2 +-
 7 files changed, 80 insertions(+), 39 deletions(-)
 create mode 100644 representatives_recommendations/migrations/0003_votescore.py

diff --git a/memopol/api.py b/memopol/api.py
index 2986d2af..40ae7fc6 100644
--- a/memopol/api.py
+++ b/memopol/api.py
@@ -17,7 +17,7 @@ from representatives_recommendations.api import (
     DossierScoreViewSet,
     RecommendationViewSet,
     RepresentativeScoreViewSet,
-    ScoredVoteViewSet
+    VoteScoreViewSet
 )
 
 
@@ -32,5 +32,5 @@ router.register(r'proposals', ProposalViewSet)
 router.register(r'recommendations', RecommendationViewSet)
 router.register(r'representatives', RepresentativeViewSet)
 router.register(r'scores', RepresentativeScoreViewSet)
-router.register(r'scored_votes', ScoredVoteViewSet)
+router.register(r'vote_scores', VoteScoreViewSet)
 router.register(r'votes', VoteViewSet)
diff --git a/memopol/views.py b/memopol/views.py
index e0f26602..9e0c6e3d 100644
--- a/memopol/views.py
+++ b/memopol/views.py
@@ -7,7 +7,7 @@ from representatives.models import Representative
 from representatives_votes import views as representatives_votes_views
 from representatives_votes.models import Dossier, Proposal
 from representatives_positions.forms import PositionForm
-from representatives_recommendations.models import ScoredVote
+from representatives_recommendations.models import VoteScore
 
 
 class RepresentativeList(
@@ -42,7 +42,7 @@ class RepresentativeDetail(representatives_views.RepresentativeDetail):
 
     def get_queryset(self):
         qs = super(RepresentativeDetail, self).get_queryset()
-        votes = ScoredVote.objects.filter(
+        votes = VoteScore.objects.filter(
             proposal__in=Proposal.objects.exclude(recommendation=None),
         ).select_related('proposal__recommendation')
         qs = qs.prefetch_related(models.Prefetch('votes', queryset=votes))
diff --git a/representatives_recommendations/api.py b/representatives_recommendations/api.py
index f33575b9..4d035c26 100644
--- a/representatives_recommendations/api.py
+++ b/representatives_recommendations/api.py
@@ -9,14 +9,14 @@ from .models import (
     DossierScore,
     Recommendation,
     RepresentativeScore,
-    ScoredVote
+    VoteScore
 )
 
 from .serializers import (
     DossierScoreSerializer,
     RecommendationSerializer,
     RepresentativeScoreSerializer,
-    ScoredVoteSerializer
+    VoteScoreSerializer
 )
 
 
@@ -85,12 +85,12 @@ class RepresentativeScoreViewSet(viewsets.ReadOnlyModelViewSet):
     serializer_class = RepresentativeScoreSerializer
 
 
-class ScoredVoteViewSet(viewsets.ReadOnlyModelViewSet):
+class VoteScoreViewSet(viewsets.ReadOnlyModelViewSet):
     """
     API endpoint to view votes with their score impact.
     This endpoint only shows votes that have a matching recommendation.
     """
-    queryset = ScoredVote.objects.select_related(
+    queryset = VoteScore.objects.select_related(
         'representative',
         'proposal',
         'proposal__dossier',
@@ -112,4 +112,4 @@ class ScoredVoteViewSet(viewsets.ReadOnlyModelViewSet):
     }
 
     pagination_class = DefaultWebPagination
-    serializer_class = ScoredVoteSerializer
+    serializer_class = VoteScoreSerializer
diff --git a/representatives_recommendations/migrations/0003_votescore.py b/representatives_recommendations/migrations/0003_votescore.py
new file mode 100644
index 00000000..7fee085c
--- /dev/null
+++ b/representatives_recommendations/migrations/0003_votescore.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('representatives_recommendations', '0002_dossierscore'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='VoteScore',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('position', models.CharField(max_length=10)),
+                ('score', models.IntegerField(default=0)),
+            ],
+            options={
+                'ordering': ['proposal__datetime'],
+                'db_table': 'representatives_recommendations_votescores',
+                'managed': False,
+            },
+        ),
+        migrations.DeleteModel(
+            name='ScoredVote',
+        ),
+        migrations.RunSQL(
+            """
+            CREATE VIEW "representatives_recommendations_votescores"
+            AS SELECT
+                "representatives_votes_vote"."id",
+                "representatives_votes_vote"."position",
+                "representatives_votes_vote"."proposal_id",
+                "representatives_votes_vote"."representative_id",
+                CASE WHEN "representatives_votes_vote"."position" = ("representatives_recommendations_recommendation"."recommendation")
+                    THEN "representatives_recommendations_recommendation"."weight"
+                    ELSE (0 - "representatives_recommendations_recommendation"."weight")
+                END AS "score"
+            FROM "representatives_votes_vote"
+            INNER JOIN "representatives_votes_proposal"
+                ON ( "representatives_votes_vote"."proposal_id" = "representatives_votes_proposal"."id" )
+            LEFT OUTER JOIN "representatives_recommendations_recommendation"
+                ON ( "representatives_votes_proposal"."id" = "representatives_recommendations_recommendation"."proposal_id" )
+            WHERE "representatives_recommendations_recommendation"."id" IS NOT NULL
+            """
+        )
+    ]
diff --git a/representatives_recommendations/models.py b/representatives_recommendations/models.py
index 49a6ecf5..89c042c6 100644
--- a/representatives_recommendations/models.py
+++ b/representatives_recommendations/models.py
@@ -1,7 +1,6 @@
 # coding: utf-8
 from django.db import models
 from django.db.models.signals import post_save
-from django.utils.functional import cached_property
 
 from representatives_votes.contrib.parltrack.import_votes import \
     vote_pre_import
@@ -23,6 +22,20 @@ class DossierScore(models.Model):
         db_table = 'representatives_recommendations_dossierscores'
 
 
+class VoteScore(models.Model):
+    proposal = models.ForeignKey(Proposal, related_name='votescores')
+
+    representative = models.ForeignKey(
+        Representative, related_name='votescores', null=True)
+    position = models.CharField(max_length=10)
+    score = models.IntegerField(default=0)
+
+    class Meta:
+        managed = False
+        ordering = ['proposal__datetime']
+        db_table = 'representatives_recommendations_votescores'
+
+
 class RepresentativeScore(models.Model):
     representative = models.OneToOneField('representatives.representative',
         primary_key=True, related_name='score')
@@ -44,20 +57,6 @@ class Recommendation(models.Model):
         ordering = ['proposal__datetime']
 
 
-class ScoredVote(Vote):
-    class Meta:
-        proxy = True
-
-    @cached_property
-    def absolute_score(self):
-        recommendation = self.proposal.recommendation
-
-        if self.position == recommendation.recommendation:
-            return recommendation.weight
-        else:
-            return -recommendation.weight
-
-
 def skip_votes(sender, vote_data=None, **kwargs):
     dossiers = getattr(sender, 'memopol_filters', None)
 
@@ -94,9 +93,9 @@ def calculate_representative_score(representative):
         proposal__recommendation=None
     ).select_related('proposal__recommendation')
 
-    votes = ScoredVote.objects.filter(pk__in=votes.values_list('pk'))
+    votes = VoteScore.objects.filter(pk__in=votes.values_list('pk'))
 
     for vote in votes:
-        score += vote.absolute_score
+        score += vote.score
 
     return score
diff --git a/representatives_recommendations/serializers.py b/representatives_recommendations/serializers.py
index 4a1ae0d0..330e53e4 100644
--- a/representatives_recommendations/serializers.py
+++ b/representatives_recommendations/serializers.py
@@ -4,7 +4,7 @@ from .models import (
     DossierScore,
     Recommendation,
     RepresentativeScore,
-    ScoredVote
+    VoteScore
 )
 
 
@@ -30,16 +30,8 @@ class RepresentativeScoreSerializer(serializers.HyperlinkedModelSerializer):
         fields = ('representative', 'score')
 
 
-class ScoredVoteSerializer(serializers.HyperlinkedModelSerializer):
-    """
-    Scored Vote serializer
-    """
+class VoteScoreSerializer(serializers.HyperlinkedModelSerializer):
 
     class Meta:
-        model = ScoredVote
-        fields = (
-            'proposal',
-            'representative',
-            'position',
-            'absolute_score'
-        )
+        model = VoteScore
+        fields = ('proposal', 'representative', 'position', 'score')
diff --git a/templates/representatives/representative_detail.haml b/templates/representatives/representative_detail.haml
index 2775cb9d..94629ffd 100644
--- a/templates/representatives/representative_detail.haml
+++ b/templates/representatives/representative_detail.haml
@@ -34,7 +34,7 @@
             %td.icon-cell
               = vote.position|position_icon
             %td.icon-cell
-              = vote.absolute_score|score_label
+              = vote.score|score_label
 
 
   %h2 Mandates
-- 
GitLab