diff --git a/representatives_votes/admin.py b/representatives_votes/admin.py index 0add9785eaa1f8b5a468717a62acc4489f64d6ab..6d185c496c71bf6e0717dbbe01b8e66df8c02ded 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 0000000000000000000000000000000000000000..5b553b537b692a96a77850964df2960fced2f9ab --- /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 0000000000000000000000000000000000000000..2939234f98be0a32e3a2172fee36e08ebb2bd6e6 --- /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 0000000000000000000000000000000000000000..1f897df1b20c95ebf878b9546bbac092b867f11e --- /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 08fdf8cb71437a31ed23cd3588da7eb7626ade65..e2292fd19cb5b48c8c3f4d68342da5a8abe65902 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 82ab573d60193a1a4781907e2e28fbba1f298e5c..d2b1a3cde0b47668f947e9484acb2da6bad8a094 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 630564ec893d5b13aaabc48b215454a3a72c6d27..b9004e698ce29ffee44595bedc08635a56c6dca9 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)