From: Benjamin Mako Hill Date: Wed, 17 Jun 2009 05:22:34 +0000 (-0400) Subject: fixed a large number of bugs in the software (see wiki) over a days work X-Git-Url: https://projects.mako.cc/source/selectricity-live/commitdiff_plain/83a39529b634c0344a7884d5813e3f7e1a7dbcb4 fixed a large number of bugs in the software (see wiki) over a days work --- diff --git a/app/controllers/election_controller.rb b/app/controllers/election_controller.rb index ad74a1b..58c5c47 100644 --- a/app/controllers/election_controller.rb +++ b/app/controllers/election_controller.rb @@ -10,8 +10,17 @@ class ElectionController < ApplicationController require_dependency "voter" require_dependency "vote" require_dependency "candidate" + + helper :sparklines layout 'main' + + before_filter :verify_owner, + :except => [:new, :general_information, :create_election] + before_filter :verify_not_active, + :except => [:new, :general_information, :create_election, + :show, :results, :details, :pref_tables] + ## methods for displaying, creating, ## and manipulating election overview data #################################################################### @@ -51,18 +60,9 @@ class ElectionController < ApplicationController end end - def create_theme_hash - target = Hash.new - params.each do |k,v| - target[k] = v if k=="top_bar" or k=="default_image" or k=="bg1" \ - or k=="bg2" or k=="bottom_bar" - end - return target - end - - # TODO add filter to verify that the person working on or looking at - # something is the owner def edit_general_information + @sidebar_content = render_to_string :partial => 'progress', + :locals => { :page => 'overview' } @election = Election.find(params[:id]) end @@ -83,7 +83,7 @@ class ElectionController < ApplicationController flash[:notice] = 'Election was successfully updated.' redirect_to :action => 'show', :id => @election else - render :action => 'edit' + render :action => 'edit_general_information' end end @@ -173,7 +173,7 @@ class ElectionController < ApplicationController def edit_candidates @sidebar_content = render_to_string :partial => 'progress', :locals => { :page => 'candidates' } - @election = Election.find( params[:id] ) + @election = Election.find(params[:id] ) end def add_candidate @@ -196,12 +196,12 @@ class ElectionController < ApplicationController end def delete_candidate - candidate = Candidate.find( params[:id] ) + candidate = Candidate.find(params[:candidate] ) candidate.destroy end def candidate_picture - candidate = Candidate.find( params[:id] ) + candidate = Candidate.find(params[:candidate]) send_data( candidate.picture.data, :filename => candidate.picture.filename, :type => candidate.picture.filetype, @@ -219,7 +219,8 @@ class ElectionController < ApplicationController @sidebar_content = render_to_string :partial => 'progress', :locals => { :page => 'voters' } - @election = Election.find( params[:id] ) + @election = Election.find(params[:id]) + if params.has_key?( :raw_voter_list ) process_incoming_voters( params[:raw_voter_list] ) end @@ -228,7 +229,7 @@ class ElectionController < ApplicationController end def delete_voter - voter = FullVoter.find( params[:id] ) + voter = FullVoter.find(params[:voter]) voter.destroy end @@ -245,40 +246,27 @@ class ElectionController < ApplicationController ## methods for computing and printing results #################################################################### def results - @election = Election.find( params[:id] ) - votes = [] - - @election.voters.each do |voter| - if voter.vote and voter.vote.confirmed? - votes << voter.vote.rankings.sort.collect {|vote| vote.candidate_id} - end + @election = Election.find(params[:id]) + + if @election.early_results? \ + or @election.enddate < Time.now + + # render results + @sidebar_content = render_to_string(:partial => 'full_results_sidebar') + render :template => 'common/results' + else + redirect_to :action => 'index' end - - @voteobj = CloneproofSSDVote.new(votes) - @resultobj = @voteobj.result - @winners = @resultobj.winners - - @candidates_by_id = {} - @election.candidates.each {|cand| @candidates_by_id[cand.id] = cand} - end - def detailed_results - - self.results - - @voter_list = [] - @vote_list = [] - - @election.voters.each do |voter| - if voter.vote and voter.vote.confirmed? - @voter_list << voter.email - @vote_list << voter.vote - end - end + def pref_tables + @election = Election.find(params[:id]) + render :template => 'common/pref_tables_wrapper', :layout => 'basic' + end - @vote_list.sort! - @vote_list.sort! { |a,b| a.token <=> b.token } + def details + @election = Election.find(params[:id]) + render :template => 'common/details' end ## private methods @@ -314,5 +302,30 @@ class ElectionController < ApplicationController voter.save end end + + def create_theme_hash + target = Hash.new + params.each do |k,v| + target[k] = v if k=="top_bar" or k=="default_image" or k=="bg1" \ + or k=="bg2" or k=="bottom_bar" + end + return target + end + + # verify that the person trying to edit the election is the owner + def verify_owner + election = Election.find(params[:id]) + unless election.user == session[:user] + redirect_to :controller => 'front', :action => 'index' + end + end + + # verify that the election is not active + def verify_not_active + election = Election.find(params[:id]) + unless election.active == 0 + redirect_to :controller => 'front', :action => 'index' + end + end end diff --git a/app/controllers/quickvote_controller.rb b/app/controllers/quickvote_controller.rb index 5bf89c7..066fd6b 100644 --- a/app/controllers/quickvote_controller.rb +++ b/app/controllers/quickvote_controller.rb @@ -44,6 +44,7 @@ class QuickvoteController < ApplicationController #Give registered users additional QuickVote functionality @quickvote.user_id = session[:user][:id] if session[:user] + @quickvote.create_candidates # try to save, if it fails, show the page again (the flash should # still be intact diff --git a/app/controllers/voter_controller.rb b/app/controllers/voter_controller.rb index 9ba619c..1634ccd 100644 --- a/app/controllers/voter_controller.rb +++ b/app/controllers/voter_controller.rb @@ -12,6 +12,9 @@ class VoterController < ApplicationController require_dependency "vote" require_dependency "election" + before_filter :authenticate, :except => [:index, :login, :reminder, + :kiosk_ready] + def index if params[:election_id] @election = Election.find(params[:election_id]) @@ -80,54 +83,23 @@ 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? }.shuffle - @voters = @votes.collect {|v| v.voter}.shuffle - render :action => 'details' - else - redirect_to :action => 'index' - end - end - def review - if authenticate - @voter.vote.time = Time.now - @voter.vote.save - @voter.reload - else - redirect_to :action => 'index' - end + @voter.vote.time = Time.now + @voter.vote.save + @voter.reload end def confirm - if authenticate - @voter.vote.confirm! - - if @voter.election.embeddable? and params[:embed] == "true" \ - and @voter.election.early_results? - redirect_to :action => :results, :id => @password, :embed => 'true' - elsif @voter.election.kiosk and params[:kiosk] = "true" - redirect_to :action => "kiosk_ready", :id => @password, :kiosk => true - else - render :action => 'thanks' - end + @voter.vote.confirm! + + if @voter.election.embeddable? and params[:embed] == "true" \ + and @voter.election.early_results? + redirect_to :action => :results, :id => @password, :embed => 'true' + elsif not(@voter.election.verifiable) \ + and @voter.election.kiosk and params[:kiosk] == "true" + redirect_to :action => "kiosk_ready", :id => @password, :kiosk => true else - redirect_to :action => 'index' + render :action => 'thanks' end end @@ -143,19 +115,12 @@ class VoterController < ApplicationController end def results - if authenticate and - (@voter.election.early_results? \ - or @voter.election.enddate < Time.now) + if @voter.election.early_results? \ + or @voter.election.enddate < Time.now @election = @voter.election - # 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') + @sidebar_content = render_to_string(:partial => 'full_results_sidebar') + #look for custom theme, and assign to instance variabels for widget use if @election.embed_custom_string @top_bar = SkinPicture.find(:first, @@ -172,13 +137,23 @@ class VoterController < ApplicationController if @election.embeddable? and params[:embed] == "true" render :template => 'embed/results', :layout => 'embed' else - render :action => 'results' + render :template => 'common/results' end else redirect_to :action => 'index' end end + def pref_tables + @election = @voter.election + render :template => 'common/pref_tables_wrapper', :layout => 'basic' + end + + def details + @election = @voter.election + render :template => 'common/details' + end + def kiosk_ready reset_session @@ -193,21 +168,25 @@ class VoterController < ApplicationController if password == "open" election = Election.find(params[:format]) - # check to see if the person has voted before - unless election.authenticated? + # if it's not actually open, lets redirect + if election.authenticated + redirect_to :action => 'index' + + # otherwise, lets see if they've before + else @voter = OpenVoter.find(:all, :conditions => ["session_id = ? and election_id = ?", session.session_id, election.id])[0] - @password = "open." + election.id.to_s - end - # if it's ready for kiosk_mode, then we create and authenticate - unless @voter and params[:action] == 'kiosk_ready' \ - and election.kiosk - - # this is maybe not quite as a dry as it should be - @voter = OpenVoter.new unless @voter + # when (a) there is no voter or (b) when there is a voter but + # it's kiosk mode on the right page, rewrite witha blank voter + if not(@voter) \ + or (params[:action] == 'kiosk_ready' and election.kiosk) + @voter = OpenVoter.new unless @voter + end + # now that we have a voter (one way or another), set things + # right @voter.election = election @voter.session_id = session.session_id @password = "open." + election.id.to_s @@ -216,9 +195,13 @@ class VoterController < ApplicationController else @voter = FullVoter.find(:all, :conditions => [ "password = ?", password ] )[0] - @password = @voter.password + + if @voter + @password = @voter.password + else + redirect_to :Action => 'index' + end end - @voter end end diff --git a/app/models/election.rb b/app/models/election.rb index a3e9394..4ce571a 100644 --- a/app/models/election.rb +++ b/app/models/election.rb @@ -11,9 +11,12 @@ class Election < ActiveRecord::Base has_many :votes belongs_to :user validates_presence_of :name, :description - + + # enforce constraints associated with dependencies (i.e., a kiosk + # election can't also be unauthenticated) + before_save :enforce_constraints + #validate that method is one of the listed election types - attr_reader :plurality_result attr_reader :approval_result attr_reader :condorcet_result @@ -21,7 +24,7 @@ class Election < ActiveRecord::Base attr_reader :borda_result require 'date' - + def initialize(params={}) super self.enddate = read_attribute( :enddate ) || \ @@ -71,7 +74,7 @@ class Election < ActiveRecord::Base def activate! self.active = 1 - self.save! + self.save end def quickvote? @@ -164,6 +167,30 @@ class Election < ActiveRecord::Base names end + + def candidate_hash + hash = {} + self.candidates.each {|c| hash[c.id] = c} + return hash + end + + + # TODO now that this code is in here, we should go ahead and remove + # date checking from other places in the code + def after_find + if self.active < 2 and self.enddate < Time.now + self.active = 2 + self.save + end + end + + private + def enforce_constraints + # kiosks can't be authenticated + self.authenticated = false if kiosk? + return true + end + end diff --git a/app/models/quick_vote.rb b/app/models/quick_vote.rb index de78813..f9e1b83 100644 --- a/app/models/quick_vote.rb +++ b/app/models/quick_vote.rb @@ -1,6 +1,5 @@ class QuickVote < Election before_validation :build_candidate_names - after_validation :create_candidates validates_uniqueness_of :name attr_accessor :candidate_names @@ -60,8 +59,8 @@ class QuickVote < Election def build_candidate_names @candidate_names ||= [] - if @candidate_names.empty? and not candidates.empty? - @candidate_names = candidates.collect {|c| c.name} + if @candidate_names.empty? and not self.candidates.empty? + @candidate_names = self.candidates.collect {|c| c.name} end end diff --git a/app/views/account/summary.rhtml b/app/views/account/summary.rhtml index b53122e..0ea9f24 100644 --- a/app/views/account/summary.rhtml +++ b/app/views/account/summary.rhtml @@ -21,12 +21,8 @@ Member since: <%=h @user.created_at.strftime("%x") %> <% @user.elections.select {|e| e.instance_of?(Election)}.each do |election| %> - <% if election.active == 1 -%> <%= link_to "#{election.name}", :controller => 'election', :action => 'show', :id => election %> - <% else -%> - <%=h election.name %> - <% end -%> <%=h election.description %> diff --git a/app/views/common/_pref_tables.rhtml b/app/views/common/_pref_tables.rhtml index 3a53144..3edc05a 100644 --- a/app/views/common/_pref_tables.rhtml +++ b/app/views/common/_pref_tables.rhtml @@ -1,7 +1,13 @@ + +<% @election.results! %> + + <% candidates = @election.ssd_result.ranked_candidates.flatten -%> <% voters = @election.voters.size %> <% matrix = @election.ssd_result.matrix %> <% victories = @election.ssd_result.victories_and_ties %> +<% @names = @election.names_by_id %> +

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.

diff --git a/app/views/voter/details.rhtml b/app/views/common/details.rhtml similarity index 50% rename from app/views/voter/details.rhtml rename to app/views/common/details.rhtml index cf73f41..ae09c29 100644 --- a/app/views/voter/details.rhtml +++ b/app/views/common/details.rhtml @@ -1,3 +1,6 @@ +<% @votes = @election.votes.select {|v| v.confirmed? }.shuffle %> +<% @voters = @votes.collect {|v| v.voter}.shuffle %> +
Details <%= @election.name %> @@ -11,27 +14,35 @@ election:

    <%- @voters.each do |voter| -%> -
  1. <%= voter.email %>
  2. +
  3. <% if voter.email %> + <%= voter.email %> + <% elsif @election.kiosk? %> + Kiosk Voter + <% else %> + Unknown voter + <% end %>
  4. <%- end -%>

The following table lists the votes cast in random order.

-

The column marked Verification Token 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 -marked Vote lists the candidates in order of the voter's -preference. To read these votes, refer to the key below.

+

<% if @election.verifiable %>The column marked Verification +Token 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.<% end %> The column marked Vote lists +the candidates in order of the voter's preference. To read these votes, +refer to the key below.

- +<% if @election.verifiable %><% end %> <%- @votes.each_with_index do |vote, i| -%> - +<% if @election.verifiable %><% end %> + <%- end -%>
Verification TokenVerification TokenVote
<%= i + 1 %><%= vote.token %><%= vote.votestring%><%= vote.token %><%= vote.votestring%>
diff --git a/app/views/common/pref_tables.rhtml b/app/views/common/pref_tables_wrapper.rhtml similarity index 100% rename from app/views/common/pref_tables.rhtml rename to app/views/common/pref_tables_wrapper.rhtml diff --git a/app/views/voter/results.rhtml b/app/views/common/results.rhtml similarity index 87% rename from app/views/voter/results.rhtml rename to app/views/common/results.rhtml index bb95c88..6f3a18d 100644 --- a/app/views/voter/results.rhtml +++ b/app/views/common/results.rhtml @@ -1,3 +1,8 @@ + +<% @results = @election.results %> +<% @names = @election.names_by_id %> +<% @candidates = @election.candidate_hash %> + <% require 'whois/whois' %>
diff --git a/app/views/election/_candidate_line_edit.rhtml b/app/views/election/_candidate_line_edit.rhtml index bcb7cd9..0260a6a 100644 --- a/app/views/election/_candidate_line_edit.rhtml +++ b/app/views/election/_candidate_line_edit.rhtml @@ -6,7 +6,7 @@ <%= link_to_remote "Delete", :complete => "Element.remove('cand#{@current_candidate.id}')", :url => { :action => :delete_candidate, - :id => @current_candidate.id } %> + :id => @election.id, :candidate => @current_candidate.id } %>
<%= render :partial => 'candidate_box_info' %> diff --git a/app/views/election/_full_results_sidebar.rhtml b/app/views/election/_full_results_sidebar.rhtml new file mode 100644 index 0000000..bb3bbc9 --- /dev/null +++ b/app/views/election/_full_results_sidebar.rhtml @@ -0,0 +1,6 @@ + +<%= render :partial => 'common/results_sidebar' %> + +

Details

+ +

<%= link_to "Auditing Information", { :action => 'details', :id => @election.id }, :popup => [] %>

diff --git a/app/views/election/_voter_list.rhtml b/app/views/election/_voter_list.rhtml index 4493b0a..34aa207 100644 --- a/app/views/election/_voter_list.rhtml +++ b/app/views/election/_voter_list.rhtml @@ -13,7 +13,8 @@ <% if @edit %> <%= link_to_remote "Delete", :complete => "Element.remove('voter#{voter.id}')", - :url => { :action => :delete_voter, :id => voter.id } %> + :url => { :action => :delete_voter, :id => @election.id, + :voter => voter.id } %> <% end %>
diff --git a/app/views/election/detailed_results.rhtml b/app/views/election/detailed_results.rhtml deleted file mode 100644 index 1f22377..0000000 --- a/app/views/election/detailed_results.rhtml +++ /dev/null @@ -1,52 +0,0 @@ -

Result

- -<%= render :partial => 'winner' %> - -

Result Details

- -<%= render :partial => 'winner_details' %> - -

Election Rolls for Voter Verification

- -

The voting rolls -- displayed here in alphabetical order -- for the -last election are are follows.

- -

Information is displayed here to help voters verifying that their own -vote was recorded correctly and that the election was not tampered -with.

- -

Voters

- - -<% for email in @voter_list %> - - - -<% end %> -
Voters (A-Z)
<%= email %>
- -

Votes by Token

- -

The votes, listed in alphabetical order by token.

- - - - - - - - -<% for candidate in @election.candidates.sort.reverse %> - -<% end %> - - -<% for vote in @vote_list %> - - - <% for ranking in vote.rankings %> - - <% end %> - -<% end %> -
Token (0-9, A-Z)Rank of Candidates
<%= candidate %>
<%= vote.token %><%= ranking %>
diff --git a/app/views/election/edit_candidates.rhtml b/app/views/election/edit_candidates.rhtml index 7e1f315..cd0bd5a 100644 --- a/app/views/election/edit_candidates.rhtml +++ b/app/views/election/edit_candidates.rhtml @@ -5,6 +5,8 @@
+<% if @election.active == 0 %> +
Enter New Candidate @@ -44,3 +46,11 @@ to proceed to the next step.

<%= button_to "Proceed to Next Step", :action => 'new_voters', :id => @election %> + +<% else %> + +

You can not edit the list of candidates once the election has begun. +please return to the <%= link_to "election overview page", :action => +'show', :id => @election.id %>.

+ +<% end %> diff --git a/app/views/election/edit_general_information.rhtml b/app/views/election/edit_general_information.rhtml index 17ec324..1d8ec90 100644 --- a/app/views/election/edit_general_information.rhtml +++ b/app/views/election/edit_general_information.rhtml @@ -6,7 +6,7 @@ <% form_tag( {:action => 'update_general_information', :id => @election}, :multipart => true ) do %> <%= render :partial => 'overview_form' %> - <%= render :partial => 'theme_upload' %> + <%= submit_tag 'Done' %> <% end %> diff --git a/app/views/election/edit_voters.rhtml b/app/views/election/edit_voters.rhtml index 77093fe..770c5ec 100644 --- a/app/views/election/edit_voters.rhtml +++ b/app/views/election/edit_voters.rhtml @@ -3,8 +3,13 @@
-

>Anyone will be able to -vote in this election.

+<% if @election.active == 0 %> + +

> +<% if @election.kiosk? %> + Because you have enabled kiosk mode, there will be no registration of voters. +<% end %> +Anyone will be able to vote in this election.

> <%= render :partial => 'voter_list' %> @@ -14,7 +19,9 @@ vote in this election.

<% end %>
-<%= check_box :election, :authenticated %> Only allow registered voters +<% unless @election.kiosk? %> + <%= check_box :election, :authenticated %> Only allow registered voters +<% end %> <%= observe_field "election_authenticated", :url => { :action => 'toggle_authenticated', :id => @election.id }, @@ -32,4 +39,13 @@ vote in this election.

When you are done entering voters, please click the button below to proceed to the next step.

-<%= button_to 'Proceed to Next Step!', :action => 'show', :id => @election.id %> +<%= button_to 'Proceed to Next Step', :action => 'show', :id => @election.id %> + +<% else %> + +

You can not edit the list of voters once the election has begun. +please return to the <%= link_to "election overview page", :action => +'show', :id => @election.id %>.

+ +<% end %> + diff --git a/app/views/election/show.rhtml b/app/views/election/show.rhtml index 2f62895..39621bf 100644 --- a/app/views/election/show.rhtml +++ b/app/views/election/show.rhtml @@ -8,13 +8,13 @@ :election_id => @election.id %>. <% elsif (@election.active? && @election.early_results? ) %> -
The creator of this election has decided that the results +
You have decided that the results should be viewable while the election is in progress. - <%if @election.voters.empty? %> + <%if @election.voters.select {|v| v.vote.confirmed?}.empty? %> However, no one has voted yet. <% else %> - <%= link_to "View results", :controller => 'voter', :action => 'results', - :id => "open" %>. + <%= link_to "View results", :controller => 'election', :action => 'results', + :id => @election.id %>. <% end %>
<% elsif @election.active? %> diff --git a/app/views/voter/_results_sidebar.rhtml b/app/views/voter/_full_results_sidebar.rhtml similarity index 99% rename from app/views/voter/_results_sidebar.rhtml rename to app/views/voter/_full_results_sidebar.rhtml index 81cc907..b85390f 100644 --- a/app/views/voter/_results_sidebar.rhtml +++ b/app/views/voter/_full_results_sidebar.rhtml @@ -4,4 +4,3 @@

Details

<%= link_to "Auditing Information", { :action => 'details', :id => @password }, :popup => [] %>

- diff --git a/app/views/voter/review.rhtml b/app/views/voter/review.rhtml index b831aef..a72ecda 100644 --- a/app/views/voter/review.rhtml +++ b/app/views/voter/review.rhtml @@ -24,15 +24,28 @@ preferred to least preferred:

Please select from one of the following pages.

-

<%= button_to 'Confirm This Vote', :action => 'confirm', :id => @password %>

- -

If you choose, you will be able to go back
and change it up until - the end of the voting period.

+

<% if @voter.election.kiosk? and params[:kiosk] == 'true' %> + <%= button_to 'Confirm This Vote', :action => 'confirm', :id => @password, + :kiosk => true %> + <% else %> + <%= button_to 'Confirm This Vote', :action => 'confirm', :id => @password %> + <% end %> +

+ + <% unless @voter.election.kiosk? %> +

If you choose, you will be able to go back
and change it up until + the end of the voting period.

+ <% end %> -

<%= button_to 'Discard This Vote', votepassword_url( - :action => 'index', :urlpassword => @password) %>

- -

You will be returned to the voting page to vote
again, if you choose.

- +

<% if @voter.election.kiosk? and params[:kiosk] == 'true' %> + <%= button_to 'Discard This Vote', + votepassword_url(:action => 'index', :urlpassword => @password, + :kiosk => true) %> + <% else %> + <%= button_to 'Discard This Vote', + votepassword_url(:action => 'index', :urlpassword => @password) %> + <% end %> +

+
diff --git a/app/views/voter/thanks.rhtml b/app/views/voter/thanks.rhtml index 7562c61..c68e22b 100644 --- a/app/views/voter/thanks.rhtml +++ b/app/views/voter/thanks.rhtml @@ -13,3 +13,12 @@ end of the election, you will be able to use this token to verify that your vote was used in the election and that your vote was recorded correctly.

+ +<% if @voter.election.kiosk? and params[:kiosk] == 'true' %> + +

Please click the done button below when finished to reset the system +for the next voter.

+ +<%= button_to "Done", :action => "kiosk_ready", :id => @password, :kiosk => true %> + +<% end %>