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
- 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
-
+===============================================
+=== 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
+
+
===============================================
=== Dependencies ==============================
===============================================
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 <mako@atdot.cc>
* John Dong <jdong@ubuntu.com>
- * Justin Sharps <jlsharps@mit.edu>
-
-===============================================
-=== 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")
-=> #<VoteResultStruct:0x336f92c @approval_winners=[1, 2], @borda_winners=[1], @plurality_winners=[1], @ssd_winners=[1], @errors=[], @condorcet_winners=[1]>
-
-Casting a quickvote:
-client.cast_quickvote("test",1,[[1,2]])
-
-To figure out what you're voting for:
->> client.get_quickvote_candidate_map("test")=> #<CandidateMap:0x335bbc0 @errors=[], @candidate_names=["test", "test2"], @candidate_ids=[1, 2]>
-
-
-
+ * Justin Sharps <jlsharps@alum.mit.edu>
# Copyright (C) 2007, 2008 Benjamin Mako Hill <mako@atdot.cc>
# 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
-# <http://www.gnu.org/licenses/>.
+# This program is free software. Please see the COPYING file for
+# details.
class QuickvoteController < ApplicationController
helper :sparklines
#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
# 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,
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
@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
# Copyright (C) 2007, 2008 Benjamin Mako Hill <mako@atdot.cc>
# 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
-# <http://www.gnu.org/licenses/>.
+# This program is free software. Please see the COPYING file for
+# details.
class VoterController < ApplicationController
helper :sparklines
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])
@voter = OpenVoter.find(:all,
:conditions => ["session_id = ? and election_id = ?",
session.session_id, @election.id])[0]
-
-
+
@voter = OpenVoter.new unless @voter
@voter.election = @election
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
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)
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
# Copyright (C) 2007, 2008 Benjamin Mako Hill <mako@atdot.cc>
# 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
-# <http://www.gnu.org/licenses/>.
+# This program is free software. Please see the COPYING file for
+# details.
class Election < ActiveRecord::Base
has_many :candidates
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
attr_reader :borda_result
require 'date'
-
+
def initialize(params={})
super
self.enddate = read_attribute( :enddate ) || \
def activate!
self.active = 1
- self.save!
+ self.save
end
def quickvote?
def authenticated?
authenticated
end
+
+ def kiosk?
+ kiosk
+ end
def shortdesc
shortdesc = description.split(/\n/)[0]
# 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
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
# Copyright (C) 2007, 2008 Benjamin Mako Hill <mako@atdot.cc>
# 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
-# <http://www.gnu.org/licenses/>.
+# This program is free software. Please see the COPYING file for
+# details.
class Vote < ActiveRecord::Base
# relationships to other classes
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