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