Skip to content

Commit

Permalink
shultze integration + various additions to sprint #5
Browse files Browse the repository at this point in the history
  • Loading branch information
amir99 committed Dec 30, 2013
1 parent b5fad91 commit ae7f14f
Show file tree
Hide file tree
Showing 28 changed files with 1,524 additions and 9 deletions.
9 changes: 6 additions & 3 deletions src/communities/templates/emails/_protocol.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ <h2 style="margin:0; background: #ddd;padding: 4px">{% trans "Meeting Details" %
<div style="margin:0; padding: 4px">
<b>{% trans "Scheduled at" %}:</b>
{{ scheduled_at|default:_("Not set yet") }}
<br/>
<b>{% trans "Location" %}:</b>
{{ location|default:_("Not set yet") }}

{% if location %}
<br/>
<b>{% trans "Location" %}:</b>
{% endif %}

{% if summary %}
<br/>
<b>{% trans "Summary" %}:</b>
Expand Down
13 changes: 13 additions & 0 deletions src/communities/templates/emails/agenda.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ <h3 style="margin:0; background: #ddd;padding: 4px; font-size: 20px;">
</div>
{% endif %}

{% if i.attachments.count %}
<div class="issue_attachments">
<p>{% trans 'Related files' %}:</p>
<ul>
{% for att in i.attachments.all %}
<li>
<a href="{{base_url}}{{ att.get_absolute_url }}" class="file_ext"><img height="16" src="{{base_url}}{{ STATIC_URL }}images/icons/{{ att.get_icon }}.png" /> {{att.title}}</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}

{% with proposals=i.active_proposals.count %}
{% if proposals %}
<h4 style="margin:0; background: #eee; padding: 2px">
Expand Down
71 changes: 71 additions & 0 deletions src/issues/shultze_vote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from django.http.response import HttpResponse, HttpResponseBadRequest
from django.shortcuts import get_object_or_404, render, redirect
from shultze.models import IssuesGraph
from issues.models import Issue, IssueRankingVote
from ocd.base_views import json_response
import json


def user_vote(community_id, current_vote, prev_vote):
try:
g = IssuesGraph.objects.get(community_id=community_id)
except IssuesGraph.DoesNotExist:
g = IssuesGraph.objects.create(community_id=community_id)
g.initialize_graph()
if prev_vote:
g.add_ballots(prev_vote,reverse=True)
g.add_ballots(current_vote)
print g.get_schulze_npr_results()


def set_issues_order_by_votes(community_id):
Issue.objects.filter(community_id=community_id).update( \
order_by_votes=9999)
try:
g = IssuesGraph.objects.get(community_id=community_id)
except IssuesGraph.DoesNotExist:
raise
res = g.get_schulze_npr_results()
issues_order = Issue.objects.in_bulk(res['order'])
for i, obj in enumerate(issues_order.values()):
obj.order_by_votes = i
print '[', i, '] ', obj
obj.save()


def send_issue_ranking(request):
if request.POST:
current_vote = json.loads(request.POST.get('new_order'))
prev_vote = IssueRankingVote.objects.filter(
voted_by=request.user,
issue__community_id=request.POST['community_id']) \
.order_by('rank')

if current_vote:
prev_param = {'ballot': [], 'count': 1, }
current_param = {'ballot': [], 'count': 1, }
if prev_vote:
prev_vote_as_list = list(prev_vote.values_list('issue_id', flat=True))
for v in prev_vote:
v.delete()

for v in prev_vote_as_list:
prev_param['ballot'].append([v])
prev_param = [prev_param,]
else:
prev_param = None

for i, v in enumerate(current_vote):
IssueRankingVote.objects.create(
voted_by=request.user,
issue_id=v,
rank=i
)
current_param['ballot'].append([v])
current_param = [current_param,]
# print current_param, prev_param
user_vote(request.POST['community_id'], current_param, prev_param)
set_issues_order_by_votes(request.POST['community_id'])
return HttpResponse(json_response('ok'))


4 changes: 2 additions & 2 deletions src/issues/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
UpdateIssueAbstractForm
from issues.models import ProposalType, Issue, IssueStatus, ProposalVote, \
ProposalVoteValue, VoteResult
from issues.stubs.order_issues import save_vote
from shultze_vote import send_issue_ranking
from meetings.models import Meeting
from oc_util.templatetags.opencommunity import minutes
from ocd.base_views import CommunityMixin, AjaxFormView, json_response
Expand Down Expand Up @@ -60,7 +60,7 @@ def get_context_data(self, **kwargs):

def post(self, request, *args, **kwargs):
print '[][][][][][][]'
save_vote(request)
send_issue_ranking(request)
return json_response({'res': 'ok', })


Expand Down
1 change: 1 addition & 0 deletions src/ocd/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
'communities',
'issues',
'meetings',
'shultze',

)

Expand Down
Empty file added src/shultze/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions src/shultze/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from shultze.models import *
g = IssuesGraph.objects.all()[0]
for e in g.edges.all():
print e.from_node_id, e.to_node_id, e.weight

input = [{'ballot': [[1], [2], [3], [4], [5]], 'count': 3},
{'ballot': [[5], [2], [3], [4], [1]], 'count': 9},
{'ballot': [[5], [1], [3], [4], [1]], 'count': 8},
{'ballot': [[3], [2], [4], [1], [5]], 'count': 5},
{'ballot': [[1], [2], [3], [4], [5]], 'count': 5}]

g.add_ballots(input)
199 changes: 199 additions & 0 deletions src/shultze/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
from django.db import models
from issues.models import Issue
from communities.models import Community
from pyvotecore.condorcet import CondorcetHelper
from pyvotecore.schulze_by_graph import SchulzeNPRByGraph
from pyvotecore.schulze_method import SchulzeMethod #TODO: remove this when iteritems bug is solved
import itertools

# Not a django model!
class PyVoteCoreAssistance(CondorcetHelper):
"""
Mainly CondorcetHelper
"""
def add_ballot(self, ballots, tie_breaker=None, ballot_notation=None):
for ballot in ballots:
if "count" not in ballot:
ballot["count"] = 1
self.standardize_ballots(ballots, ballot_notation)
self.graph = self.ballots_into_graph(self.candidates, self.ballots)
self.pairs = self.edge_weights(self.graph)


#TODO: Reconsider abstract graph structure models.

#class Graph(models.Model):
# """
# Abstract graph class
# Graph level general functionality should go here
# """
# class Meta:
# abstract = True
#
# def get_nodes_query(self):
# return Node.objects.filter(graph=self)
#
#
#class Node(models.Model):
# """
# Abstract graph node class
# Node level functionality
# related queries:
# node.in_edges
# node.out_edges
# """
# graph = models.ForeignKey(Graph, verbose_name=_("Graph"), related_name="nodes")
#
# class Meta:
# abstract = True
#
#
#class Edge(models.Model):
# """
# Abstract graph directed edge class
# Simple directed edge with numeric weight
# """
# graph = models.ForeignKey(Graph, verbose_name=_("Graph"), related_name="edges")
# from_node = models.ForeignKey(Node, verbose_name=_("From node"), related_name="out_edges")
# to_node = models.ForeignKey(Node, verbose_name=_("To node"), related_name="in_edges")
# weight = models.IntegerField(_("Weight"), default=0)
#
# class Meta:
# abstract = True


class IssuesGraph(models.Model):
"""
Graph-Issues class
Graph level general functionality should go here, one-to-one relation with OpenCommunity Community
"""
community = models.ForeignKey(Community, related_name="shultze_issues_graph")

def initialize_graph(self):
"""
Initialize candidates graph by community issues table.
This should happen only once!
"""
candidates = list(self.community.issues.all())
candidate_nodes = []
for candidate in candidates:
candidate_nodes.append(IssueNode.objects.create(graph=self, issue=candidate))
for pair in itertools.permutations(candidate_nodes, 2):
IssueEdge.objects.create(graph=self, from_node=pair[0], to_node=pair[1], weight = 0)

def add_ballots(self, ballots, tie_breaker=None, ballot_notation=None, reverse=False):
"""
Add ballots of ordered issues.
Ballots are translated to their complementary graph using PyVoteCoreAssistance and the graph's
edges values are added/deducted from the IssuesGraph instance.
Example:
input = [{'ballot': [[1], [2], [3], [4], [5]], 'count': 3},
{'ballot': [[5], [2], [3], [4], [1]], 'count': 9},
{'ballot': [[5], [1], [3], [4], [1]], 'count': 8},
{'ballot': [[3], [2], [4], [1], [5]], 'count': 5},
{'ballot': [[1], [2], [3], [4], [5]], 'count': 5}]
g.add_ballots(input)
use reverse=True to deduct the ballot from the IssuesGraph, reversing it's effect.
"""
output = SchulzeMethod(ballots, ballot_notation="grouping").as_dict() #TODO: remove this line when iteritems bug is solved
assistance = PyVoteCoreAssistance()
assistance.add_ballot(ballots, tie_breaker, ballot_notation)
for edge_key in assistance.pairs.keys():
try:
from_node = IssueNode.objects.get(issue_id = edge_key[0])
except IssueNode.DoesNotExist:
try:
new_issue = Issue.objects.get(id = edge_key[0])
from_node = self.add_node(new_issue)
except Issue.DoesNotExist:
raise # TODO: decide on proper exception
try:
to_node = IssueNode.objects.get(issue_id = edge_key[1])
except IssueNode.DoesNotExist:
try:
new_issue = Issue.objects.get(id = edge_key[1])
to_node = self.add_node(new_issue)
except Issue.DoesNotExist:
raise # TODO: decide on proper exception
edge = IssueEdge.objects.get(graph=self, from_node=from_node, to_node=to_node)
if reverse:
edge.weight -= assistance.pairs[edge_key]
else:
edge.weight += assistance.pairs[edge_key]
edge.save()

def add_node(self, candidate):
node = IssueNode.objects.create(graph=self, issue=candidate)
for other_node in IssueNode.objects.filter(graph=self).exclude(issue=candidate):
IssueEdge.objects.create(graph=self, from_node=node, to_node=other_node, weight = 0)
IssueEdge.objects.create(graph=self, from_node=other_node, to_node=node, weight = 0)
return node

def get_edges_dict(self):
"""
Return the graph's edges in the form of an edges dictionary.
example:
{
('a', 'b'): 8,
('b', 'a'): 3,
('a', 'c'): 3,
('c', 'a'): 4,
('b', 'c'): 6,
('c', 'b'): 3,
}
"""
edges_dict = dict()
for edge in IssueEdge.objects.filter(graph=self):
edges_dict[(edge.from_node.issue_id, edge.to_node.issue_id)] = edge.weight
return edges_dict

def get_schulze_npr_results(self):
input = self.get_edges_dict()
output = SchulzeNPRByGraph(input).as_dict()
return output

class IssueNode(models.Model):
"""
Issue-node class
Node level functionality, one-to-one relation with OpenCommunity Issue
related queries:
node.in_edges
node.out_edges
"""
graph = models.ForeignKey(IssuesGraph, related_name="nodes")
issue = models.ForeignKey(Issue, related_name="shultze_graph_node")


class IssueEdge(models.Model):
"""
Abstract graph directed edge class
Simple directed edge with numeric weight
"""
graph = models.ForeignKey(IssuesGraph, related_name="edges")
from_node = models.ForeignKey(IssueNode, related_name="out_edges")
to_node = models.ForeignKey(IssueNode, related_name="in_edges")
weight = models.IntegerField(default=0)


def process_vote_stub(community_id, current_order, prev_order=[]):
"""
Vote processing function
Find the appropriate IssuesGraph for community_id
"""
try:
community_instance = Community.objects.get(id=community_id)
except Community.DoesNotExist:
raise
try:
graph = IssuesGraph.objects.get(community=community_instance)
except IssuesGraph.DoesNotExist:
raise # initialize a new graph here?
except IssuesGraph.MultipleObjectsReturned:
raise # there should be only one! TODO: decide on proper exception
if prev_order:
graph.add_ballots(prev_order,reverse=True)
graph.add_ballots(current_order)
return
Empty file.
Loading

0 comments on commit ae7f14f

Please sign in to comment.