added support for results for full elections
author<mako@atdot.cc> <>
Wed, 6 Feb 2008 06:38:47 +0000 (01:38 -0500)
committer<mako@atdot.cc> <>
Wed, 6 Feb 2008 06:38:47 +0000 (01:38 -0500)
this included a full details page, a pop-up preferences tables, some
general reorganization of things due to new reusing, and several typo
and bug fixing

26 files changed:
app/controllers/voter_controller.rb
app/models/vote.rb
app/views/common/_methodinfo_approval.rhtml [moved from app/views/quickvote/_methodinfo_approval.rhtml with 100% similarity]
app/views/common/_methodinfo_borda.rhtml [moved from app/views/quickvote/_methodinfo_borda.rhtml with 100% similarity]
app/views/common/_methodinfo_condorcet.rhtml [moved from app/views/quickvote/_methodinfo_condorcet.rhtml with 58% similarity]
app/views/common/_methodinfo_plurality.rhtml [moved from app/views/quickvote/_methodinfo_plurality.rhtml with 100% similarity]
app/views/common/_methodinfo_runoff.rhtml [moved from app/views/quickvote/_methodinfo_runoff.rhtml with 100% similarity]
app/views/common/_methodinfo_ssd.rhtml [new file with mode: 0644]
app/views/common/_pref_tables.rhtml [moved from app/views/quickvote/_pref_tables.rhtml with 97% similarity]
app/views/common/_result.rhtml [moved from app/views/quickvote/_result.rhtml with 100% similarity]
app/views/common/_result_box.rhtml [moved from app/views/quickvote/_result_box.rhtml with 79% similarity]
app/views/common/_sortable_vote.rhtml [moved from app/views/voter/_sortable_vote.rhtml with 66% similarity]
app/views/common/pref_tables.rhtml [new file with mode: 0644]
app/views/layouts/basic.rhtml [new file with mode: 0644]
app/views/layouts/frontpage.rhtml
app/views/layouts/main.rhtml
app/views/quickvote/_methodinfo_ssd.rhtml [deleted file]
app/views/quickvote/_results_sidebar.rhtml
app/views/quickvote/index.rhtml
app/views/quickvote/results.rhtml
app/views/voter/_results_sidebar.rhtml [new file with mode: 0644]
app/views/voter/_vote_sidebar.rhtml [new file with mode: 0644]
app/views/voter/details.rhtml [new file with mode: 0644]
app/views/voter/results.rhtml [new file with mode: 0644]
public/stylesheets/common.css
public/stylesheets/main.css

index 461be29d252d480831413544e449e66bb2d024da..ac57f1b6d7d9d2fa6895f4f3ce35e001bfbf93e7 100644 (file)
@@ -7,20 +7,33 @@ class VoterController < ApplicationController
   def index
     if params[:urlpassword]
       password = params[:urlpassword]
-    else
-      password = nil
-    end
 
-    if @voter = FullVoter.find(:all, :conditions =>
-                                       [ "password = ?", password ] )[0]
-      @voter.vote = Vote.new if @voter.vote.nil?
-      @voter.vote.set_defaults! if @voter.vote.rankings.empty?
+      if @voter = FullVoter.find(:all,
+        :conditions => [ "password = ?", password ] )[0]
 
-      @election = @voter.election
-      @sidebar_content = render_to_string(:partial => 'sortable_vote')
-      render :action => 'full_vote'
-    elsif params[:urlpassword] 
-      redirect_to :action => 'index'
+        @voter.vote = Vote.new if @voter.vote.nil?
+        @voter.vote.set_defaults! if @voter.vote.rankings.empty?
+
+        @election = @voter.election
+        
+        # if the election is now finished 
+        if @election.enddate < Time.now
+          # compute and display results
+    
+          @results = @election.results
+          @candidates = {}
+          @election.candidates.each {|c| @candidates[c.id] = c}
+          @names = @election.names_by_id
+          
+          @sidebar_content = render_to_string(:partial => 'results_sidebar')
+          render :action => 'results'
+        else
+          @sidebar_content = render_to_string(:partial => 'vote_sidebar')
+          render :action => 'full_vote'
+        end
+      elsif params[:urlpassword] 
+        redirect_to :action => 'index'
+      end
     end
   end
 
@@ -32,6 +45,30 @@ class VoterController < ApplicationController
     end
   end
   
+  def pref_tables
+    if authenticate
+      @election = @voter.election
+      @results = @election.results
+      @candidates = {}
+      @election.candidates.each {|c| @candidates[c.id] = c}
+      @names = @election.names_by_id
+      render :template => 'common/pref_tables', :layout => 'basic'
+    else
+      redirect_to :action => 'index'
+    end
+  end
+
+  def details
+    if authenticate
+      @election = @voter.election
+      @votes = @election.votes.select {|v| v.confirmed? }.randomize
+      @voters = @votes.collect {|v| v.voter}.randomize
+      render :action => 'details'
+    else
+      redirect_to :action => 'index'
+    end
+  end
+
   def review
     if authenticate
       @voter.vote.time = Time.now
index 8852fa5fe066e6ae47ea93dd2c21afc87e1c609c..21b5408ff8c83e130ff8154f3380c7a1a1cc4c96 100644 (file)
@@ -71,16 +71,6 @@ class Vote < ActiveRecord::Base
     confirmed == 1
   end
   
-  def votestring=(string="")
-    candidate_ids = voter.election.candidates.sort.collect \
-      { |candidate| candidate.id.to_i }
-      
-    rel_votes = string.split("").collect { |vote| vote.to_i }
-    
-    # covert relative orders to absolute candidate ids
-    self.votes = rel_votes.collect { |vote| candidate_ids[ vote - 1 ] }
-  end
-
   def votestring
     # create a mapping of candidates ids and the relative order of the
     # candidates as they appear when sorted alphabetically
@@ -90,7 +80,7 @@ class Vote < ActiveRecord::Base
     end
 
     # assemble the votestring
-    self.votes.collect {|v| cand_relnums[v]}.join("")
+    self.votes.collect {|v| (cand_relnums[v] + 64).chr}.join("")
   end
 
   # the following subroutine is used for quickvotes, but need for elections now
similarity index 58%
rename from app/views/quickvote/_methodinfo_condorcet.rhtml
rename to app/views/common/_methodinfo_condorcet.rhtml
index b1e700537cdc0ffd66577b1e7555c4228c0cc686..5fb29e05fc22f33e0f0bf9e47da2e70599099a3d 100644 (file)
@@ -11,6 +11,15 @@ will be the winner.</p>
 "Simple Condorcet" to distinguish it from the Schulze method which is
 another Condorcet system.</p>
 
-<%= render :partial => 'pref_tables' %>
+<% candidates = @election.ssd_result.ranked_candidates.flatten -%>
+<% if candidates.size <= 7 -%>
+  <%= render_partial 'common/pref_tables' %>
+<% else %>
+
+  There are too many candidates in your elections to show the result
+  tables.  <%= link_to "Click here", { :action => 'pref_tables', :id =>
+  @voter.password }, :popup => [] %> to view details.
+
+<% end -%>
 
 </div>
diff --git a/app/views/common/_methodinfo_ssd.rhtml b/app/views/common/_methodinfo_ssd.rhtml
new file mode 100644 (file)
index 0000000..0c8dc95
--- /dev/null
@@ -0,0 +1,36 @@
+<p>The full results of this election ranked the candidates in order of
+preference (from most preferred to least preferred):</p>
+
+<ol>
+<% @election.ssd_result.ranked_candidates.each do |place|  %>
+  <li><%= h(place.collect {|c| @names[c].capitalize}.join( " <em>and</em> " )) %>
+      <%= "<strong>(TIE)</strong>" if place.length > 1 %></li>
+<% end %>
+</ol>
+
+
+<div class="rbmoreinfo">
+<h4>About the Schulze Method</h4>
+
+<p>The <%= link_to "Schulze method",
+"http://en.wikipedia.org/wiki/Schulze_method" %> is a preferential
+voting system. It is based on the Condorcet method but includes a set of
+methods for resolving "circular" defeats.</p>
+
+<p>The Schulze method is also known as Schwartz Sequential Dropping
+(SSD), Cloneproof Schwartz Sequential Dropping (CSSD), Beatpath Method,
+Beatpath Winner, Path Voting, and Path Winner.</p>
+</div>
+
+
+<% candidates = @election.ssd_result.ranked_candidates.flatten -%>
+<% if candidates.size <= 7 -%>
+  <%= render_partial 'common/pref_tables' %>
+<% else %>
+
+  There are too many candidates in your elections to show the result
+  tables.  <%= link_to "Click here", { :action => 'pref_tables', :id =>
+  @voter.password }, :popup => [] %> to view details.
+
+<% end -%>
+
similarity index 97%
rename from app/views/quickvote/_pref_tables.rhtml
rename to app/views/common/_pref_tables.rhtml
index b9d41ececcb6b2738dbc639ddf4785956eca4915..7701985ec49b5e5a3c7a1ce965c410242e046c75 100644 (file)
@@ -3,8 +3,6 @@
 <% matrix = @election.ssd_result.matrix %>
 <% victories = @election.ssd_result.victories_and_ties %>
 
-<% if candidates.size <= 7 -%>
-
 <p>Each number in the table below shows how many times the candidate on
 the left beat the matching candidate on the top. The winner is on the
 top of the left column.</p>
@@ -63,5 +61,4 @@ parenthesis.</p>
   <% end -%>
 </table>
 </div>
-<% end -%>
 
similarity index 79%
rename from app/views/quickvote/_result_box.rhtml
rename to app/views/common/_result_box.rhtml
index 917dbd4eea590d0ecbdec4da97361428e8783069..528885ddb944af1a74dc04bfd94192307617f220 100644 (file)
@@ -10,9 +10,9 @@
 </div>
 
 <% unless @election.election_method == method -%>
-<%= render :partial => 'result', :object => @results[method]%>
+<%= render :partial => 'common/result', :object => @results[method]%>
 <% end -%>
-<%= render :partial => 'methodinfo_' + method,
+<%= render :partial => 'common/methodinfo_' + method,
            :object => @results[method] %>
 
 </div>
similarity index 66%
rename from app/views/voter/_sortable_vote.rhtml
rename to app/views/common/_sortable_vote.rhtml
index 2452476d7f71ed0ae26f3457d5c6a64819d95e96..6876966f0f44dbf6f24aae5aa996c49acfa3e4f7 100644 (file)
@@ -1,8 +1,3 @@
-<h2>Your Vote</h2>
-
-<p>Please vote by dragging items in the list below into your preferred
-order.</p>
-
 <div id="sortable_list">
 <ol id="rankings-list">
   <% for ranking in @voter.vote.rankings %>
@@ -18,6 +13,3 @@ order.</p>
     :url => { :action => "sort_candidates", :id => @voter.vote.id },
     :complete => visual_effect(:highlight, 'rankings-list') %>
 
-<div style="margin-left: 30pt;">
-<%= button_to "Confirm Vote", :action => 'review', :id => @voter.password %>
-</div>
diff --git a/app/views/common/pref_tables.rhtml b/app/views/common/pref_tables.rhtml
new file mode 100644 (file)
index 0000000..1215c7b
--- /dev/null
@@ -0,0 +1 @@
+<%= render_partial 'common/pref_tables' %>
diff --git a/app/views/layouts/basic.rhtml b/app/views/layouts/basic.rhtml
new file mode 100644 (file)
index 0000000..a7e9327
--- /dev/null
@@ -0,0 +1,24 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title><%= @page_title || "Selectricity" %></title>
+    <%= stylesheet_link_tag "common", :media => "all" %>
+    <%= stylesheet_link_tag *(@stylesheets) %>
+    <%begin%>
+      <%= stylesheet_link_tag "ie6hacks", :media => "all" if 
+      request.user_agent =~ /msie\s(5\.[5-9]|[6]\.[0-9]*).*(win)/i %>
+    <%rescue NoMethodError%>
+    <%end%>
+    <%= javascript_include_tag "prototype", "effects", "dragdrop", "controls" %>
+  </head>
+
+  <body>
+  <div>
+        <%= @content_for_layout %>
+  </div>
+
+    <div class="clear-div"></div>
+    <%= render_partial 'layouts/footer' %>
+    </div>
+  </body>
+</html>
index e227e87daa23197727934e876a87659c6ccae207..570a4f3942cf8314bddbede177a9281488302b37 100644 (file)
     <div id="header">
       <div id="top-bar">
        <% if session[:user]%>
-       <%= link_to User.find(session[:user]).login.capitalize,
+       <%= link_to User.find(session[:user]).login.dowcase,
                   :controller => "account",
                   :action => "summary", :id => session[:user][:id] %>
-       &nbsp;&nbsp;<%= link_to( 'Log out', :controller => 'account', 
+       &nbsp;&nbsp;<%= link_to( 'log out', :controller => 'account', 
                        :action => 'logout' )%>
        <% else %>
       <%= link_to("login", :controller => "account", :action => "login")
index c34e797b6f60028658061777bfe2fad261e2f67e..3f90f7a26b6234450937eb0dea6b20eedb21d806 100644 (file)
@@ -33,7 +33,7 @@
       <div id="top-bar">
            <div id="bar-left">
         <% if session[:user] %>
-          <%= link_to User.find(session[:user]).login.capitalize,
+          <%= link_to User.find(session[:user]).login.downcase,
                       :controller => "account",
                       :action => "summary", :id => session[:user][:id] %>
           <%= link_to "logout", :controller => "account",
diff --git a/app/views/quickvote/_methodinfo_ssd.rhtml b/app/views/quickvote/_methodinfo_ssd.rhtml
deleted file mode 100644 (file)
index e3ff831..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<div class="rbmoreinfo">
-<h4>About the Schulze Method</h4>
-
-<p>The <%= link_to "Schulze method",
-"http://en.wikipedia.org/wiki/Schulze_method" %> is a preferential
-voting system. It is based on the Condorcet method but includes a set of
-methods for resolving "circular" defeats.</p>
-
-<p>The Schulze method is also known as Schwartz Sequential Dropping
-(SSD), Cloneproof Schwartz Sequential Dropping (CSSD), Beatpath Method,
-Beatpath Winner, Path Voting, and Path Winner.</p>
-</div>
-
-<%= render :partial => 'pref_tables' %>
-
index 30047a48347921ca790773ab2c6b5bb81623f103..13e0dd057075605435bee8997f9c0db1062862e0 100644 (file)
@@ -1,40 +1,5 @@
-<h2>Method</h2>
-
-<p>This election was run using:
-<strong><%= ELECTION_TYPES[@election.election_method] %></strong></p>
-
-<p>View results using other methods:<br />
-<% type_hash = {}; ELECTION_TYPES.each {|k,v| type_hash[v] = k} %>
-<%= select_tag 'election_type_select', options_for_select(type_hash, @election.election_method) %></p>
-
-<script>
-var election_methods = new Array(<%= ELECTION_TYPES.keys.collect {|k| "'#{k}'"}.join(', ') %>);
-var method_select = $('election_type_select'); 
-
-function show_results_for() {
-  var test = $('test');
-
-  method_select.value;
-  var result_boxes = document.getElementsByClassName('resultbox');
-  for (i = 0; i < result_boxes.length; i++) {
-    result_box = result_boxes[i];
-    if (result_box.id == (method_select.value + "_result")) {
-      Element.show(result_box);
-    } else {
-      Element.hide(result_box);
-    }
-  }
-
-  //alert(method_select.value);
-}
-
-Event.observe(method_select, 'change', show_results_for);
-</script>
-
-<h2>Statistics</h2>
-<%= image_tag( graph_url( :action => 'votes_per_interval', :id => @election ))%>
-<br />
-<br />
+<!-- common data to all sidebars -->
+<%= render_partial 'common/results_sidebar' %>
 
 <h2>Voter Report</h2>
 
index ccc7ec62073be033e7af17ca215555be4491b5c0..320d85fbf830c7a95dafc3615343b2841d9e517d 100644 (file)
@@ -22,7 +22,7 @@
 from <em>most preferred at the top</em> to <em>least preferred at the
 bottom</em>. When you are done, press confirm to record your vote.</p>
 
-<%= render :partial => 'voter/sortable_vote' %>
+<%= render :partial => 'common/sortable_vote' %>
 
 <%= button_to "Confirm Vote", quickaction_url( :action => 'confirm', :ident => @voter.election.name)  %>
 
index 5fd5f9e4bc0d15cf5cd8defe0a71a97be85a167b..8d8bf5f15d88b9413d534e6930ae8bf5314107d6 100644 (file)
@@ -6,7 +6,7 @@
 </div>
 
 <div id="winner_box">
-<%= render :partial => 'result', :object => @results[@election.election_method] %>
+<%= render :partial => 'common/result', :object => @results[@election.election_method] %>
 </div>
 
 <% if @election.shortdesc %>
      <%= @election.voters.reject {|v| not v.voted? }.length %>
    </blockquote>
 
-<%= render :partial => 'result_box',
+<%= render :partial => 'common/result_box',
            :locals => { :method => @election.election_method } %>
 
 <% for result_type in @election.other_methods %>
 
-<%= render :partial => 'result_box',
+<%= render :partial => 'common/result_box',
            :locals => { :method => result_type } %>
 
 <% end %>
diff --git a/app/views/voter/_results_sidebar.rhtml b/app/views/voter/_results_sidebar.rhtml
new file mode 100644 (file)
index 0000000..9814aed
--- /dev/null
@@ -0,0 +1,7 @@
+<!-- common data to all sidebars -->
+<%= render_partial 'common/results_sidebar' %>
+
+<h2>Details</h2>
+
+<p><%= link_to "Auditing Information", { :action => 'details', :id => @voter.password }, :popup => [] %></p>
+
diff --git a/app/views/voter/_vote_sidebar.rhtml b/app/views/voter/_vote_sidebar.rhtml
new file mode 100644 (file)
index 0000000..5b8d038
--- /dev/null
@@ -0,0 +1,10 @@
+<h2>Your Vote</h2>
+
+<p>Please vote by dragging items in the list below into your preferred
+order.</p>
+
+<%= render :partial => 'common/sortable_vote' %>
+
+<div style="margin-left: 30pt;">
+<%= button_to "Submit Vote", :action => 'review', :id => @voter.password %>
+</div>
diff --git a/app/views/voter/details.rhtml b/app/views/voter/details.rhtml
new file mode 100644 (file)
index 0000000..584c408
--- /dev/null
@@ -0,0 +1,50 @@
+<div id="title-header">
+  <span class="header">Details</span>
+  <span class="subheader"><%= @election.name %></span>
+</div>
+
+<p>This page contains information useful for auditing elections and
+verify that votes were tabulated correctly.</p>
+
+<p>The following invididuals (in random order) voted in this
+election:</p>
+
+<ol>
+<%- @voters.each do |voter| -%>
+<li><%= voter.email %></li>
+<%- end -%>
+</ol>
+
+<p>The following table lists the votes cast in random order.</p>
+
+<p>The column marked <em>Verification Token</em> lists tokens that were
+given to voters at the time of voting. Voters can check to see that the
+vote that corresponds to their token was recorded correctly. The column
+marks "vote" lists the candidates in order of the voter's preference. To
+read these votes, please refer to the key below.</p>
+
+<table class="preftable">
+<tr>
+<th>Verification Token</th>
+<th>Vote</th>
+<%- @votes.each do |vote| -%>
+<tr>
+<td><%= vote.token %></td><td><%= vote.votestring%></td>
+</tr>
+<%- end -%>
+</table>
+
+<p style="margin-top: 1em;">Key:</p>
+
+<table class="preftable">
+<tr>
+<th>Code</th>
+<th>Candidate</th>
+<%- @election.candidates.sort.each_with_index do |c, i| -%>
+<tr>
+<td><%= (i + 65).chr %></td>
+<td><%= c.name %></td>
+</tr>
+<%- end -%>
+</table>
+
diff --git a/app/views/voter/results.rhtml b/app/views/voter/results.rhtml
new file mode 100644 (file)
index 0000000..bb95c88
--- /dev/null
@@ -0,0 +1,43 @@
+<% require 'whois/whois' %>
+
+<div id="title-header">
+  <span class="header">Results</span>
+  <span class="subheader"><%= @election.name %></span>
+</div>
+
+<div id="winner_box">
+<%= render :partial => 'common/result', :object => @results[@election.election_method] %>
+</div>
+
+<% if @election.shortdesc %>
+  <p><strong>Vote Description:</strong></p>
+  <blockquote><em><%=h @election.shortdesc %></em>
+    <% if @election.longdesc -%>
+      <br />
+      <%= h(@election.longdesc) -%>
+    <% end -%>
+  </blockquote>
+<% end %>
+
+<p><strong>Number of voters:</strong></p>
+   <blockquote>
+     <%= @election.voters.reject {|v| not v.voted? }.length %>
+   </blockquote>
+
+<%= render :partial => 'common/result_box',
+           :locals => { :method => @election.election_method } %>
+
+<% for result_type in @election.other_methods %>
+
+<%= render :partial => 'common/result_box',
+           :locals => { :method => result_type } %>
+
+<% end %>
+
+
+<div class="clear-div"></div>
+
+<!--
+<%= image_tag( graph_url( :action => 'choices_positions', :id => @election ) ) %><br />
+-->
+
index a050444f3327d328062db58e2e93e20d9e02290b..5d7f199ae6943c7dcfdc523551ee6951172556aa 100644 (file)
@@ -105,3 +105,36 @@ h2, h3 {
     border: 1px black solid;
        text-align: center;
        font-weight: bold
+}
+
+.preftable {
+       font-family: verdana,arial,helvetica,sans-serif;
+       border-spacing: 0px;
+       border-width: 2px;
+       border-color: #999999;
+       border-style: solid;
+       caption-side: top;
+}
+
+.preftable th {
+       font-family: verdana,arial,helvetica,sans-serif;
+       border-width: 2px;
+       border-color: #999999;
+       border-style: solid;
+       text-align: center;
+       font-weight: bold;
+       padding: 5px 5px 5px 5px;
+       background-color: #999999;
+       color: #FFFFFF;
+}
+
+.preftable td {
+  border-collapse: collapse;
+  border-width: 1px;
+  border-color: #999999;
+  border-style: solid;
+  text-align: right;
+  padding-right: 5px;
+  padding-left: 5px;
+}
+
index 97c684858052b9f400493531ca63a5997c4cc797..e7ff4a6587ec91b1854b689e5ccabf64676c3043 100644 (file)
@@ -250,36 +250,6 @@ blockquote {
  text-align: center;
  margin-bottom: 1em;
 }
-.preftable {
-       font-family: verdana,arial,helvetica,sans-serif;
-       border-spacing: 0px;
-       border-width: 2px;
-       border-color: #999999;
-       border-style: solid;
-       caption-side: top;
-}
-
-.preftable th {
-       font-family: verdana,arial,helvetica,sans-serif;
-       border-width: 2px;
-       border-color: #999999;
-       border-style: solid;
-       text-align: center;
-       font-weight: bold;
-       padding: 5px 5px 5px 5px;
-       background-color: #999999;
-       color: #FFFFFF;
-}
-
-.preftable td {
-  border-collapse: collapse;
-  border-width: 1px;
-  border-color: #999999;
-  border-style: solid;
-  text-align: right;
-  padding-right: 5px;
-  padding-left: 5px;
-}
 
 .voterbox {
   border-spacing: 0px;

Benjamin Mako Hill || Want to submit a patch?