From 5280efb4dab7a4e4e6832542b9f70a3488e73c35 Mon Sep 17 00:00:00 2001
From: Arnaud Fabre <arnaud.fabre@camobscura.fr>
Date: Mon, 1 Jun 2015 15:56:51 +0200
Subject: [PATCH] updates dossier serializers

---
 .../commands/import_dossier_from_toutatis.py  |  38 +++-
 .../migrations/0006_auto_20150601_1121.py     |  38 ++++
 representatives_votes/models.py               |  26 ++-
 representatives_votes/serializers.py          | 163 ++++++++++++++++++
 representatives_votes/utils.py                | 108 ++++--------
 5 files changed, 295 insertions(+), 78 deletions(-)
 create mode 100644 representatives_votes/migrations/0006_auto_20150601_1121.py
 create mode 100644 representatives_votes/serializers.py

diff --git a/representatives_votes/management/commands/import_dossier_from_toutatis.py b/representatives_votes/management/commands/import_dossier_from_toutatis.py
index 76beeab..91c24a6 100644
--- a/representatives_votes/management/commands/import_dossier_from_toutatis.py
+++ b/representatives_votes/management/commands/import_dossier_from_toutatis.py
@@ -1,11 +1,43 @@
+# coding: utf-8
+
+# This file is part of toutatis.
+#
+# toutatis is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of
+# the License, or any later version.
+#
+# toutatis is distributed in the hope that it will
+# be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Affero Public
+# License along with Foobar.
+# If not, see <http://www.gnu.org/licenses/>.
+#
+# Copyright (C) 2013 Laurent Peuch <cortex@worlddomination.be>
+# Copyright (C) 2015 Arnaud Fabre <af@laquadrature.net>
+
 import json
-import urllib2
+from urllib2 import urlopen
 
 from django.core.management.base import BaseCommand
 from django.conf import settings
 
+from representatives_votes.utils import import_a_dossier
 
 class Command(BaseCommand):
     def handle(self, *args, **options):
-        dossier_ref = args[0]
-        print(dossier_ref)
+        reference = args[0]
+        toutatis_server = getattr(settings,
+                                  'TOUTATIS_SERVER',
+                                  'http://toutatis.mm.staz.be')
+        search_url = toutatis_server + '/api/dossiers/?reference=%s' % reference
+        data = json.load(urlopen(search_url))
+        if len(data) != 1:
+            raise Exception('Search should return one and only one result')
+        detail_url = data[0]['url']
+        data = json.load(urlopen(detail_url))
+
+        import_a_dossier(data)
diff --git a/representatives_votes/migrations/0006_auto_20150601_1121.py b/representatives_votes/migrations/0006_auto_20150601_1121.py
new file mode 100644
index 0000000..e3dc841
--- /dev/null
+++ b/representatives_votes/migrations/0006_auto_20150601_1121.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('representatives_votes', '0005_auto_20150521_1529'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='dossier',
+            name='text',
+            field=models.TextField(null=True, blank=True),
+            preserve_default=True,
+        ),
+        migrations.AlterField(
+            model_name='proposal',
+            name='description',
+            field=models.TextField(null=True, blank=True),
+            preserve_default=True,
+        ),
+        migrations.AlterField(
+            model_name='proposal',
+            name='dossier',
+            field=models.ForeignKey(related_name='proposals', to='representatives_votes.Dossier'),
+            preserve_default=True,
+        ),
+        migrations.AlterField(
+            model_name='vote',
+            name='proposal',
+            field=models.ForeignKey(related_name='votes', to='representatives_votes.Proposal'),
+            preserve_default=True,
+        ),
+    ]
diff --git a/representatives_votes/models.py b/representatives_votes/models.py
index c8da255..cbe712b 100644
--- a/representatives_votes/models.py
+++ b/representatives_votes/models.py
@@ -1,19 +1,35 @@
 # coding: utf-8
 
+# This file is part of toutatis.
+#
+# toutatis is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of
+# the License, or any later version.
+#
+# toutatis is distributed in the hope that it will
+# be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Affero Public
+# License along with django-representatives.
+# If not, see <http://www.gnu.org/licenses/>.
+
 from django.db import models
 
 
 class Dossier(models.Model):
     title = models.CharField(max_length=1000)
     reference = models.CharField(max_length=200)
-    text = models.TextField()
+    text = models.TextField(blank=True, null=True)
     link = models.URLField()
 
 
 class Proposal(models.Model):
-    dossier = models.ForeignKey(Dossier)
+    dossier = models.ForeignKey(Dossier, related_name='proposals')
     title = models.CharField(max_length=1000)
-    description = models.TextField()
+    description = models.TextField(blank=True, null=True)
     reference = models.CharField(max_length=200, null=True)
     datetime = models.DateTimeField()
     kind = models.CharField(max_length=200, null=True)
@@ -28,7 +44,7 @@ class Proposal(models.Model):
                  'representative_name': vote.representative_name
              }
             for vote in self.vote_set.all()]
-        
+
 
 class Vote(models.Model):
     VOTECHOICES = (
@@ -37,7 +53,7 @@ class Vote(models.Model):
         ('against', 'against')
     )
 
-    proposal = models.ForeignKey(Proposal)
+    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)
diff --git a/representatives_votes/serializers.py b/representatives_votes/serializers.py
new file mode 100644
index 0000000..6f23929
--- /dev/null
+++ b/representatives_votes/serializers.py
@@ -0,0 +1,163 @@
+# coding: utf-8
+
+# This file is part of toutatis.
+#
+# toutatis is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of
+# the License, or any later version.
+#
+# toutatis is distributed in the hope that it will
+# be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Affero Public
+# License along with django-representatives.
+# If not, see <http://www.gnu.org/licenses/>.
+#
+# Copyright (C) 2015 Arnaud Fabre <af@laquadrature.net>
+
+import representatives_votes.models as models
+from rest_framework import serializers
+
+from django.db import transaction
+from django.db import connection
+from django.db.utils import OperationalError
+
+def truncate_model(model):
+    cursor = connection.cursor()
+    try:
+        cursor.execute('TRUNCATE TABLE "{0}"'.format(model._meta.db_table))
+    except OperationalError:
+        cursor.execute('DELETE FROM "{0}"'.format(model._meta.db_table))        
+
+
+class VoteSerializer(serializers.ModelSerializer):
+    '''
+    Serializer for votes
+    '''
+    class Meta:
+        model = models.Vote
+        fields = (
+            'representative_name',
+            'representative_remote_id',
+            'position'
+        )
+
+
+class ProposalSerializer(serializers.ModelSerializer):
+    '''
+    Base Proposal Serializer
+    '''
+    class Meta:
+        model = models.Proposal
+        fields = (
+            'title',
+            'description',
+            'reference',
+            'datetime',
+            'kind',
+            'total_abstain',
+            'total_against',
+            'total_for',
+        )
+
+
+class ProposalHyperLinkedSerializer(ProposalSerializer):
+    '''
+    Proposal Serializer with hyperlink to dossier (used for listing)
+    '''
+    dossier = serializers.HyperlinkedRelatedField(
+        read_only = True,
+        view_name = 'dossier-detail',
+    )
+
+    class Meta(ProposalSerializer.Meta):
+        fields = ('dossier', 'url',) + ProposalSerializer.Meta.fields
+
+
+class ProposalDetailSerializer(ProposalSerializer):
+    '''
+    Proposal Serializer with votes detail (used in Dossier Detail)
+    '''
+    votes = VoteSerializer(many=True)
+    
+    class Meta(ProposalSerializer.Meta):
+        fields = ProposalSerializer.Meta.fields + (
+            'votes',
+        )
+
+
+class ProposalDetailHyperLinkedSerializer(ProposalDetailSerializer, ProposalHyperLinkedSerializer):
+    '''
+    Proposal Serializer combined Detail Serializer and Hyperlinked Serializer
+    '''
+    class Meta(ProposalSerializer.Meta):
+        fields = ('dossier',) + ProposalSerializer.Meta.fields + (
+            'votes',
+        )
+
+
+class DossierSerializer(serializers.ModelSerializer):
+    '''
+    Base Dossier Serializer
+    '''
+    class Meta:
+        model = models.Dossier
+        fields = (
+            'title',
+            'reference',
+            'text',
+            'link',
+        )
+
+
+class DossierHyperLinkedSerializer(DossierSerializer):
+    '''
+    Dossier Serializer with hyperlinks to proposals
+    '''
+    proposals = serializers.HyperlinkedRelatedField(
+        many=True,
+        read_only=True,
+        view_name='proposal-detail',
+    )
+
+    class Meta(DossierSerializer.Meta):
+        fields = DossierSerializer.Meta.fields + (
+            'url',
+            'proposals',
+        )
+
+
+class DossierDetailSerializer(DossierSerializer):
+    '''
+    Dossier Serializer with proposals details
+    '''
+    proposals = ProposalDetailSerializer(
+        many=True,
+    )
+
+    class Meta(DossierSerializer.Meta):
+        fields = DossierSerializer.Meta.fields + (
+            'proposals',
+        )
+
+    @transaction.atomic
+    def create(self, validated_data):
+        proposals_data = validated_data.pop('proposals')
+        dossier, _ = models.Dossier.objects.get_or_create(**validated_data)
+
+        self._create_proposals(proposals_data, dossier)
+        return dossier
+
+    def _create_proposals(self, proposals_data, dossier):
+        truncate_model(models.Proposal)
+        truncate_model(models.Vote)
+        for proposal_data in proposals_data:
+            votes_data = proposal_data.pop('votes')
+            proposal_data['dossier'] = dossier
+            proposal = models.Proposal.objects.create(**proposal_data)
+            for vote_data in votes_data:
+                vote_data['proposal'] = proposal
+                models.Vote.objects.create(**vote_data)
diff --git a/representatives_votes/utils.py b/representatives_votes/utils.py
index 71ed719..d45be86 100644
--- a/representatives_votes/utils.py
+++ b/representatives_votes/utils.py
@@ -1,74 +1,42 @@
-from dateutil.parser import parse as date_parse
-from .models import Dossier, Proposal, Vote
-import datetime
-# Export
-
-def export_all_dossiers():
-    return [export_a_dossier(dossier) for dossier in Dossier.objects.all()]
+# coding: utf-8
+
+# This file is part of toutatis.
+#
+# toutatis is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of
+# the License, or any later version.
+#
+# toutatis is distributed in the hope that it will
+# be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Affero Public
+# License along with django-representatives.
+# If not, see <http://www.gnu.org/licenses/>.
+#
+# Copyright (C) 2015 Arnaud Fabre <af@laquadrature.net>
+
+from representatives_votes.models import Dossier
+from representatives_votes.serializers import DossierDetailSerializer
+
+# Import a dossier
+def import_a_dossier(data):
+    serializer = DossierDetailSerializer(data=data)
+    print(serializer.is_valid())
+    print(serializer.save())
+    
+def import_dossiers(data):
+    return [import_a_dossier(d_data) for d_data in data]
 
+# Export a dossier
 def export_a_dossier(dossier):
-    ret = {'dossier': {
-        'title': dossier.title,
-        'reference': dossier.reference,
-        'text': dossier.text
-    }}
-
-    ret['proposals'] = [export_a_proposal(proposal) for proposal in dossier.proposal_set.all()]
-    return ret
-
-def export_a_proposal(proposal):
-    ret = {
-        'title': proposal.title,
-        'reference': proposal.reference,
-        'description': proposal.description,
-        'datetime': proposal.datetime.isoformat(),
-        'kind': proposal.kind,
-        'total_abstain': proposal.total_abstain,
-        'total_against': proposal.total_against,
-        'total_for': proposal.total_for
-    }
-
-    ret['votes'] = [export_a_vote(vote) for vote in proposal.vote_set.all()]
-    return ret
+    serialized = DossierDetailSerializer(dossier)
+    return serialized.data
 
-def export_a_vote(vote):
-    return {
-        'representative_name': vote.representative_name,
-        'representative_remote_id': vote.representative_remote_id,
-        'postion': vote.position
-    }
-
-# Import
-
-def import_a_dossier(dossier_data):
-    dossier, created = Dossier.objects.get_or_create(reference=dossier_data['reference'])
-
-    if created:
-        dossier_attr = ['title', 'text', 'link']
-        for attr in dossier_attr:
-            setattr(dossier, attr, dossier_data[attr])
-        dossier.save()
-
-    dossier.proposal_set.all().delete()
-    for proposal_data in dossier_data['proposals']:
-        import_a_proposal(proposal_data, dossier)
-        
-def import_a_proposal(proposal_data, dossier):
-    proposal = Proposal.objects.create(
-        dossier=dossier,
-        title=proposal_data['title'],
-        description=proposal_data['description'],
-        reference=proposal_data['reference'],
-        datetime=date_parse(proposal_data['datetime']),
-        kind=proposal_data['kind'],
-        total_abstain=proposal_data['total_abstain'],
-        total_against=proposal_data['total_against'],
-        total_for=proposal_data['total_for']        
-    )
-    
-    for vote_data in proposal_data['votes']:
-        import_a_vote(vote_data, proposal)
+def export_dossiers(filters={}):
+    return [export_a_dossier(dossier) for dossier in Dossier.objects.filter(**filters)]
 
-def import_a_vote(vote_data, proposal):
-    vote_data['proposal'] = proposal
-    Vote.objects.create(**vote_data)
+def export_all_dossier():
+    return export_dossiers()
-- 
GitLab