From df9bb01cb90623c343d038210057c62292bb3e0d Mon Sep 17 00:00:00 2001
From: Arnaud Fabre <arnaud.fabre@resel.fr>
Date: Thu, 9 Jul 2015 12:07:41 +0200
Subject: [PATCH] updates link between representatives / votes

---
 representatives_votes/admin.py                |  8 +--
 .../migrations/0002_auto_20150707_1611.py     | 35 ++++++++++++
 .../migrations/0003_auto_20150708_1358.py     | 24 ++++++++
 .../migrations/0004_auto_20150709_0819.py     | 27 +++++++++
 representatives_votes/models.py               | 29 ++++++++--
 representatives_votes/serializers.py          | 55 ++++++++++++++-----
 representatives_votes/tasks.py                | 39 +++++++------
 7 files changed, 177 insertions(+), 40 deletions(-)
 create mode 100644 representatives_votes/migrations/0002_auto_20150707_1611.py
 create mode 100644 representatives_votes/migrations/0003_auto_20150708_1358.py
 create mode 100644 representatives_votes/migrations/0004_auto_20150709_0819.py

diff --git a/representatives_votes/admin.py b/representatives_votes/admin.py
index 0add978..6d185c4 100644
--- a/representatives_votes/admin.py
+++ b/representatives_votes/admin.py
@@ -17,21 +17,21 @@ class ProposalAdmin(admin.ModelAdmin):
 
 
 class NoneMatchingFilter(admin.SimpleListFilter):
-    title = 'Remote id'
-    parameter_name = 'representative_remote_id'
+    title = 'Representative'
+    parameter_name = 'representative'
 
     def lookups(self, request, model_admin):
         return [('None', 'Unknown')]
     
     def queryset(self, request, queryset):
         if self.value() == 'None':
-            return queryset.filter(representative_remote_id=None)
+            return queryset.filter(representative=None)
         else:
             return queryset            
 
 
 class VoteAdmin(admin.ModelAdmin):
-    list_display = ('id', 'proposal_reference', 'position', 'representative_name', 'representative_remote_id')
+    list_display = ('id', 'proposal_reference', 'position', 'representative', 'representative_name')
     list_filter = (NoneMatchingFilter,)
     
     def proposal_reference(self, obj):
diff --git a/representatives_votes/migrations/0002_auto_20150707_1611.py b/representatives_votes/migrations/0002_auto_20150707_1611.py
new file mode 100644
index 0000000..5b553b5
--- /dev/null
+++ b/representatives_votes/migrations/0002_auto_20150707_1611.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('representatives', '0003_auto_20150702_1827'),
+        ('representatives_votes', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='vote',
+            name='representative_remote_id',
+        ),
+        migrations.AddField(
+            model_name='vote',
+            name='representative',
+            field=models.ForeignKey(related_name='votes', to='representatives.Representative', null=True),
+        ),
+        migrations.AddField(
+            model_name='vote',
+            name='representative_fingerprint',
+            field=models.CharField(max_length=200, blank=True),
+        ),
+        migrations.AlterField(
+            model_name='vote',
+            name='representative_name',
+            field=models.CharField(default='', max_length=200, blank=True),
+            preserve_default=False,
+        ),
+    ]
diff --git a/representatives_votes/migrations/0003_auto_20150708_1358.py b/representatives_votes/migrations/0003_auto_20150708_1358.py
new file mode 100644
index 0000000..2939234
--- /dev/null
+++ b/representatives_votes/migrations/0003_auto_20150708_1358.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('representatives', '0003_auto_20150702_1827'),
+        ('representatives_votes', '0002_auto_20150707_1611'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='vote',
+            name='representative_fingerprint',
+        ),
+        migrations.AddField(
+            model_name='proposal',
+            name='representatives',
+            field=models.ManyToManyField(to='representatives.Representative', through='representatives_votes.Vote'),
+        ),
+    ]
diff --git a/representatives_votes/migrations/0004_auto_20150709_0819.py b/representatives_votes/migrations/0004_auto_20150709_0819.py
new file mode 100644
index 0000000..1f897df
--- /dev/null
+++ b/representatives_votes/migrations/0004_auto_20150709_0819.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('representatives_votes', '0003_auto_20150708_1358'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='proposal',
+            options={'ordering': ['datetime']},
+        ),
+        migrations.AlterModelOptions(
+            name='vote',
+            options={'ordering': ['proposal__datetime']},
+        ),
+        migrations.AlterField(
+            model_name='proposal',
+            name='representatives',
+            field=models.ManyToManyField(related_name='proposals', through='representatives_votes.Vote', to='representatives.Representative'),
+        ),
+    ]
diff --git a/representatives_votes/models.py b/representatives_votes/models.py
index 08fdf8c..e2292fd 100644
--- a/representatives_votes/models.py
+++ b/representatives_votes/models.py
@@ -18,7 +18,8 @@
 
 from django.db import models
 
-from representatives.models import TimeStampedModel, HashableModel
+from representatives.models import TimeStampedModel, HashableModel, Representative
+
 
 class Dossier(HashableModel, TimeStampedModel):
     title = models.CharField(max_length=1000)
@@ -43,10 +44,25 @@ class Proposal(HashableModel, TimeStampedModel):
     total_against = models.IntegerField()
     total_for = models.IntegerField()
 
+    representatives = models.ManyToManyField(
+        Representative, through='Vote', related_name='proposals'
+    )
+    
     hashable_fields = ['dossier', 'title', 'reference',
                        'kind', 'total_abstain', 'total_against',
                        'total_for']
 
+    class Meta:
+        ordering = ['datetime']
+        
+        
+    @property
+    def status(self):
+        if self.total_for > self.total_against:
+            return 'adopted'
+        else:
+            return 'rejected'
+        
     def __unicode__(self):
         return unicode(self.title)
 
@@ -59,9 +75,12 @@ class Vote(models.Model):
     )
 
     proposal = models.ForeignKey(Proposal, related_name='votes')
-
-    # There are two representative fields for flexibility,
-    representative_name = models.CharField(max_length=200, blank=True, null=True)
-    representative_remote_id = models.CharField(max_length=200, blank=True, null=True)
+    
+    representative = models.ForeignKey(Representative, related_name='votes', null=True)
+    # Save representative name in case of we don't find the representative
+    representative_name = models.CharField(max_length=200, blank=True)
 
     position = models.CharField(max_length=10, choices=VOTECHOICES)
+
+    class Meta:
+        ordering = ['proposal__datetime']
diff --git a/representatives_votes/serializers.py b/representatives_votes/serializers.py
index 82ab573..d2b1a3c 100644
--- a/representatives_votes/serializers.py
+++ b/representatives_votes/serializers.py
@@ -19,23 +19,29 @@
 # Copyright (C) 2015 Arnaud Fabre <af@laquadrature.net>
 
 import representatives_votes.models as models
+from representatives.models import Representative
 from rest_framework import serializers
 
-from django.db import transaction
-
 
 class VoteSerializer(serializers.ModelSerializer):
+    """
+    Vote serializer
+    """
     proposal = serializers.CharField(
         source='proposal.fingerprint'
     )
+    representative = serializers.CharField(
+        source='representative.fingerprint',
+        allow_null=True
+    )
     
     class Meta:
         model = models.Vote
         fields = (
             'id',
             'proposal',
+            'representative',
             'representative_name',
-            'representative_remote_id',
             'position'
         )
 
@@ -44,6 +50,13 @@ class VoteSerializer(serializers.ModelSerializer):
         data['proposal'] = models.Proposal.objects.get(
             fingerprint=data['proposal']['fingerprint']
         )
+        if data['representative']['fingerprint']:
+            data['representative'] = Representative.objects.get(
+                fingerprint=data['representative']['fingerprint']
+            )
+        else:
+            data['representative'] = None
+
         return data
 
 
@@ -51,6 +64,16 @@ class ProposalSerializer(serializers.ModelSerializer):
     dossier = serializers.CharField(
         source='dossier.fingerprint'
     )
+
+    dossier_title = serializers.CharField(
+        source='dossier.title',
+        read_only=True
+    )
+
+    dossier_reference = serializers.CharField(
+        source='dossier.reference',
+        read_only=True
+    )
     
     class Meta:
         model = models.Proposal
@@ -58,6 +81,8 @@ class ProposalSerializer(serializers.ModelSerializer):
             'id',
             'fingerprint',
             'dossier',
+            'dossier_title',
+            'dossier_reference',
             'title',
             'description',
             'reference',
@@ -66,6 +91,7 @@ class ProposalSerializer(serializers.ModelSerializer):
             'total_abstain',
             'total_against',
             'total_for',
+            'url',
         )
 
     def to_internal_value(self, data):
@@ -75,14 +101,15 @@ class ProposalSerializer(serializers.ModelSerializer):
         )
         validated_data['votes'] = data['votes']
         return validated_data
-
+    
     def _create_votes(self, votes_data, proposal):
         for vote in votes_data:
-            vote['proposal'] = proposal
-            models.Vote.objects.create(
-                **vote
-            )
-        
+            serializer = VoteSerializer(data=vote)
+            if serializer.is_valid():
+                serializer.save()
+            else:
+                raise Exception(serializer.errors)
+            
     def create(self, validated_data):
         votes_data = validated_data.pop('votes')
         proposal = models.Proposal.objects.create(
@@ -100,6 +127,7 @@ class ProposalSerializer(serializers.ModelSerializer):
 
 
 class ProposalDetailSerializer(ProposalSerializer):
+    """ Proposal serializer that includes votes """
     votes = VoteSerializer(many=True)
     
     class Meta(ProposalSerializer.Meta):
@@ -109,6 +137,7 @@ class ProposalDetailSerializer(ProposalSerializer):
 
 
 class DossierSerializer(serializers.ModelSerializer):
+    """ Base dossier serializer """
     class Meta:
         model = models.Dossier
         fields = (
@@ -123,10 +152,10 @@ class DossierSerializer(serializers.ModelSerializer):
 
 
 class DossierDetailSerializer(DossierSerializer):
-    '''
-    Dossier Serializer with proposals details
-    '''
-
+    """ 
+    Dossier serializer that includes proposals
+    and votes 
+    """
     proposals = ProposalDetailSerializer(
         many = True,
     )
diff --git a/representatives_votes/tasks.py b/representatives_votes/tasks.py
index 630564e..b9004e6 100644
--- a/representatives_votes/tasks.py
+++ b/representatives_votes/tasks.py
@@ -35,7 +35,7 @@ from representatives_votes.serializers import DossierSerializer, ProposalSeriali
 
 logger = logging.getLogger(__name__)
 
-@shared_task
+
 def import_a_dossier_from_toutatis(fingerprint):
     '''
     Import a complete dossier from a toutatis server
@@ -54,37 +54,40 @@ def import_a_dossier_from_toutatis(fingerprint):
         raise Exception('Search should return one and only one result')
     detail_url = data['results'][0]['url']
     data = json.load(urlopen(detail_url))
-    import_a_model(data, Dossier, DossierSerializer)
+    dossier = import_a_model(data, Dossier, DossierSerializer)
     for proposal in data['proposals']:
         logger.info('Import proposal {}'.format(proposal['title']))
         import_a_model(proposal, Proposal, ProposalSerializer)
+    return dossier
 
-@shared_task
-def import_a_proposal_from_toutatis(fingerprint, delay=False):
+def import_a_proposal_from_toutatis(fingerprint):
     '''
     Import a partial dossier from a toutatis server
     '''
     toutatis_server = settings.TOUTATIS_SERVER
-    search_url = '{server}/api/proposals/?fingerprint={fingerprint}'.format({
-        'server': toutatis_server,
-        'fingerprint': fingerprint
-    })
+    search_url = '{}/api/proposals/?fingerprint={}'.format(
+        toutatis_server,
+        fingerprint
+    )
     logger.info('Import proposal with fingerprint {} from {}'.format(
         fingerprint,
         search_url
     ))
-    data = json.load(urlopen(search_url))
-    if data['count'] != 1:
+    proposal_data = json.load(urlopen(search_url))
+    if proposal_data['count'] != 1:
         raise Exception('Search should return one and only one result')
-    detail_url = data['results'][0]['url']
+    detail_url = proposal_data['results'][0]['url']
     proposal_data = json.load(urlopen(detail_url))
-    dossier_url = proposal_data['dossier']
-    dossier_data = json.load(urlopen(dossier_url))
-    dossier_data['proposals'] = [proposal_data]
-    if delay:
-        import_a_dossier.delay(dossier_data)
-    else:
-        import_a_dossier(dossier_data)
+    search_url = '{}/api/dossiers/?fingerprint={}'.format(
+        toutatis_server,
+        proposal_data['dossier']
+    )
+    dossier_data = json.load(urlopen(search_url))
+    if dossier_data['count'] != 1:
+        raise Exception('Search should return one and only one result')
+    import_a_model(dossier_data['results'][0], Dossier, DossierSerializer)
+    return import_a_model(proposal_data, Proposal, ProposalSerializer)
+
 
 def export_a_dossier(dossier):
     serialized = DossierDetailSerializer(dossier)
-- 
GitLab