diff --git a/.gitignore b/.gitignore
index 651fa196ece8748d41857af527b62ec936c3eb74..91a88847d62beef224d2d73834ed5de1cb1dd351 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,3 @@
-# Django apps
-
-compotista_django-representatives
-representatives
-chronograph
-
*.sqlite3
# SASS Cache
diff --git a/legislature/migrations/0001_initial.py b/legislature/migrations/0001_initial.py
deleted file mode 100644
index c7559608ac447dfcfe3356bd8ad55da3e2b6fa80..0000000000000000000000000000000000000000
--- a/legislature/migrations/0001_initial.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import models, migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('representatives', '0001_initial'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='MemopolGroup',
- fields=[
- ('group', models.OneToOneField(parent_link=True, primary_key=True, serialize=False, to='representatives.Group')),
- ('active', models.BooleanField(default=False)),
- ],
- options={
- },
- bases=('representatives.group',),
- ),
- migrations.CreateModel(
- name='MemopolRepresentative',
- fields=[
- ('representative_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='representatives.Representative')),
- ('representative_remote_id', models.CharField(unique=True, max_length=255)),
- ('score', models.IntegerField(default=0)),
- ('country', models.ForeignKey(to='representatives.Country', null=True)),
- ],
- options={
- },
- bases=('representatives.representative',),
- ),
- ]
diff --git a/legislature/migrations/__init__.py b/legislature/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/legislature/models.py b/legislature/models.py
index ef1faec54c71a440ab2d08f067f4f00d075119ac..834d4fdc3a00766f1cb5e00c8c043d508f840cd4 100644
--- a/legislature/models.py
+++ b/legislature/models.py
@@ -22,11 +22,29 @@ from datetime import datetime
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+from django.utils.functional import cached_property
-from representatives.models import Representative, Group, Country
+from representatives.models import Representative, Mandate, Country
from representatives_votes.models import Vote
+from core.utils import create_child_instance_from_parent
-class MemopolRepresentative(Representative):
+
+class MemopolRepresentative(models.Model):
+
+ # We should link a memopol representative to a representative based
+ # on the remote_id attribute
+ parent_identifier = 'remote_id'
+ child_parent_identifier = 'representative_remote_id'
+
+ representative = models.OneToOneField(
+ Representative,
+ parent_link=True,
+ related_name='extra',
+ null=True,
+ on_delete=models.SET_NULL
+ )
representative_remote_id = models.CharField(max_length=255, unique=True)
country = models.ForeignKey(Country, null=True)
@@ -34,14 +52,23 @@ class MemopolRepresentative(Representative):
def update_score(self):
score = 0
- for vote in self.representative.votes.all():
- proposal = vote.m_proposal
- if proposal.recommendation:
- recommendation = proposal.recommendation
- if vote.position != recommendation.recommendation:
- score -= recommendation.weight
- else:
- score += recommendation.weight
+ for vote in self.votes.all():
+ proposal = vote.proposal
+ try:
+ if proposal.recommendation:
+ recommendation = proposal.recommendation
+ if ( vote.position != recommendation.recommendation
+ and (
+ vote.position == 'abstain' or
+ recommendation.recommendation == 'abstain' )):
+ score -= (recommendation.weight / 2)
+ elif vote.position != recommendation.recommendation:
+ score -= recommendation.weight
+ else:
+ score += recommendation.weight
+ except Exception:
+ pass
+
self.score = score
self.save()
@@ -66,11 +93,11 @@ class MemopolRepresentative(Representative):
self.save()
- # @property
- # def votes(self):
- # return Vote.objects.filter(
- # representative_remote_id = self.remote_id
- # )
+ @cached_property
+ def votes(self):
+ return Vote.objects.filter(
+ representative_remote_id = self.remote_id
+ )
def active_mandates(self):
return self.mandates.filter(
@@ -88,20 +115,19 @@ class MemopolRepresentative(Representative):
group__kind='group'
)
-class MemopolGroup(Group):
- group = models.OneToOneField(
- Group,
- parent_link = True
- )
-
- active = models.BooleanField(default=False)
-
- def update_active(self):
- self.active = False
- for mandate in self.mandates.all():
- if mandate.end_date > datetime.date(datetime.now()):
- self.active = True
- break
- self.save()
+@receiver(post_save, sender=Representative)
+def create_memopolrepresentative_from_representative(instance, **kwargs):
+ # create_child_instance_from_parent(MemopolRepresentative, instance)
+ pass
+
+
+@receiver(post_save, sender=Mandate)
+def update_memopolrepresentative_country(instance, created, **kwargs):
+ return
+ if not created:
+ return
+ # Update representative country
+ if instance.group.kind == 'country' and instance.representative.extra.country == None:
+ instance.representative.extra.update_country()
diff --git a/legislature/templates/legislature/representative_view.haml b/legislature/templates/legislature/representative_view.haml
index 2765b1e43ba5b2b2631f3c7ceb1fceef1b163b0b..2ccd1347336cea9a7dbfb08a8d55abd4b50b3895 100644
--- a/legislature/templates/legislature/representative_view.haml
+++ b/legislature/templates/legislature/representative_view.haml
@@ -9,6 +9,9 @@
%h1= representative.full_name
+ %h2
+ SCORE : {{ representative.extra.score }}
+
%p
%strong
%a{:href => "{{ representative.current_group_mandate|by_group_url }}"}
diff --git a/legislature/views.py b/legislature/views.py
index 334892a43fb6902e7f6c28a374d89544cd86b277..56299c43bcb17f2f79fffadd4ceaba1c19f75fd8 100644
--- a/legislature/views.py
+++ b/legislature/views.py
@@ -132,8 +132,8 @@ def _render_list(request, representative_list, num_by_page=30):
def groups_by_kind(request, kind):
groups = Group.objects.filter(
kind=kind,
- memopolgroup__active=True
- )
+ mandates__end_date__gte=datetime.now()
+ ).distinct().order_by('name')
return render(
request,
diff --git a/representatives b/representatives
new file mode 120000
index 0000000000000000000000000000000000000000..f6fb692fff737aa5ccfa923fe6b6356ab3d88f99
--- /dev/null
+++ b/representatives
@@ -0,0 +1 @@
+../django-representatives/representatives
\ No newline at end of file
diff --git a/votes/admin.py b/votes/admin.py
index ef533b9ca016fe044ab2a8baabbf59a2624945db..3542eee6e5b32ad515cff9cfdcc2697444d5b66a 100644
--- a/votes/admin.py
+++ b/votes/admin.py
@@ -20,16 +20,45 @@
from __future__ import absolute_import
from django.contrib import admin
+from django.core.urlresolvers import reverse
+
+from .admin_views import import_vote_with_recommendation, import_vote
+from .models import Recommendation, MemopolDossier
-from .admin_import_vote import import_vote_with_recommendation, import_vote
-from .models import Recommendation
admin.site.register_view('import_vote', view=import_vote)
admin.site.register_view('import_vote_with_recommendation', view=import_vote_with_recommendation)
+def link_to_edit(obj, field):
+ try:
+ related_obj = getattr(obj, field)
+ url = reverse(
+ 'admin:{}_{}_change'.format(
+ related_obj._meta.app_label,
+ related_obj._meta.object_name.lower()
+ ),
+ args=(related_obj.pk,)
+
+ )
+ return ' {obj}'.format(url=url,obj=related_obj)
+
+ except:
+ return '???'
+
+class MemopolDossierAdmin(admin.ModelAdmin):
+
+ list_display = ('name', 'dossier')
+ search_fields = ('name',)
+
class RecommendationsAdmin(admin.ModelAdmin):
- list_display = ('title', 'recommendation', 'proposal', 'weight')
+
+ def link_to_proposal(self):
+ return link_to_edit(self, 'proposal')
+ link_to_proposal.allow_tags = True
+
+ list_display = ('id', 'title', link_to_proposal, 'recommendation','weight')
search_fields = ('title', 'recommendation', 'proposal')
+admin.site.register(MemopolDossier, MemopolDossierAdmin)
admin.site.register(Recommendation, RecommendationsAdmin)
diff --git a/votes/admin_import_vote.py b/votes/admin_views.py
similarity index 96%
rename from votes/admin_import_vote.py
rename to votes/admin_views.py
index f192d0ab240f357ad8e6c41528a215164dc42e4a..b2ea179806eed4780016c86cf7a828d2399c0a8c 100644
--- a/votes/admin_import_vote.py
+++ b/votes/admin_views.py
@@ -106,10 +106,9 @@ def import_vote(request):
else:
proposal_id = request.GET.get('import', None)
if proposal_id:
- api_url = '{}/api/proposals/{}'.format(toutatis_server, proposal_id)
- proposal = requests.get(api_url).json()
+ # api_url = '{}/api/proposals/{}'.format(toutatis_server, proposal_id)
+ # proposal = requests.get(api_url).json()
-
call_command('import_proposal_from_toutatis', proposal_id, interactive=False)
# call_command('update_memopol_votes', proposal['dossier_reference'], interactive=False)
return redirect('/admin/')
diff --git a/votes/admin_views_flymake.py b/votes/admin_views_flymake.py
new file mode 100644
index 0000000000000000000000000000000000000000..b2ea179806eed4780016c86cf7a828d2399c0a8c
--- /dev/null
+++ b/votes/admin_views_flymake.py
@@ -0,0 +1,117 @@
+# coding: utf-8
+
+# This file is part of memopol.
+#
+# memopol 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.
+#
+# memopol 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 .
+#
+# Copyright (C) 2015 Arnaud Fabre
+from __future__ import absolute_import
+
+from django.conf import settings
+from django.shortcuts import render, redirect
+from django import forms
+from django.core.management import call_command
+
+import requests
+
+from representatives_votes.models import Proposal
+from .forms import RecommendationForm
+
+
+class SearchForm(forms.Form):
+ query = forms.CharField(label='Search', max_length=100)
+
+
+def import_vote_with_recommendation(request):
+ context = {}
+ toutatis_server = getattr(settings,
+ 'TOUTATIS_SERVER',
+ 'http://toutatis.mm.staz.be')
+
+ if request.method == 'POST' and 'search' in request.POST:
+ form = SearchForm(request.POST)
+ if form.is_valid():
+ query = form.cleaned_data['query']
+ context['api_url'] = '{}/api/proposals/?search={}&limit=1000'.format(
+ toutatis_server,
+ query
+ )
+ r = requests.get(context['api_url'])
+ context['results'] = r.json()
+ elif request.method == 'POST' and 'create_recommendation' in request.POST:
+ form = RecommendationForm(data=request.POST)
+ if form.is_valid():
+ # First import proposal
+ proposal_id = int(request.POST['proposal_id'])
+ api_url = '{}/api/proposals/{}'.format(toutatis_server, proposal_id)
+ proposal = requests.get(api_url).json()
+
+ call_command('import_proposal_from_toutatis', proposal_id, interactive=False)
+ # call_command('update_memopol_votes', proposal['dossier_reference'], interactive=False)
+
+ memopol_proposal = Proposal.objects.get(
+ title = proposal['title'],
+ datetime = proposal['datetime'],
+ kind = proposal['kind'],
+ )
+ recommendation = form.save(commit=False)
+ recommendation.proposal = memopol_proposal
+ recommendation.save()
+
+ return redirect('/admin/votes/recommendation/')
+ else:
+ proposal_id = request.GET.get('import', None)
+ if proposal_id:
+ api_url = '{}/api/proposals/{}'.format(toutatis_server, proposal_id)
+ proposal = requests.get(api_url).json()
+
+ context['recommendation_proposal_title'] = proposal['title']
+ context['recommendation_proposal_dossier_title'] = proposal['dossier_title']
+ context['recommendation_proposal_id'] = proposal_id
+ context['recommendation_form'] = RecommendationForm()
+ form = SearchForm()
+
+ context['form'] = form
+ return render(request, 'votes/admin/import.html', context)
+
+def import_vote(request):
+ context = {}
+ toutatis_server = getattr(settings,
+ 'TOUTATIS_SERVER',
+ 'http://toutatis.mm.staz.be')
+
+ if request.method == 'POST' and 'search' in request.POST:
+ print(request.POST)
+ form = SearchForm(request.POST)
+ if form.is_valid():
+ query = form.cleaned_data['query']
+ context['api_url'] = '{}/api/proposals/?search={}&limit=1000'.format(
+ toutatis_server,
+ query
+ )
+ r = requests.get(context['api_url'])
+ context['results'] = r.json()
+ else:
+ proposal_id = request.GET.get('import', None)
+ if proposal_id:
+ # api_url = '{}/api/proposals/{}'.format(toutatis_server, proposal_id)
+ # proposal = requests.get(api_url).json()
+
+ call_command('import_proposal_from_toutatis', proposal_id, interactive=False)
+ # call_command('update_memopol_votes', proposal['dossier_reference'], interactive=False)
+ return redirect('/admin/')
+ form = SearchForm()
+ context['form'] = form
+ return render(request, 'votes/admin/import.html', context)
diff --git a/votes/migrations/0002_auto_20150616_1516.py b/votes/migrations/0002_auto_20150616_1516.py
new file mode 100644
index 0000000000000000000000000000000000000000..a618c6552a0fc55e398591f97dcd2972254920aa
--- /dev/null
+++ b/votes/migrations/0002_auto_20150616_1516.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import core.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('representatives_votes', '0002_auto_20150616_1249'),
+ ('votes', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='memopoldossier',
+ name='dossier_ptr',
+ ),
+ migrations.AddField(
+ model_name='memopoldossier',
+ name='dossier',
+ field=core.fields.AutoOneToOneField(primary_key=True, default=0, serialize=False, to='representatives_votes.Dossier'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/votes/migrations/0003_auto_20150616_1523.py b/votes/migrations/0003_auto_20150616_1523.py
new file mode 100644
index 0000000000000000000000000000000000000000..87d21ba3724e6a75e828fd993c0b8cf3a7d3fffd
--- /dev/null
+++ b/votes/migrations/0003_auto_20150616_1523.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('votes', '0002_auto_20150616_1516'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='memopoldossier',
+ name='description',
+ field=models.TextField(default=b'', blank=True),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='memopoldossier',
+ name='name',
+ field=models.CharField(default=b'', max_length=1000, blank=True),
+ preserve_default=True,
+ ),
+ ]
diff --git a/votes/migrations/0004_auto_20150616_1527.py b/votes/migrations/0004_auto_20150616_1527.py
new file mode 100644
index 0000000000000000000000000000000000000000..74cd4b0274ce79eab09de8f23667e46695df661d
--- /dev/null
+++ b/votes/migrations/0004_auto_20150616_1527.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import core.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('votes', '0003_auto_20150616_1523'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='memopoldossier',
+ name='dossier',
+ field=core.fields.AutoOneToOneField(parent_link=True, related_name='extra', primary_key=True, serialize=False, to='representatives_votes.Dossier'),
+ preserve_default=True,
+ ),
+ ]
diff --git a/votes/migrations/0005_auto_20150617_1243.py b/votes/migrations/0005_auto_20150617_1243.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f0f2dc3be7ed24577db3f94a57535da96e50dbe
--- /dev/null
+++ b/votes/migrations/0005_auto_20150617_1243.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('votes', '0004_auto_20150616_1527'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='memopoldossier',
+ name='dossier_reference',
+ field=models.CharField(default='', max_length=200),
+ preserve_default=False,
+ ),
+ migrations.AlterField(
+ model_name='memopoldossier',
+ name='dossier',
+ field=models.OneToOneField(parent_link=True, related_name='extra', primary_key=True, serialize=False, to='representatives_votes.Dossier'),
+ preserve_default=True,
+ ),
+ ]
diff --git a/votes/models.py b/votes/models.py
index 8e1b1dda5b4e45b71535189e9477f41307774018..26ade88a53dfe6667811d5c5d24f7b1af25eae87 100644
--- a/votes/models.py
+++ b/votes/models.py
@@ -19,16 +19,21 @@
# Copyright (C) 2015 Arnaud Fabre
from django.db import models
+from django.utils.functional import cached_property
+from django.db.models.signals import post_save
+from django.dispatch import receiver
from representatives_votes.models import Vote, Proposal, Dossier
from legislature.models import MemopolRepresentative
+from core.utils import create_child_instance_from_parent
+from .tasks import update_representatives_score_for_proposal
+
class Recommendation(models.Model):
SCORE_TABLE = {
('abstain', 'abstain'): 1,
('abstain', 'for'): -0.5,
('abstain', 'against'): -0.5,
-
}
VOTECHOICES = (
@@ -36,27 +41,54 @@ class Recommendation(models.Model):
('for', 'for'),
('against', 'against')
)
-
+
proposal = models.OneToOneField(
Proposal,
related_name='recommendation'
)
-
+
recommendation = models.CharField(max_length=10, choices=VOTECHOICES)
title = models.CharField(max_length=1000, blank=True)
description = models.TextField(blank=True)
weight = models.IntegerField(default=0)
+@receiver(post_save, sender=Recommendation)
+def update_score(instance, **kwargs):
+ update_representatives_score_for_proposal(instance.proposal)
+
+
class MemopolDossier(Dossier):
- name = models.CharField(max_length=1000)
- description = models.TextField(blank=True)
+ parent_identifier = 'reference'
+ child_parent_identifier = 'dossier_reference'
+
+ dossier = models.OneToOneField(
+ Dossier,
+ primary_key=True,
+ parent_link=True,
+ related_name='extra'
+ )
+
+ dossier_reference = models.CharField(max_length=200)
+ name = models.CharField(max_length=1000, blank=True, default='')
+ description = models.TextField(blank=True, default='')
+
+ def save(self, *args, **kwargs):
+ if not self.name:
+ self.name = self.dossier.title
+ return super(MemopolDossier, self).save(*args, **kwargs)
+
+
+@receiver(post_save, sender=Dossier)
+def create_memopolrepresentative_from_representative(instance, **kwargs):
+ create_child_instance_from_parent(MemopolDossier, instance)
+
class MemopolVote(Vote):
class Meta:
proxy = True
- @property
+ @cached_property
def representative(self):
return MemopolRepresentative.objects.get(
remote_id = self.representative_remote_id
diff --git a/votes/tasks.py b/votes/tasks.py
new file mode 100644
index 0000000000000000000000000000000000000000..e57cd7b540d2753a684333775315460075363b5b
--- /dev/null
+++ b/votes/tasks.py
@@ -0,0 +1,44 @@
+# coding: utf-8
+
+# This file is part of memopol.
+#
+# memopol 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.
+#
+# memopol 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 .
+#
+# Copyright (C) 2015 Arnaud Fabre
+
+from __future__ import absolute_import
+
+from celery import shared_task
+
+from legislature.models import MemopolRepresentative
+from django.db.models import get_model
+
+@shared_task
+def update_representatives_score():
+ '''
+ Update score for all representatives
+ '''
+ for representative in MemopolRepresentative.objects.all():
+ representative.update_score()
+
+@shared_task
+def update_representatives_score_for_proposal(proposal):
+ '''
+ Update score for representatives that have votes for proposal
+ '''
+ MemopolVote = get_model('votes', 'MemopolVote')
+ for vote in MemopolVote.objects.filter(proposal_id = proposal.id):
+ # Extra is the MemopolRepresentative object
+ vote.representative.extra.update_score()
diff --git a/votes/templates/votes/admin/import.haml b/votes/templates/votes/admin/import.haml
index 833dfb268eca1759bae69afa124d3939c3942d0e..844b1e7cd3ce15439a6f2360f7f3f408d76baded 100644
--- a/votes/templates/votes/admin/import.haml
+++ b/votes/templates/votes/admin/import.haml
@@ -8,7 +8,7 @@
%form{:action => '', :method => 'post'}
- csrf_token
- {{ form }}.
+ {{ form }}
%input{:type => 'submit', :value => 'Search', :name => 'search'}
- if results