forked from nonZero/OpenCommunity
-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
shultze integration + various additions to sprint #5
- Loading branch information
Showing
28 changed files
with
1,524 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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')) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -167,6 +167,7 @@ | |
'communities', | ||
'issues', | ||
'meetings', | ||
'shultze', | ||
|
||
) | ||
|
||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Oops, something went wrong.