Commit 7cc4b1b7 authored by njoyard's avatar njoyard

Merge pull request #24 from political-memory/fix_api

Fixed API
parents b8ea5a31 2cf53413
...@@ -7,7 +7,7 @@ python: ...@@ -7,7 +7,7 @@ python:
before_install: before_install:
- pip install codecov - pip install codecov
install: install:
- pip install $DJANGO pep8 flake8 pytest-django pytest-cov codecov django-responsediff mock - pip install $DJANGO pep8 flake8 pytest-django pytest-cov codecov django-responsediff==0.4.0 mock
- pip install https://github.com/political-memory/django-representatives/archive/parltrack.tar.gz#egg=django-representatives - pip install https://github.com/political-memory/django-representatives/archive/parltrack.tar.gz#egg=django-representatives
- pip install -e .[api] - pip install -e .[api]
script: script:
......
...@@ -49,13 +49,11 @@ class DossierViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -49,13 +49,11 @@ class DossierViewSet(viewsets.ReadOnlyModelViewSet):
'proposals__title' 'proposals__title'
) )
ordering_fields = ('id', 'reference') ordering_fields = ['reference']
def list(self, request):
return super(DossierViewSet, self).list(request)
def retrieve(self, request, pk=None): def retrieve(self, request, pk=None):
self.serializer_class = DossierDetailSerializer self.serializer_class = DossierDetailSerializer
self.queryset = self.queryset.prefetch_related('proposals')
return super(DossierViewSet, self).retrieve(request, pk) return super(DossierViewSet, self).retrieve(request, pk)
...@@ -65,7 +63,7 @@ class ProposalViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -65,7 +63,7 @@ class ProposalViewSet(viewsets.ReadOnlyModelViewSet):
""" """
pagination_class = DefaultWebPagination pagination_class = DefaultWebPagination
queryset = Proposal.objects.select_related('dossier') queryset = Proposal.objects.all()
serializer_class = ProposalSerializer serializer_class = ProposalSerializer
filter_backends = ( filter_backends = (
...@@ -92,10 +90,7 @@ class ProposalViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -92,10 +90,7 @@ class ProposalViewSet(viewsets.ReadOnlyModelViewSet):
'dossier__reference' 'dossier__reference'
) )
ordering_fields = ('id', 'reference') ordering_fields = ['reference']
def list(self, request):
return super(ProposalViewSet, self).list(request)
def retrieve(self, request, pk=None): def retrieve(self, request, pk=None):
self.serializer_class = ProposalDetailSerializer self.serializer_class = ProposalDetailSerializer
...@@ -123,9 +118,3 @@ class VoteViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -123,9 +118,3 @@ class VoteViewSet(viewsets.ReadOnlyModelViewSet):
'representative_name': ['exact', 'icontains'], 'representative_name': ['exact', 'icontains'],
'representative': ['exact'] 'representative': ['exact']
} }
def list(self, request):
return super(VoteViewSet, self).list(request)
def retrieve(self, request, pk=None):
return super(VoteViewSet, self).retrieve(request, pk)
# coding: utf-8 # coding: utf-8
import representatives_votes.models as models import representatives_votes.models as models
from representatives.models import Representative
from rest_framework import serializers from rest_framework import serializers
class VoteSerializer(serializers.ModelSerializer): class VoteSerializer(serializers.HyperlinkedModelSerializer):
""" """
Vote serializer Vote serializer
""" """
proposal = serializers.CharField(
source='proposal.fingerprint'
)
representative = serializers.CharField(
source='representative.fingerprint',
allow_null=True
)
class Meta: class Meta:
model = models.Vote model = models.Vote
fields = ( fields = (
'id',
'proposal', 'proposal',
'representative', 'representative',
'representative_name', 'representative_name',
'position' 'position'
) )
def to_internal_value(self, data):
data = super(VoteSerializer, self).to_internal_value(data)
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
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 ProposalSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = models.Proposal model = models.Proposal
fields = ( fields = (
'id',
'fingerprint',
'dossier', 'dossier',
'dossier_title',
'dossier_reference',
'title', 'title',
'description', 'description',
'reference', 'reference',
...@@ -76,56 +35,23 @@ class ProposalSerializer(serializers.ModelSerializer): ...@@ -76,56 +35,23 @@ class ProposalSerializer(serializers.ModelSerializer):
'url', 'url',
) )
def to_internal_value(self, data):
validated_data = super(ProposalSerializer, self).to_internal_value(
data)
validated_data['dossier'] = models.Dossier.objects.get(
fingerprint=validated_data['dossier']['fingerprint']
)
validated_data['votes'] = data['votes']
return validated_data
def _create_votes(self, votes_data, proposal):
for vote in votes_data:
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(
**validated_data
)
self._create_votes(votes_data, proposal)
return proposal
def update(self, instance, validated_data):
validated_data.pop('votes')
for attr, value in validated_data.iteritems():
setattr(instance, attr, value)
instance.save()
return instance
class ProposalDetailSerializer(ProposalSerializer): class ProposalDetailSerializer(ProposalSerializer):
""" Proposal serializer that includes votes """ """ Proposal serializer that includes votes """
votes = VoteSerializer(many=True) votes = VoteSerializer(many=True)
class Meta(ProposalSerializer.Meta): class Meta:
fields = ProposalSerializer.Meta.fields + ( model = models.Proposal
'votes', fields = ProposalSerializer.Meta.fields + ('votes',)
)
class DossierSerializer(serializers.ModelSerializer): class DossierSerializer(serializers.HyperlinkedModelSerializer):
""" Base dossier serializer """ """ Base dossier serializer """
class Meta: class Meta:
model = models.Dossier model = models.Dossier
fields = ( fields = (
'id',
'fingerprint',
'title', 'title',
'reference', 'reference',
'text', 'text',
...@@ -138,9 +64,9 @@ class DossierDetailSerializer(DossierSerializer): ...@@ -138,9 +64,9 @@ class DossierDetailSerializer(DossierSerializer):
""" """
Dossier serializer that includes proposals and votes. Dossier serializer that includes proposals and votes.
""" """
proposals = ProposalDetailSerializer(many=True)
class Meta(DossierSerializer.Meta): proposals = ProposalSerializer(many=True)
fields = DossierSerializer.Meta.fields + (
'proposals', class Meta:
) model = models.Dossier
field = DossierSerializer.Meta.fields + ('proposals',)
{
"url": "http://testserver/api/dossiers/1/",
"proposals": [
{
"dossier": "http://testserver/api/dossiers/1/",
"title": "A7-0234/2012 - Charles Goerens - § 31",
"description": "",
"reference": "A7-0234/2012",
"datetime": "2012-10-23T16:31:10Z",
"kind": "§ 31",
"total_abstain": 2,
"total_against": 0,
"total_for": 0,
"url": "http://testserver/api/proposals/1/"
},
{
"dossier": "http://testserver/api/dossiers/1/",
"title": "A7-0234/2012 - Charles Goerens - Résolution",
"description": "",
"reference": "A7-0234/2012",
"datetime": "2012-10-23T16:34:32Z",
"kind": "Résolution",
"total_abstain": 0,
"total_against": 0,
"total_for": 2,
"url": "http://testserver/api/proposals/2/"
}
],
"created": "2015-12-27T11:51:14.770000Z",
"updated": "2015-12-27T11:51:14.770000Z",
"fingerprint": "9e2cccdc5f6d22afd008af8b5b55dc193c27c5d6",
"title": "Agenda for change: the future of EU development policy",
"reference": "2012/2002(INI)",
"text": "",
"link": "http://www.europarl.europa.eu/oeil/popups/ficheprocedure.do?reference=2012/2002(INI)",
"ext_link": ""
}
\ No newline at end of file
[{"id":1,"fingerprint":"9e2cccdc5f6d22afd008af8b5b55dc193c27c5d6","title":"Agenda for change: the future of EU development policy","reference":"2012/2002(INI)","text":"","link":"http://www.europarl.europa.eu/oeil/popups/ficheprocedure.do?reference=2012/2002(INI)","url":"http://testserver/api/dossiers/1/?format=json"},{"id":2,"fingerprint":"e6856e0880e701c1022f23d595cc37a9a1cdcca8","title":"2016 general budget: all sections","reference":"2015/2132(BUD)","text":"","link":"http://www.europarl.europa.eu/oeil/popups/ficheprocedure.do?reference=2015/2132(BUD)","url":"http://testserver/api/dossiers/2/?format=json"}] [
\ No newline at end of file {
"title": "Agenda for change: the future of EU development policy",
"reference": "2012/2002(INI)",
"text": "",
"link": "http://www.europarl.europa.eu/oeil/popups/ficheprocedure.do?reference=2012/2002(INI)",
"url": "http://testserver/api/dossiers/1/"
},
{
"title": "2016 general budget: all sections",
"reference": "2015/2132(BUD)",
"text": "",
"link": "http://www.europarl.europa.eu/oeil/popups/ficheprocedure.do?reference=2015/2132(BUD)",
"url": "http://testserver/api/dossiers/2/"
}
]
\ No newline at end of file
{
"dossier": "http://testserver/api/dossiers/1/",
"title": "A7-0234/2012 - Charles Goerens - § 31",
"description": "",
"reference": "A7-0234/2012",
"datetime": "2012-10-23T16:31:10Z",
"kind": "§ 31",
"total_abstain": 2,
"total_against": 0,
"total_for": 0,
"url": "http://testserver/api/proposals/1/",
"votes": [
{
"proposal": "http://testserver/api/proposals/1/",
"representative": "http://testserver/api/representatives/2/",
"representative_name": "",
"position": "abstain"
},
{
"proposal": "http://testserver/api/proposals/1/",
"representative": "http://testserver/api/representatives/1/",
"representative_name": "",
"position": "abstain"
}
]
}
\ No newline at end of file
[{"id":1,"fingerprint":"6c1fb67feac3ab2ea567b37aa7dfd0d578bc04b4","dossier":"9e2cccdc5f6d22afd008af8b5b55dc193c27c5d6","dossier_title":"Agenda for change: the future of EU development policy","dossier_reference":"2012/2002(INI)","title":"A7-0234/2012 - Charles Goerens - § 31","description":"","reference":"A7-0234/2012","datetime":"2012-10-23T16:31:10Z","kind":"§ 31","total_abstain":2,"total_against":0,"total_for":0,"url":"http://testserver/api/proposals/1/?format=json"},{"id":2,"fingerprint":"18df8a97581832a95f3bdfb8c14ba2b05abb91e9","dossier":"9e2cccdc5f6d22afd008af8b5b55dc193c27c5d6","dossier_title":"Agenda for change: the future of EU development policy","dossier_reference":"2012/2002(INI)","title":"A7-0234/2012 - Charles Goerens - Résolution","description":"","reference":"A7-0234/2012","datetime":"2012-10-23T16:34:32Z","kind":"Résolution","total_abstain":0,"total_against":0,"total_for":2,"url":"http://testserver/api/proposals/2/?format=json"},{"id":3,"fingerprint":"4e575a1bc17602d23d2f0acf8ee482ebe08de79c","dossier":"e6856e0880e701c1022f23d595cc37a9a1cdcca8","dossier_title":"2016 general budget: all sections","dossier_reference":"2015/2132(BUD)","title":"A8-0298/2015 - José Manuel Fernandes et Gérard Deprez - Am 4","description":"","reference":"A8-0298/2015","datetime":"2015-10-28T11:59:35Z","kind":"Am 4","total_abstain":0,"total_against":2,"total_for":0,"url":"http://testserver/api/proposals/3/?format=json"},{"id":4,"fingerprint":"fc786534e832d9af7e2cdcd0b0952d7d60bb4331","dossier":"e6856e0880e701c1022f23d595cc37a9a1cdcca8","dossier_title":"2016 general budget: all sections","dossier_reference":"2015/2132(BUD)","title":"A8-0298/2015 - José Manuel Fernandes et Gérard Deprez - Am 29","description":"","reference":"A8-0298/2015","datetime":"2015-10-28T12:00:12Z","kind":"Am 29","total_abstain":0,"total_against":1,"total_for":1,"url":"http://testserver/api/proposals/4/?format=json"},{"id":5,"fingerprint":"90c022cf70dc6ceff796b876d3e1b814620c8637","dossier":"e6856e0880e701c1022f23d595cc37a9a1cdcca8","dossier_title":"2016 general budget: all sections","dossier_reference":"2015/2132(BUD)","title":"A8-0298/2015 - José Manuel Fernandes et Gérard Deprez - Am 31","description":"","reference":"A8-0298/2015","datetime":"2015-10-28T12:00:42Z","kind":"Am 31","total_abstain":1,"total_against":1,"total_for":0,"url":"http://testserver/api/proposals/5/?format=json"},{"id":6,"fingerprint":"5b3aa46182803c11d70ccfec666234b9fe2f44a7","dossier":"e6856e0880e701c1022f23d595cc37a9a1cdcca8","dossier_title":"2016 general budget: all sections","dossier_reference":"2015/2132(BUD)","title":"A8-0298/2015 - José Manuel Fernandes et Gérard Deprez - Am 30","description":"","reference":"A8-0298/2015","datetime":"2015-10-28T12:01:09Z","kind":"Am 30","total_abstain":0,"total_against":0,"total_for":2,"url":"http://testserver/api/proposals/6/?format=json"}] [
\ No newline at end of file {
"dossier": "http://testserver/api/dossiers/1/",
"title": "A7-0234/2012 - Charles Goerens - § 31",
"description": "",
"reference": "A7-0234/2012",
"datetime": "2012-10-23T16:31:10Z",
"kind": "§ 31",
"total_abstain": 2,
"total_against": 0,
"total_for": 0,
"url": "http://testserver/api/proposals/1/"
},
{
"dossier": "http://testserver/api/dossiers/1/",
"title": "A7-0234/2012 - Charles Goerens - Résolution",
"description": "",
"reference": "A7-0234/2012",
"datetime": "2012-10-23T16:34:32Z",
"kind": "Résolution",
"total_abstain": 0,
"total_against": 0,
"total_for": 2,
"url": "http://testserver/api/proposals/2/"
},
{
"dossier": "http://testserver/api/dossiers/2/",
"title": "A8-0298/2015 - José Manuel Fernandes et Gérard Deprez - Am 4",
"description": "",
"reference": "A8-0298/2015",
"datetime": "2015-10-28T11:59:35Z",
"kind": "Am 4",
"total_abstain": 0,
"total_against": 2,
"total_for": 0,
"url": "http://testserver/api/proposals/3/"
},
{
"dossier": "http://testserver/api/dossiers/2/",
"title": "A8-0298/2015 - José Manuel Fernandes et Gérard Deprez - Am 29",
"description": "",
"reference": "A8-0298/2015",
"datetime": "2015-10-28T12:00:12Z",
"kind": "Am 29",
"total_abstain": 0,
"total_against": 1,
"total_for": 1,
"url": "http://testserver/api/proposals/4/"
},
{
"dossier": "http://testserver/api/dossiers/2/",
"title": "A8-0298/2015 - José Manuel Fernandes et Gérard Deprez - Am 31",
"description": "",
"reference": "A8-0298/2015",
"datetime": "2015-10-28T12:00:42Z",
"kind": "Am 31",
"total_abstain": 1,
"total_against": 1,
"total_for": 0,
"url": "http://testserver/api/proposals/5/"
},
{
"dossier": "http://testserver/api/dossiers/2/",
"title": "A8-0298/2015 - José Manuel Fernandes et Gérard Deprez - Am 30",
"description": "",
"reference": "A8-0298/2015",
"datetime": "2015-10-28T12:01:09Z",
"kind": "Am 30",
"total_abstain": 0,
"total_against": 0,
"total_for": 2,
"url": "http://testserver/api/proposals/6/"
}
]
\ No newline at end of file
{
"proposal": "http://testserver/api/proposals/1/",
"representative": "http://testserver/api/representatives/2/",
"representative_name": "",
"position": "abstain"
}
\ No newline at end of file
[{"id":1,"proposal":"6c1fb67feac3ab2ea567b37aa7dfd0d578bc04b4","representative":"314d0f4c25af31bfa2a6b286838367994b902615","representative_name":"","position":"abstain"},{"id":2,"proposal":"6c1fb67feac3ab2ea567b37aa7dfd0d578bc04b4","representative":"2a3c90346d40e9c540050534d832ceb3e0d25a49","representative_name":"","position":"abstain"},{"id":3,"proposal":"18df8a97581832a95f3bdfb8c14ba2b05abb91e9","representative":"314d0f4c25af31bfa2a6b286838367994b902615","representative_name":"","position":"for"},{"id":4,"proposal":"18df8a97581832a95f3bdfb8c14ba2b05abb91e9","representative":"2a3c90346d40e9c540050534d832ceb3e0d25a49","representative_name":"","position":"for"},{"id":5,"proposal":"4e575a1bc17602d23d2f0acf8ee482ebe08de79c","representative":"314d0f4c25af31bfa2a6b286838367994b902615","representative_name":"","position":"against"},{"id":6,"proposal":"4e575a1bc17602d23d2f0acf8ee482ebe08de79c","representative":"2a3c90346d40e9c540050534d832ceb3e0d25a49","representative_name":"","position":"against"},{"id":7,"proposal":"fc786534e832d9af7e2cdcd0b0952d7d60bb4331","representative":"2a3c90346d40e9c540050534d832ceb3e0d25a49","representative_name":"","position":"for"},{"id":8,"proposal":"fc786534e832d9af7e2cdcd0b0952d7d60bb4331","representative":"314d0f4c25af31bfa2a6b286838367994b902615","representative_name":"","position":"against"},{"id":9,"proposal":"90c022cf70dc6ceff796b876d3e1b814620c8637","representative":"2a3c90346d40e9c540050534d832ceb3e0d25a49","representative_name":"","position":"abstain"},{"id":10,"proposal":"90c022cf70dc6ceff796b876d3e1b814620c8637","representative":"314d0f4c25af31bfa2a6b286838367994b902615","representative_name":"","position":"against"},{"id":11,"proposal":"5b3aa46182803c11d70ccfec666234b9fe2f44a7","representative":"2a3c90346d40e9c540050534d832ceb3e0d25a49","representative_name":"","position":"for"}] [
\ No newline at end of file {
"proposal": "http://testserver/api/proposals/1/",
"representative": "http://testserver/api/representatives/2/",
"representative_name": "",
"position": "abstain"
},
{
"proposal": "http://testserver/api/proposals/1/",
"representative": "http://testserver/api/representatives/1/",
"representative_name": "",
"position": "abstain"
},
{
"proposal": "http://testserver/api/proposals/2/",
"representative": "http://testserver/api/representatives/2/",
"representative_name": "",
"position": "for"
},
{
"proposal": "http://testserver/api/proposals/2/",
"representative": "http://testserver/api/representatives/1/",
"representative_name": "",
"position": "for"
},
{
"proposal": "http://testserver/api/proposals/3/",
"representative": "http://testserver/api/representatives/2/",
"representative_name": "",
"position": "against"
},
{
"proposal": "http://testserver/api/proposals/3/",
"representative": "http://testserver/api/representatives/1/",
"representative_name": "",
"position": "against"
},
{
"proposal": "http://testserver/api/proposals/4/",
"representative": "http://testserver/api/representatives/1/",
"representative_name": "",
"position": "for"
},
{
"proposal": "http://testserver/api/proposals/4/",
"representative": "http://testserver/api/representatives/2/",
"representative_name": "",
"position": "against"
},
{
"proposal": "http://testserver/api/proposals/5/",
"representative": "http://testserver/api/representatives/1/",
"representative_name": "",
"position": "abstain"
},
{
"proposal": "http://testserver/api/proposals/5/",
"representative": "http://testserver/api/representatives/2/",
"representative_name": "",
"position": "against"
},
{
"proposal": "http://testserver/api/proposals/6/",
"representative": "http://testserver/api/representatives/1/",
"representative_name": "",
"position": "for"
}
]
\ No newline at end of file
...@@ -8,14 +8,28 @@ class RepresentativeManagerTest(test.TestCase): ...@@ -8,14 +8,28 @@ class RepresentativeManagerTest(test.TestCase):
def functional_test(self, queries, url): def functional_test(self, queries, url):
with self.assertNumQueries(queries): with self.assertNumQueries(queries):
result = test.client.Client().get(url) result = test.client.Client().get(
url,
HTTP_ACCEPT='application/json; indent=4',
)
Response.for_test(self).assertNoDiff(result) Response.for_test(self).assertNoDiff(result)
def test_dossier(self):
# One for dossier + 1 for proposals
self.functional_test(2, '/api/dossiers/1/')
def test_dossiers(self): def test_dossiers(self):
self.functional_test(1, '/api/dossiers/?format=json') self.functional_test(1, '/api/dossiers/')
def test_proposal(self):
# One for proposal and dossier + 1 for votes
self.functional_test(2, '/api/proposals/1/')
def test_proposals(self): def test_proposals(self):
self.functional_test(1, '/api/proposals/?format=json') self.functional_test(1, '/api/proposals/')
def test_vote(self):
self.functional_test(1, '/api/votes/1/')
def test_votes(self): def test_votes(self):
self.functional_test(1, '/api/votes/?format=json') self.functional_test(1, '/api/votes/')
...@@ -8,7 +8,18 @@ from representatives_votes.api import ( ...@@ -8,7 +8,18 @@ from representatives_votes.api import (
VoteViewSet, VoteViewSet,
) )
from representatives.api import (
ConstituencyViewSet,
GroupViewSet,
MandateViewSet,
RepresentativeViewSet,
)
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register(r'constituencies', ConstituencyViewSet)
router.register(r'groups', GroupViewSet)
router.register(r'mandates', MandateViewSet)
router.register(r'representatives', RepresentativeViewSet)
router.register(r'dossiers', DossierViewSet) router.register(r'dossiers', DossierViewSet)
router.register(r'proposals', ProposalViewSet) router.register(r'proposals', ProposalViewSet)
router.register(r'votes', VoteViewSet) router.register(r'votes', VoteViewSet)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment