From: Benjamin Mako Hill Date: Sun, 17 Jun 2012 22:59:36 +0000 (-0400) Subject: Merge branch 'live' of ssh://ephesus.xvm.mit.edu/org/selectricity/selectricity-live X-Git-Url: https://projects.mako.cc/source/selectricity/commitdiff_plain/005f5a86085c9cbcbc09c6655f3851a877462638?hp=-c Merge branch 'live' of ssh://ephesus.xvm.mit.edu/org/selectricity/selectricity-live Conflicts: README app/controllers/voter_controller.rb app/views/layouts/_footer.rhtml --- 005f5a86085c9cbcbc09c6655f3851a877462638 diff --combined README index 9ec9d62,afa0a25..5ce586b --- a/README +++ b/README @@@ -8,35 -8,28 +8,43 @@@ distribute, or rework Selectricity unde course, we'd sure like it if you would send fixes back to us and tell us about cool stuff you do with our software! --The best way to get Selectricity is just to download it from our source - tree. At the moment, we're hosting our code at Gitorious which is a - free software hosting provider. You can download Gitorious at the - project page by following detailed instructions at Gitorious: ++The best way to get Selectricity is just to download it from our + source repository. You'll need the Git version control system or + source control manager to check it. You can get it here: - http://gitorious.org/selectricity + http://git-scm.com/ + + Once you have it, getting the source code is pretty easy. You just need + to check out a branch with a command like this: + + git clone http://projects.mako.cc/source/selectricity/.git + + By default, this will create a working copy with the latest + *development* version of our code. If you want the latest production + version (i.e., what we're running on the site), you need to switch to + the live version of the software which is kept in a branch called + "live." Once you cloned the repository above, you can switch into the + directory (i.e., run "cd selectricity") and then run the following + command: + + git checkout -b live origin/live +=============================================== +=== Getting Help and Contributing ============= +=============================================== + +If you have a question, you can always email the core team at: + + team@selectricity.org + +If you want to get involved in development, want to discuss +selectricity, or want to participate, please subscribe to our mailing +list here: + + http://mailman.mit.edu/mailman/listinfo/selectricity + - In terms of bugs and documentation, we current plan to build this out in - our Gitorious wiki so feel free to get started with your own efforts - along these lines there: - - http://gitorious.org/selectricity/pages/Home - + =============================================== === Dependencies ============================== =============================================== @@@ -65,18 -58,88 +73,17 @@@ On Ubuntu, you can install install the Our server configuration uses Mongrel (installed from gems) behind an Apache2 load balancing proxy using mod_proxy. +You'll also need to have a MTA installed. We use Postfix and have not +tried it with any other system. Presumably though, anything that +provides '/usr/bin/sendmail' should work. + - =============================================== === Contributors to Selectricity Include ====== =============================================== * Benjamin Mako Hill * John Dong - * Justin Sharps - -=============================================== -=== Log ======================================= -=============================================== - -07/31/07 -jlsharps: I've added a user authentication system known as -"acts_as_authenticated" to the code. The plugin is the the vendor/plugins -directory. The two most noticeable changes are the AccountController and a -redone User model. I've left the UserController in place for now, but the -AccountController works in a different manner, so am switching over to that -gradually. I saved the 5 lines or so in the old User model, overwrote -it with the authenticated generator and then recopied the old stuff back in: -has_many :elections and the name() method. The generator also creates its own -migration file, but since we are using a create.sql file I adopted the -migration file into a new users table in the create.sql file. I have yet to -delete the old table because I haven't fully combed through the code yet and -determined how many of the old attributes (such as first_name, last_name) may -need to be retained. -http://technoweenie.stikipad.com/plugins/show/Acts+as+Authenticated is the -best site for documentation regarding acts_as_authenticaed. Also, currently -it only stores the user_id in the session, but i just found a guide to help -me make it store the entire user object, so I'll do that while my battery -charges. - -08/03/07 -Handy trick: use the command 'gem_server' from a shell to create a server at -http://localhost:8008 that is an easy to navigate locally-hosted website with -all the documentation on local gems you have in a easy to read format. - -jlsharps: I added the Gruff plug-in today, which is viewable under the folder -vender/plugins/gruff. I installed it directly using the Gruff plug-in and -included controller generate utility. The version 0.1.2, which doesn't seem to -be the latest version. I've looked into it and it see and it seems that the -latest version is 0.2.8. However, I wasn't sure how including a gem w/o a plugin -would function in end-game rails so I just what I used for now. If you guys -(mako of john) know how to do it, it'd probably be better to upgrade, but it -didn't seem like the best use of my time right now. I got the plug-in here: -http://topfunky.net/svn/plugins/gruff. You can get the gruff gem v 0.2.8 by -typing "sudo gem install gruff", I believe it's also hosted on RubyForge. - -I created the GraphsController for Gruff methods to use. In Pollarize I put them -in the ApplicationContorller file, so they would be accessible to all. While -that it also an option here, it would also mean there wouldn't be much room for -playing around because everything in the Application file has to be perfect or -it seems to throw Error Code 500 (basically everything breaks). The show() -is a sample sample provided with Gruff. - -Documentation is here:http://gruff.rubyforge.org/ Alternately, if you have the -gem installed, you can use the ri command, or the above mentioned gem_server. - -If you guys want more helpful stuff here, let me know. - -====================================== -=== XML-RPC INFO == -====================================== - -The XML-RPC API is still under development, but is somewhat functional already: - -To instantiate a client in Ruby, try something like: -client=ActionWebService::Client::XmlRpc.new(SelectricityAPI,"http://localhost:3000/selectricity_service/vote") - - -Getting the results of a quickvote is quite simple: -?> client.get_quickvote_results("test") -=> # - -Casting a quickvote: -client.cast_quickvote("test",1,[[1,2]]) - -To figure out what you're voting for: ->> client.get_quickvote_candidate_map("test")=> # - - - + * Justin Sharps diff --combined app/controllers/quickvote_controller.rb index 4b3c0c9,cd16743..701005d --- a/app/controllers/quickvote_controller.rb +++ b/app/controllers/quickvote_controller.rb @@@ -2,8 -2,19 +2,8 @@@ # Copyright (C) 2007, 2008 Benjamin Mako Hill # Copyright (C) 2007 Massachusetts Institute of Technology # -# This program 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 (at your option) any later version. -# -# This program 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 Affero General Public -# License along with this program. If not, see -# . +# This program is free software. Please see the COPYING file for +# details. class QuickvoteController < ApplicationController helper :sparklines @@@ -44,7 -55,6 +44,7 @@@ #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 @@@ -97,13 -107,6 +97,13 @@@ # if the person has specified an election, we show them the voting # page. otherwise, we redirect back to main the page if @election + + # if the election is over, redirect to the the results page + unless @election.active? + redirect_to quickaction_url(:ident => params[:ident], + :action => 'results') + end + # look to see that the voter has been created and has voted in # this election, and has confirmed their vote @voter = QuickVoter.find(:all, @@@ -158,14 -161,16 +158,16 @@@ else # record the ip address for posterity - @voter.ipaddress = request.env["REMOTE_ADDR"] + @voter.ipaddress = request.env["HTTP_X_FORWARDED_FOR"] @voter.save # toggle the confirmation bit - @voter.vote.confirm! - - @voter.reload - render :action => 'thanks' + if @voter.vote.confirm! + @voter.reload + render :action => 'thanks' + else + redirect_to :action => 'index' + end end end @@@ -183,9 -188,10 +185,10 @@@ @election=QuickVote.ident_to_quickvote(params[:id]) @election.voters.each do |voter| next unless voter.ipaddress + location=nil - if defined? Cache and location=Cache.get("GEO:#{voter.ipaddress}") - elsif defined? Cache + if Cache and location=Cache.get("GEO:#{voter.ipaddress}") + elsif Cache location = GeoKit::Geocoders::IpGeocoder.geocode(voter.ipaddress) Cache.set "GEO:#{voter.ipaddress}", location else diff --combined app/controllers/voter_controller.rb index 408bdab,78f9a0c..a550f67 --- a/app/controllers/voter_controller.rb +++ b/app/controllers/voter_controller.rb @@@ -2,8 -2,19 +2,8 @@@ # Copyright (C) 2007, 2008 Benjamin Mako Hill # Copyright (C) 2007 Massachusetts Institute of Technology # -# This program 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 (at your option) any later version. -# -# This program 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 Affero General Public -# License along with this program. If not, see -# . +# This program is free software. Please see the COPYING file for +# details. class VoterController < ApplicationController helper :sparklines @@@ -12,9 -23,6 +12,9 @@@ require_dependency "vote" require_dependency "election" + before_filter :authenticate, :except => [:index, :login, :reminder, + :kiosk_ready, :sort_candidates] + def index if params[:election_id] @election = Election.find(params[:election_id]) @@@ -22,7 -30,8 +22,7 @@@ @voter = OpenVoter.find(:all, :conditions => ["session_id = ? and election_id = ?", session.session_id, @election.id])[0] - - + @voter = OpenVoter.new unless @voter @voter.election = @election @@@ -54,24 -63,18 +54,24 @@@ else @sidebar_content = render_to_string(:partial => 'vote_sidebar') if @election.embeddable? and params[:embed] == "true" - #look for custom theme, and assign to instance variabels for widget use + # look for custom theme, and assign to instance variabels + # for widget use if @election.embed_custom_string @top_bar = SkinPicture.find(:first, - :conditions => ["filename = ?", @election.embed_custom_string + "top_bar.png"]) + :conditions => ["filename = ?", + @election.embed_custom_string + "top_bar.png"]) @default_image = SkinPicture.find(:first, - :conditions => ["filename = ?", @election.embed_custom_string + "default_image.png"]) + :conditions => ["filename = ?", + @election.embed_custom_string + "default_image.png"]) @bg1 = SkinPicture.find(:first, - :conditions => ["filename = ?", @election.embed_custom_string + "bg1.png"]) + :conditions => ["filename = ?", + @election.embed_custom_string + "bg1.png"]) @bg2 = SkinPicture.find(:first, - :conditions => ["filename = ?", @election.embed_custom_string + "bg2.png"]) + :conditions => ["filename = ?", + @election.embed_custom_string + "bg2.png"]) @bottom_bar = SkinPicture.find(:first, - :conditions => ["filename = ?", @election.embed_custom_string + "bottom_bar.png"]) + :conditions => ["filename = ?", + @election.embed_custom_string + "bottom_bar.png"]) end render :template => 'embed/full_vote', :layout => 'embed' else @@@ -83,37 -86,66 +83,39 @@@ def login if params[:vote] and params[:vote][:password] - redirect_to votepassword_url( :action => 'index', :urlpassword => params[:vote][:password]) + redirect_to votepassword_url(:action => 'index', + :urlpassword => params[:vote][:password]) else redirect_to :action => 'index' 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 - @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 - if authenticate - if @voter.vote.confirm! - if @voter.election.embeddable? and params[:embed] == "true" \ - and @voter.election.early_results? - redirect_to :action => :results, :id => @password, :embed => 'true' - else - render :action => 'thanks' - end ++ if @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 else - render :action => 'thanks' - redirect_to :action => 'index' ++ redirect_to :action => 'index' end end def reminder if params[:email] - voter_array= FullVoter.find(:all, :conditions => ["email = ?", params[:email]]) + voter_array= FullVoter.find(:all, + :conditions => ["email = ?", params[:email]]) voter_array.delete_if {|voter| voter.election.active == 0} unless voter_array.empty? VoterNotify.deliver_reminder(voter_array) @@@ -128,96 -160,64 +130,96 @@@ 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') - #look for custom theme, and assign to instance variabels for widget use + @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, - :conditions => ["filename = ?", @election.embed_custom_string + "top_bar.png"]) + :conditions => ["filename = ?", + @election.embed_custom_string + "top_bar.png"]) @default_image = SkinPicture.find(:first, - :conditions => ["filename = ?", @election.embed_custom_string + "default_image.png"]) + :conditions => ["filename = ?", + @election.embed_custom_string + "default_image.png"]) @bg1 = SkinPicture.find(:first, - :conditions => ["filename = ?", @election.embed_custom_string + "bg1.png"]) + :conditions => ["filename = ?", + @election.embed_custom_string + "bg1.png"]) @bg2 = SkinPicture.find(:first, - :conditions => ["filename = ?", @election.embed_custom_string + "bg2.png"]) + :conditions => ["filename = ?", + @election.embed_custom_string + "bg2.png"]) @bottom_bar = SkinPicture.find(:first, - :conditions => ["filename = ?", @election.embed_custom_string + "bottom_bar.png"]) + :conditions => ["filename = ?", + @election.embed_custom_string + "bottom_bar.png"]) end 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 + + if not authenticate + redirect_to :action => 'index' + end + end + private def authenticate password = params[:id] if password == "open" election = Election.find(params[:format]) - # double check to make sure the election is not authenticated - 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] - # if the election is over, proceed - if (not @voter) and (election.enddate < Time.now) - @voter = OpenVoter.new - @voter.election = election + # when (a) there is no voter or (b) when there is a voter but + # it's kiosk mode on the right page, rewrite with a blank voter + if ((not @voter) and (election.enddate < Time.now)) \ + 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 end 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 --combined app/models/election.rb index 4ce571a,7c15dcf..b48c923 --- a/app/models/election.rb +++ b/app/models/election.rb @@@ -2,8 -2,19 +2,8 @@@ # Copyright (C) 2007, 2008 Benjamin Mako Hill # Copyright (C) 2007 Massachusetts Institute of Technology # -# This program 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 (at your option) any later version. -# -# This program 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 Affero General Public -# License along with this program. If not, see -# . +# This program is free software. Please see the COPYING file for +# details. class Election < ActiveRecord::Base has_many :candidates @@@ -11,12 -22,9 +11,12 @@@ 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 @@@ -24,7 -32,7 +24,7 @@@ attr_reader :borda_result require 'date' - + def initialize(params={}) super self.enddate = read_attribute( :enddate ) || \ @@@ -74,7 -82,7 +74,7 @@@ def activate! self.active = 1 - self.save! + self.save end def quickvote? @@@ -92,10 -100,6 +92,10 @@@ def authenticated? authenticated end + + def kiosk? + kiosk + end def shortdesc shortdesc = description.split(/\n/)[0] @@@ -137,11 -141,11 +137,11 @@@ # skip if the voter has not voted or has an unconfirmed vote next unless voter.voted? - plurality_tally << voter.vote.rankings.sort[0].candidate.id + plurality_tally << voter.vote.rankings.sort[0].candidate_id approval_tally << voter.vote.rankings.sort[0..1].collect \ - { |ranking| ranking.candidate.id } + { |ranking| ranking.candidate_id } preference_tally << voter.vote.rankings.sort.collect \ - { |ranking| ranking.candidate.id } + { |ranking| ranking.candidate_id } end @plurality_result = PluralityVote.new(plurality_tally).result @@@ -167,30 -171,6 +167,30 @@@ 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 --combined app/models/vote.rb index cc42311,719aa7b..12dd64d --- a/app/models/vote.rb +++ b/app/models/vote.rb @@@ -2,8 -2,19 +2,8 @@@ # Copyright (C) 2007, 2008 Benjamin Mako Hill # Copyright (C) 2007 Massachusetts Institute of Technology # -# This program 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 (at your option) any later version. -# -# This program 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 Affero General Public -# License along with this program. If not, see -# . +# This program is free software. Please see the COPYING file for +# details. class Vote < ActiveRecord::Base # relationships to other classes @@@ -62,14 -73,19 +62,19 @@@ end def confirm! - self.confirmed = 1 - self.time = Time.now - self.save - - unless self.voter.election.quickvote? - token.destroy and token.reload if token - self.token = Token.new + if self.voter.election.candidates.length == self.rankings.length + self.confirmed = 1 + self.time = Time.now self.save + + unless self.voter.election.quickvote? + token.destroy and token.reload if token + self.token = Token.new + self.save + end + return false + else + return true end end