refinerycms-ballots 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/app/controllers/admin/ballots_controller.rb +9 -0
- data/app/controllers/ballots_controller.rb +24 -0
- data/app/controllers/votes_controller.rb +114 -0
- data/app/helpers/admin/ballots_helper.rb +19 -0
- data/app/helpers/votes_helper.rb +26 -0
- data/app/models/ballot.rb +67 -0
- data/app/models/ballot_vote.rb +41 -0
- data/app/models/candidate.rb +22 -0
- data/app/models/candidate_vote.rb +17 -0
- data/app/models/office.rb +20 -0
- data/app/models/office_vote.rb +33 -0
- data/app/views/admin/ballots/_actions.html.erb +28 -0
- data/app/views/admin/ballots/_ballot.html.erb +21 -0
- data/app/views/admin/ballots/_ballots.html.erb +2 -0
- data/app/views/admin/ballots/_candidate_fields.html.erb +6 -0
- data/app/views/admin/ballots/_form.html.erb +40 -0
- data/app/views/admin/ballots/_office_fields.html.erb +21 -0
- data/app/views/admin/ballots/_records.html.erb +18 -0
- data/app/views/admin/ballots/_show_voters.html.erb +6 -0
- data/app/views/admin/ballots/_sortable_list.html.erb +7 -0
- data/app/views/admin/ballots/edit.html.erb +1 -0
- data/app/views/admin/ballots/index.html.erb +10 -0
- data/app/views/admin/ballots/new.html.erb +1 -0
- data/app/views/admin/ballots/show.html.erb +32 -0
- data/app/views/votes/_confirmation_office_votes_display.html.erb +30 -0
- data/app/views/votes/confirm.html.erb +34 -0
- data/app/views/votes/create.html.erb +15 -0
- data/app/views/votes/login.html.erb +24 -0
- data/app/views/votes/new.html.erb +41 -0
- data/config/locales/en.yml +25 -0
- data/config/locales/fr.yml +25 -0
- data/config/locales/lolcat.yml +25 -0
- data/config/locales/nb.yml +21 -0
- data/config/locales/nl.yml +21 -0
- data/config/routes.rb +21 -0
- data/lib/generators/refinerycms_ballots_generator.rb +6 -0
- data/lib/member_extensions.rb +12 -0
- data/lib/refinerycms-ballots.rb +30 -0
- data/lib/tasks/ballots.rake +13 -0
- metadata +105 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
class BallotsController < ApplicationController
|
2
|
+
|
3
|
+
before_filter :find_all_ballots
|
4
|
+
before_filter :find_page
|
5
|
+
|
6
|
+
def index
|
7
|
+
redirect_to root_url
|
8
|
+
end
|
9
|
+
|
10
|
+
def show
|
11
|
+
redirect_to root_url
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def find_all_ballots
|
17
|
+
@ballots = Ballot.order('position ASC')
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_page
|
21
|
+
@page = Page.where(:link_url => "/ballots").first
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
class VotesController < ApplicationController
|
2
|
+
|
3
|
+
before_filter :find_ballot
|
4
|
+
before_filter :find_page
|
5
|
+
before_filter :find_member, :except => :login
|
6
|
+
|
7
|
+
def login
|
8
|
+
if @ballot.too_early_to_vote?
|
9
|
+
flash[:error] = "This election doesn't start until #{@ballot.pretty_start_date}"
|
10
|
+
redirect_to root_url
|
11
|
+
elsif @ballot.too_late_to_vote?
|
12
|
+
flash[:error] = "This election ended on #{@ballot.pretty_end_date}"
|
13
|
+
redirect_to root_url
|
14
|
+
else
|
15
|
+
present @page
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# this is basically a state machine to take you through the form
|
20
|
+
def proceed
|
21
|
+
case params[:commit]
|
22
|
+
when /proceed/i then new # login -> new
|
23
|
+
when /vote/i then confirm # new -> confirm
|
24
|
+
when /back/i then back_to_new # confirm -> new
|
25
|
+
when /confirm/i then create # confirm -> create
|
26
|
+
else
|
27
|
+
logger.error "VotesController#proceed was called with params[:commit]=#{params[:commit].inspect}"
|
28
|
+
unspecified_error
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def new
|
37
|
+
if !@member
|
38
|
+
flash[:error] = 'Please double check your number and try again. If it is correct, you may need to contact an admin.'
|
39
|
+
redirect_to login_ballot_votes_path(@ballot)
|
40
|
+
elsif @member.has_voted_on?(@ballot)
|
41
|
+
flash[:error] = "You have already voted on this ballot, revoting isn't allowed"
|
42
|
+
redirect_to login_ballot_votes_path(@ballot)
|
43
|
+
else
|
44
|
+
@ballot_vote = BallotVote.new_for_ballot @ballot
|
45
|
+
render :new
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def confirm
|
50
|
+
find_ballot_vote
|
51
|
+
if cannot_create_ballot_vote?
|
52
|
+
log_cant_create_ballot('new->confirm')
|
53
|
+
unspecified_error
|
54
|
+
elsif @ballot_vote.valid?
|
55
|
+
render :confirm
|
56
|
+
else
|
57
|
+
render :new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def back_to_new
|
62
|
+
find_ballot_vote
|
63
|
+
render :new
|
64
|
+
end
|
65
|
+
|
66
|
+
def create
|
67
|
+
find_ballot_vote
|
68
|
+
if can_create_ballot_vote? && @ballot_vote.save
|
69
|
+
flash[:notice] = "Thank you for voting."
|
70
|
+
redirect_to root_url
|
71
|
+
else
|
72
|
+
log_cant_create_ballot('confirm->create')
|
73
|
+
unspecified_error
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def unspecified_error
|
78
|
+
flash[:error] = "It appears there was a problem, please try again."
|
79
|
+
redirect_to login_ballot_votes_path(@ballot)
|
80
|
+
end
|
81
|
+
|
82
|
+
def find_ballot
|
83
|
+
@ballot = Ballot.where(:id => params[:ballot_id]).first
|
84
|
+
end
|
85
|
+
|
86
|
+
def find_page
|
87
|
+
@page = Page.where(:link_url => "/ballots").first
|
88
|
+
end
|
89
|
+
|
90
|
+
def find_member
|
91
|
+
@member = Member.find_by_unique_identifier params[:unique_identifier]
|
92
|
+
end
|
93
|
+
|
94
|
+
def find_ballot_vote
|
95
|
+
@ballot_vote = BallotVote.new params[:ballot_vote]
|
96
|
+
@ballot_vote.ballot = @ballot
|
97
|
+
@ballot_vote.member = @member
|
98
|
+
end
|
99
|
+
|
100
|
+
def cannot_create_ballot_vote?
|
101
|
+
!can_create_ballot_vote?
|
102
|
+
end
|
103
|
+
|
104
|
+
def can_create_ballot_vote?
|
105
|
+
@member && !@ballot_vote.tampered?
|
106
|
+
end
|
107
|
+
|
108
|
+
def log_cant_create_ballot(path_through_state_machine='')
|
109
|
+
logger.error "Ballots #{path_through_state_machine} failed @member=#{@member.inspect}"
|
110
|
+
logger.error "Ballots #{path_through_state_machine} failed @ballot_vote.tampered?=#{@ballot_vote.tampered?.inspect}"
|
111
|
+
logger.error "Ballots #{path_through_state_machine} failed @ballot_vote.errors=#{@ballot_vote.errors.inspect}"
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Admin
|
2
|
+
module BallotsHelper
|
3
|
+
def link_to_remove_fields name, f
|
4
|
+
link_to_function(name, "remove_fields(this)") + f.hidden_field(:_destroy)
|
5
|
+
end
|
6
|
+
|
7
|
+
def link_to_add_fields(name, f, association)
|
8
|
+
new_object = f.object.class.reflect_on_association(association).klass.new
|
9
|
+
fields = f.fields_for association, new_object, :child_index => "new_#{association}" do |builder|
|
10
|
+
render association.to_s.singularize + "_fields", :f => builder
|
11
|
+
end
|
12
|
+
raw link_to_function(name, "add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")")
|
13
|
+
end
|
14
|
+
|
15
|
+
def position_message_for(n)
|
16
|
+
"Vote for #{n}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module VotesHelper
|
2
|
+
include Admin::BallotsHelper
|
3
|
+
def undervote_message_for(office_vote)
|
4
|
+
"you have voted for #{pluralize office_vote.num_votes, 'candidate', 'candidates'}, " \
|
5
|
+
"which is fewer than the #{office_vote.max_num_votes} you could have voted for."
|
6
|
+
end
|
7
|
+
|
8
|
+
def undervoted?(office_vote)
|
9
|
+
office_vote.num_votes < office_vote.max_num_votes
|
10
|
+
end
|
11
|
+
|
12
|
+
def show_errors_for_ballot_vote(ballot)
|
13
|
+
errors = ballot.errors[:base]
|
14
|
+
return '' if errors.empty?
|
15
|
+
raw "<div class='errorExplanation'><ul>#{errors_as_li errors}</ul></div>"
|
16
|
+
end
|
17
|
+
|
18
|
+
def show_errors_for_office_vote(office)
|
19
|
+
return '' if office.errors.empty?
|
20
|
+
raw "<div class='errorExplanation'><ul>#{errors_as_li office.errors.full_messages}</ul></div>"
|
21
|
+
end
|
22
|
+
|
23
|
+
def errors_as_li(error_messages)
|
24
|
+
error_messages.map { |msg| "<li>#{msg}</li>" }.join
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Ballot < ActiveRecord::Base
|
2
|
+
|
3
|
+
acts_as_indexed :fields => [:title]
|
4
|
+
has_many :offices, :dependent => :destroy
|
5
|
+
has_many :ballot_votes
|
6
|
+
has_many :candidate_votes, :through => :ballot_votes # this will work in rails 3.1, but not 3.0.10, at which time, we can change the implementation of number_of_votes
|
7
|
+
|
8
|
+
validates :title, :presence => true, :uniqueness => true
|
9
|
+
validate :start_date_is_before_end_date
|
10
|
+
|
11
|
+
accepts_nested_attributes_for :offices,
|
12
|
+
:reject_if => lambda { |attrs| attrs[:title].blank? },
|
13
|
+
:allow_destroy => true
|
14
|
+
|
15
|
+
def pretty_start_date
|
16
|
+
pretty_date start_date
|
17
|
+
end
|
18
|
+
|
19
|
+
def pretty_end_date
|
20
|
+
pretty_date end_date
|
21
|
+
end
|
22
|
+
|
23
|
+
def pretty_date(date)
|
24
|
+
date.strftime "%B %e, %Y"
|
25
|
+
end
|
26
|
+
|
27
|
+
def open_for_voting?
|
28
|
+
start_date <= today && today <= end_date
|
29
|
+
end
|
30
|
+
|
31
|
+
def too_early_to_vote?
|
32
|
+
today < start_date
|
33
|
+
end
|
34
|
+
|
35
|
+
def too_late_to_vote?
|
36
|
+
end_date < today
|
37
|
+
end
|
38
|
+
|
39
|
+
def number_of_votes
|
40
|
+
sums = ballot_votes.map do |ballot_vote|
|
41
|
+
ballot_vote.candidate_votes.where(:voted => true).count
|
42
|
+
end
|
43
|
+
sums.inject :+
|
44
|
+
end
|
45
|
+
|
46
|
+
def number_of_voters
|
47
|
+
ballot_votes.count
|
48
|
+
end
|
49
|
+
|
50
|
+
def voting_members
|
51
|
+
Member.all.select { |member| member.has_voted_on? self }
|
52
|
+
end
|
53
|
+
|
54
|
+
def nonvoting_members
|
55
|
+
Member.all.reject { |member| member.has_voted_on? self }
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def start_date_is_before_end_date
|
60
|
+
return if start_date < end_date
|
61
|
+
errors[:start_date] << 'must be after end date'
|
62
|
+
end
|
63
|
+
private
|
64
|
+
def today
|
65
|
+
Time.now
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class BallotVote < ActiveRecord::Base
|
2
|
+
belongs_to :member
|
3
|
+
belongs_to :ballot
|
4
|
+
has_many :office_votes
|
5
|
+
has_many :candidate_votes, :through => :office_votes
|
6
|
+
validates_associated :office_votes
|
7
|
+
validate :ballot_must_be_open_for_voting
|
8
|
+
validate :member_vote_must_be_unique
|
9
|
+
|
10
|
+
accepts_nested_attributes_for :office_votes, :allow_destroy => true
|
11
|
+
|
12
|
+
def self.new_for_ballot(ballot)
|
13
|
+
bv = BallotVote.new :ballot => ballot
|
14
|
+
ballot.offices.each do |office|
|
15
|
+
ov = bv.office_votes.build :office => office
|
16
|
+
office.candidates.each do |candidate|
|
17
|
+
ov.candidate_votes.build :candidate => candidate
|
18
|
+
end
|
19
|
+
end
|
20
|
+
bv
|
21
|
+
end
|
22
|
+
|
23
|
+
def tampered?
|
24
|
+
!ballot || office_votes.any? { |office_vote| office_vote.tampered? ballot }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def member_vote_must_be_unique
|
29
|
+
count = BallotVote.where(:member_id => member_id, :ballot_id => ballot_id).count
|
30
|
+
return if count.zero?
|
31
|
+
errors[:base] << "You cannot vote twice"
|
32
|
+
end
|
33
|
+
|
34
|
+
def ballot_must_be_open_for_voting
|
35
|
+
if ballot.too_early_to_vote?
|
36
|
+
errors[:base] << "This ballot won't open for voting until #{ballot.pretty_start_date}."
|
37
|
+
elsif ballot.too_late_to_vote?
|
38
|
+
errors[:base] << "This ballot closed for voting on #{ballot.pretty_end_date}."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Candidate < ActiveRecord::Base
|
2
|
+
|
3
|
+
acts_as_indexed :fields => [:name]
|
4
|
+
belongs_to :office
|
5
|
+
has_many :candidate_votes
|
6
|
+
|
7
|
+
validates :name, :presence => true
|
8
|
+
|
9
|
+
def percentage
|
10
|
+
return 0 if number_of_votes.zero?
|
11
|
+
100 * number_of_votes / number_of_voters
|
12
|
+
end
|
13
|
+
|
14
|
+
def number_of_votes
|
15
|
+
candidate_votes.where(:voted => true).count
|
16
|
+
end
|
17
|
+
|
18
|
+
def number_of_voters
|
19
|
+
candidate_votes.count
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CandidateVote < ActiveRecord::Base
|
2
|
+
belongs_to :office_vote
|
3
|
+
belongs_to :candidate
|
4
|
+
|
5
|
+
def name
|
6
|
+
candidate && candidate.name
|
7
|
+
end
|
8
|
+
|
9
|
+
def vote
|
10
|
+
self.voted = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def tampered?(office)
|
14
|
+
!candidate || candidate.office != office
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Office < ActiveRecord::Base
|
2
|
+
|
3
|
+
acts_as_indexed :fields => [:title]
|
4
|
+
has_many :candidates, :dependent => :destroy
|
5
|
+
belongs_to :ballot
|
6
|
+
|
7
|
+
validates :title, :presence => true
|
8
|
+
|
9
|
+
|
10
|
+
validates_numericality_of :number_of_positions, :only_integer => true, :message => "is the number of people who can be elected to this office (an integer)."
|
11
|
+
|
12
|
+
accepts_nested_attributes_for :candidates,
|
13
|
+
:reject_if => lambda { |attrs| attrs[:name].blank? },
|
14
|
+
:allow_destroy => true
|
15
|
+
|
16
|
+
def candidates_by_rating
|
17
|
+
candidates.sort_by { |candidate| candidate.number_of_votes }.reverse
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class OfficeVote < ActiveRecord::Base
|
2
|
+
belongs_to :ballot_vote
|
3
|
+
belongs_to :office
|
4
|
+
has_many :candidate_votes
|
5
|
+
validate :cannot_overvote
|
6
|
+
|
7
|
+
accepts_nested_attributes_for :candidate_votes, :allow_destroy => true
|
8
|
+
|
9
|
+
def title
|
10
|
+
office && office.title
|
11
|
+
end
|
12
|
+
|
13
|
+
def num_votes
|
14
|
+
candidate_votes.select { |cv| cv.voted? }.size
|
15
|
+
end
|
16
|
+
|
17
|
+
def max_num_votes
|
18
|
+
(office && office.number_of_positions) || 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def tampered?(ballot)
|
22
|
+
!office || office.ballot != ballot || candidate_votes.any? { |candidate_vote| candidate_vote.tampered? office }
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def cannot_overvote
|
28
|
+
if num_votes > max_num_votes
|
29
|
+
errors[:base] << "Cannot vote for #{num_votes}, maximum is #{max_num_votes}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<ul>
|
2
|
+
<% if Admin::BallotsController.searchable? %>
|
3
|
+
<li>
|
4
|
+
<%= render :partial => "/shared/admin/search",
|
5
|
+
:locals => {
|
6
|
+
:url => admin_ballots_path
|
7
|
+
} %>
|
8
|
+
</li>
|
9
|
+
<% end %>
|
10
|
+
<li>
|
11
|
+
<%= link_to t('.create_new'), new_admin_ballot_path,
|
12
|
+
:class => "add_icon" %>
|
13
|
+
</li>
|
14
|
+
<% if !searching? and Admin::BallotsController.sortable? and Ballot.count > 1 %>
|
15
|
+
<li>
|
16
|
+
<%= link_to t('.reorder', :what => "Ballots"),
|
17
|
+
admin_ballots_path,
|
18
|
+
:id => "reorder_action",
|
19
|
+
:class => "reorder_icon" %>
|
20
|
+
|
21
|
+
<%= link_to t('.reorder_done', :what => "Ballots"),
|
22
|
+
admin_ballots_path,
|
23
|
+
:id => "reorder_action_done",
|
24
|
+
:style => "display: none;",
|
25
|
+
:class => "reorder_icon" %>
|
26
|
+
</li>
|
27
|
+
<% end %>
|
28
|
+
</ul>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<li class='clearfix record <%= cycle("on", "on-hover") %>' id="<%= dom_id(ballot) -%>">
|
2
|
+
<span class='title'>
|
3
|
+
<%= ballot.title %>
|
4
|
+
<span class="preview"> </span>
|
5
|
+
</span>
|
6
|
+
<span class='vote-link'>
|
7
|
+
(<%= link_to 'Link for voters', login_ballot_votes_url(ballot) %>)
|
8
|
+
</span>
|
9
|
+
<span class='actions'>
|
10
|
+
<%= link_to refinery_icon_tag("application_go.png"), admin_ballot_path(ballot),
|
11
|
+
:title => t('.view_live_html'),
|
12
|
+
:target => "_blank" %>
|
13
|
+
<%= link_to refinery_icon_tag("application_edit.png"), edit_admin_ballot_path(ballot),
|
14
|
+
:title => t('.edit') %>
|
15
|
+
<%= link_to refinery_icon_tag("delete.png"), admin_ballot_path(ballot),
|
16
|
+
:class => "cancel confirm-delete",
|
17
|
+
:title => t('.delete'),
|
18
|
+
:confirm => t('message', :scope => 'shared.admin.delete', :title => ballot.title),
|
19
|
+
:method => :delete %>
|
20
|
+
</span>
|
21
|
+
</li>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<% content_for :stylesheets, stylesheet_link_tag("ballots.css") -%>
|
2
|
+
<script src="/javascripts/refinerycms-ballots.js" type="text/javascript"></script>
|
3
|
+
|
4
|
+
<%= form_for [:admin, @ballot] do |f| -%>
|
5
|
+
<%= render :partial => "/shared/admin/error_messages", :locals => {
|
6
|
+
:object => @ballot,
|
7
|
+
:include_object_name => true
|
8
|
+
} %>
|
9
|
+
|
10
|
+
<div class='field'>
|
11
|
+
<%= f.label :title -%>
|
12
|
+
<%= f.text_field :title, :class => 'larger widest' -%>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<div class="field start_date">
|
16
|
+
<%= f.label :start_date %><br />
|
17
|
+
<%= f.datetime_select :start_date %>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div class="field end_date">
|
21
|
+
<%= f.label :end_date %><br />
|
22
|
+
<%= f.datetime_select :end_date %>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<% @ballot.offices.build %>
|
26
|
+
<%= f.fields_for :offices do |builder| %>
|
27
|
+
<%= render 'office_fields', :f => builder %>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<p><%= link_to_add_fields "Add Office", f, :offices %></p>
|
31
|
+
|
32
|
+
<%= render :partial => "/shared/admin/form_actions",
|
33
|
+
:locals => {
|
34
|
+
:f => f,
|
35
|
+
:continue_editing => false,
|
36
|
+
:delete_title => t('delete', :scope => 'admin.ballots.ballot'),
|
37
|
+
:delete_confirmation => t('message', :scope => 'shared.admin.delete', :title => @ballot.title)
|
38
|
+
} %>
|
39
|
+
<% end -%>
|
40
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<div class="office fields">
|
2
|
+
<%=f.label :content, "Office (#{link_to_remove_fields 'remove', f})".html_safe %>
|
3
|
+
|
4
|
+
<div class="field">
|
5
|
+
<%= f.label :title %>
|
6
|
+
<%= f.text_field :title, :size => 15 %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div class="field">
|
10
|
+
<%= f.label :number_of_positions %>
|
11
|
+
<%= f.text_field :number_of_positions, :size => 15 %>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<% f.object.candidates.build %>
|
15
|
+
|
16
|
+
<%= f.fields_for :candidates do |builder| %>
|
17
|
+
<%= render 'candidate_fields', :f => builder %>
|
18
|
+
<% end %>
|
19
|
+
|
20
|
+
<p><%= link_to_add_fields "Add candidate", f, :candidates %></p>
|
21
|
+
</div>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<% if searching? %>
|
2
|
+
<h2><%= t('results_for', :scope => 'shared.admin.search', :query => params[:search]) %></h2>
|
3
|
+
<% end %>
|
4
|
+
<% if @ballots.any? %>
|
5
|
+
<div class='pagination_container'>
|
6
|
+
<%= render :partial => 'ballots' %>
|
7
|
+
</div>
|
8
|
+
<% else %>
|
9
|
+
<p>
|
10
|
+
<% unless searching? %>
|
11
|
+
<strong>
|
12
|
+
<%= t('.no_items_yet') %>
|
13
|
+
</strong>
|
14
|
+
<% else %>
|
15
|
+
<%= t('no_results', :scope => 'shared.admin.search') %>
|
16
|
+
<% end %>
|
17
|
+
</p>
|
18
|
+
<% end %>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<ul id='sortable_list'>
|
2
|
+
<%= render :partial => 'ballot', :collection => @ballots %>
|
3
|
+
</ul>
|
4
|
+
<%= render :partial => "/shared/admin/sortable_list",
|
5
|
+
:locals => {
|
6
|
+
:continue_reordering => (local_assigns.keys.include?(:continue_reordering)) ? continue_reordering : true
|
7
|
+
} %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render :partial => "form" %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<section id='records'>
|
2
|
+
<%= render :partial => 'records' %>
|
3
|
+
</section>
|
4
|
+
<aside id='actions'>
|
5
|
+
<%= render :partial => 'actions' %>
|
6
|
+
</aside>
|
7
|
+
<%= render :partial => '/shared/admin/make_sortable',
|
8
|
+
:locals => {
|
9
|
+
:tree => false
|
10
|
+
} if !searching? and Admin::BallotsController.sortable? and Ballot.count > 1 %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render :partial => "form" %>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<% content_for :stylesheets, stylesheet_link_tag("ballots.css") -%>
|
2
|
+
|
3
|
+
<% content_for :body_content_title do %>
|
4
|
+
<%= @ballot.title %>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
|
8
|
+
<% content_for :body_content_left do %>
|
9
|
+
<section class="ballot left-column">
|
10
|
+
<% @ballot.offices.each do |office| %>
|
11
|
+
<h4><%= office.title %></h4>
|
12
|
+
<ol class="candidates">
|
13
|
+
<% office.candidates_by_rating.each do |candidate| %>
|
14
|
+
<li>
|
15
|
+
<%= candidate.name %>
|
16
|
+
<%= candidate.percentage %>% --
|
17
|
+
<%= pluralize candidate.number_of_votes, 'vote', 'votes' %>
|
18
|
+
</li>
|
19
|
+
<% end %>
|
20
|
+
</ol>
|
21
|
+
<% end %>
|
22
|
+
</section>
|
23
|
+
|
24
|
+
|
25
|
+
<section class="right-column">
|
26
|
+
<%= render 'show_voters', :voters => @ballot.voting_members, :title => 'Members who have voted' %>
|
27
|
+
<%= render 'show_voters', :voters => @ballot.nonvoting_members, :title => 'Members who have not voted' %>
|
28
|
+
</section>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
|
32
|
+
<%= render :partial => "/shared/content_page" %>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<h4><%= office_vote.office.title %></h4>
|
2
|
+
|
3
|
+
<% candidate_votes = office_vote.candidate_votes.select(&:voted) %>
|
4
|
+
|
5
|
+
|
6
|
+
<% if candidate_votes.size.zero? %>
|
7
|
+
<div class="errorExplanation warningExplanation">
|
8
|
+
<ul><li>You did not vote.</li></ul>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<ol class="candidates">
|
12
|
+
</ol>
|
13
|
+
<% else %>
|
14
|
+
|
15
|
+
<% if undervoted? office_vote %>
|
16
|
+
<div class="errorExplanation warningExplanation">
|
17
|
+
<ul>
|
18
|
+
<li><%= undervote_message_for office_vote %></li>
|
19
|
+
</ul>
|
20
|
+
</div>
|
21
|
+
<% end %>
|
22
|
+
|
23
|
+
<ol class="candidates">
|
24
|
+
<% candidate_votes.each do |candidate_vote| %>
|
25
|
+
<li><%= candidate_vote.name %></li>
|
26
|
+
<% end %>
|
27
|
+
</ol>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<% content_for :stylesheets, stylesheet_link_tag("ballots.css") -%>
|
2
|
+
<% content_for :body_content_title do %>
|
3
|
+
Confirm <%= @ballot.title %>
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
|
7
|
+
<% content_for :body_content_left do %>
|
8
|
+
|
9
|
+
<!-- the displayed results -->
|
10
|
+
<section class="ballot_vote">
|
11
|
+
<% @ballot_vote.office_votes.each do |office_vote| %>
|
12
|
+
<%= render 'confirmation_office_votes_display', :office_vote => office_vote %>
|
13
|
+
<% end %>
|
14
|
+
</section>
|
15
|
+
|
16
|
+
|
17
|
+
<!-- the full hidden data -->
|
18
|
+
<%= form_for @ballot_vote, :url => { :action => "proceed" } do |f| -%>
|
19
|
+
<%= text_field_tag :unique_identifier, @member.unique_identifier, :hidden => true %>
|
20
|
+
<%= f.fields_for :office_votes do |office_vote_form| %>
|
21
|
+
<%= office_vote_form.text_field :office_id, :hidden => true %>
|
22
|
+
<%= office_vote_form.fields_for :candidate_votes do |candidate_vote_form| %>
|
23
|
+
<%= candidate_vote_form.text_field :candidate_id, :hidden => true %>
|
24
|
+
<%= candidate_vote_form.check_box :voted, :checked => candidate_vote_form.object.voted, :hidden => true %>
|
25
|
+
<% end %>
|
26
|
+
<% end %>
|
27
|
+
<div class="field"><%= submit_tag 'Back' %>
|
28
|
+
<%= submit_tag 'Confirm' %></div>
|
29
|
+
<% end %>
|
30
|
+
<% end %>
|
31
|
+
|
32
|
+
|
33
|
+
<%= render :partial => "/shared/content_page" %>
|
34
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<% content_for :stylesheets, stylesheet_link_tag("ballots.css") -%>
|
2
|
+
<% content_for :body_content_title do %>
|
3
|
+
Success
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
<% content_for :body_content_left do %>
|
7
|
+
<section class="ballot">
|
8
|
+
<p>
|
9
|
+
Your vote has been recorded. <%= link_to 'Back to ballot', @ballot %>.
|
10
|
+
</p>
|
11
|
+
</section>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
|
15
|
+
<%= render :partial => "/shared/content_page" %>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<%= form_tag proceed_ballot_votes_path(@ballot), :method => 'post' do %>
|
2
|
+
|
3
|
+
<fieldset>
|
4
|
+
|
5
|
+
<div class="field">
|
6
|
+
<%= label_tag :first_name %>
|
7
|
+
<%= text_field_tag :first_name %><br />
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<div class="field">
|
11
|
+
<%= label_tag :last_name %>
|
12
|
+
<%= text_field_tag :last_name %><br />
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<div class="field">
|
16
|
+
<%= label_tag :unique_identifier %>
|
17
|
+
<%= text_field_tag :unique_identifier %><br />
|
18
|
+
</div>
|
19
|
+
|
20
|
+
</fieldset>
|
21
|
+
|
22
|
+
|
23
|
+
<div class="field"><%= submit_tag "Proceed to ballot" %></div>
|
24
|
+
<% end %>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<% content_for :stylesheets, stylesheet_link_tag("ballots.css") -%>
|
2
|
+
<% content_for :body_content_title do %>
|
3
|
+
<%= @ballot.title %>
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
<% content_for :body_content_left do %>
|
7
|
+
<section class="ballot">
|
8
|
+
<p><%= @ballot.pretty_start_date %> through <%= @ballot.pretty_end_date %></p>
|
9
|
+
|
10
|
+
<%= form_for @ballot_vote, :url => { :action => "proceed" } do |f| -%>
|
11
|
+
<%= show_errors_for_ballot_vote @ballot_vote %>
|
12
|
+
|
13
|
+
<%= text_field_tag :unique_identifier, @member.unique_identifier, :hidden => true %>
|
14
|
+
<%= f.fields_for :office_votes do |office_vote_form| %>
|
15
|
+
<% office_vote = office_vote_form.object %>
|
16
|
+
|
17
|
+
<fieldset>
|
18
|
+
<h4><%= office_vote.title %></h4>
|
19
|
+
<%= show_errors_for_office_vote office_vote %>
|
20
|
+
<%= office_vote_form.text_field :office_id, :hidden => true %>
|
21
|
+
<p><%= position_message_for office_vote.max_num_votes %></p>
|
22
|
+
<ol class="candidates">
|
23
|
+
<%= office_vote_form.fields_for :candidate_votes do |candidate_vote_form| %>
|
24
|
+
<li class="field">
|
25
|
+
<%= candidate_vote_form.text_field :candidate_id, :hidden => true %>
|
26
|
+
<%= candidate_vote_form.check_box :voted, :checked => candidate_vote_form.object.voted %>
|
27
|
+
<%= candidate_vote_form.label :voted, candidate_vote_form.object.name %><br />
|
28
|
+
</li>
|
29
|
+
<% end %>
|
30
|
+
</ol>
|
31
|
+
</fieldset>
|
32
|
+
|
33
|
+
<% end %>
|
34
|
+
|
35
|
+
<div class="field"><%= submit_tag 'Vote' %></div>
|
36
|
+
<% end %>
|
37
|
+
</section>
|
38
|
+
<% end %>
|
39
|
+
|
40
|
+
|
41
|
+
<%= render :partial => "/shared/content_page" %>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
en:
|
2
|
+
shared:
|
3
|
+
admin:
|
4
|
+
image_picker:
|
5
|
+
image: image
|
6
|
+
plugins:
|
7
|
+
ballots:
|
8
|
+
title: Ballots
|
9
|
+
admin:
|
10
|
+
ballots:
|
11
|
+
actions:
|
12
|
+
create_new: Add New Ballot
|
13
|
+
reorder: Reorder Ballots
|
14
|
+
reorder_done: Done Reordering Ballots
|
15
|
+
records:
|
16
|
+
title: Ballots
|
17
|
+
sorry_no_results: Sorry! There are no results found.
|
18
|
+
no_items_yet: There are no Ballots yet. Click "Add New Ballot" to add your first ballot.
|
19
|
+
ballot:
|
20
|
+
view_live_html: View results.
|
21
|
+
edit: Edit this ballot
|
22
|
+
delete: Remove this ballot forever
|
23
|
+
ballots:
|
24
|
+
show:
|
25
|
+
other: Other Ballots
|
@@ -0,0 +1,25 @@
|
|
1
|
+
fr:
|
2
|
+
shared:
|
3
|
+
admin:
|
4
|
+
image_picker:
|
5
|
+
image: image
|
6
|
+
plugins:
|
7
|
+
ballots:
|
8
|
+
title: Ballots
|
9
|
+
admin:
|
10
|
+
ballots:
|
11
|
+
actions:
|
12
|
+
create_new: Créer un(e) nouve(au/l/lle) Ballot
|
13
|
+
reorder: Réordonner les Ballots
|
14
|
+
reorder_done: Fin de réordonnancement des Ballots
|
15
|
+
records:
|
16
|
+
title: Ballots
|
17
|
+
sorry_no_results: "Désolé ! Aucun résultat."
|
18
|
+
no_items_yet: 'Il n''y a actuellement aucun(e) Ballot. Cliquer sur "Créer un(e) nouve(au/l/lle) Ballot" pour créer votre premi(er/ère) ballot.'
|
19
|
+
ballot:
|
20
|
+
view_live_html: Voir ce(t/tte) ballot <br/><em>(Ouvre une nouvelle fenêtre)</em>
|
21
|
+
edit: Modifier ce(t/tte) ballot
|
22
|
+
delete: Supprimer définitivement ce(t/tte) ballot
|
23
|
+
ballots:
|
24
|
+
show:
|
25
|
+
other: Autres Ballots
|
@@ -0,0 +1,25 @@
|
|
1
|
+
lolcat:
|
2
|
+
shared:
|
3
|
+
admin:
|
4
|
+
image_picker:
|
5
|
+
image: IMAGE
|
6
|
+
plugins:
|
7
|
+
ballots:
|
8
|
+
title: Ballots
|
9
|
+
admin:
|
10
|
+
ballots:
|
11
|
+
actions:
|
12
|
+
create_new: CREATE NEW Ballot
|
13
|
+
reorder: REORDR Ballots
|
14
|
+
reorder_done: DUN REORDERIN Ballots
|
15
|
+
records:
|
16
|
+
title: Ballots
|
17
|
+
sorry_no_results: SRY! THAR R NO RESULTS FINDZ.
|
18
|
+
no_items_yet: THAR R NO Ballots YET. CLICK "CREATE NEW Ballot" 2 ADD UR FURST ballot.
|
19
|
+
ballot:
|
20
|
+
view_live_html: VIEW DIS ballot LIV <BR/><EM>(OPENS IN NEW WINDOW)</EM>
|
21
|
+
edit: EDIT DIS ballot
|
22
|
+
delete: REMOOV DIS ballot FOREVR
|
23
|
+
ballots:
|
24
|
+
show:
|
25
|
+
other: OTHR Ballots
|
@@ -0,0 +1,21 @@
|
|
1
|
+
nb:
|
2
|
+
plugins:
|
3
|
+
ballots:
|
4
|
+
title: Ballots
|
5
|
+
admin:
|
6
|
+
ballots:
|
7
|
+
actions:
|
8
|
+
create_new: Lag en ny Ballot
|
9
|
+
reorder: Endre rekkefølgen på Ballots
|
10
|
+
reorder_done: Ferdig å endre rekkefølgen Ballots
|
11
|
+
records:
|
12
|
+
title: Ballots
|
13
|
+
sorry_no_results: Beklager! Vi fant ikke noen resultater.
|
14
|
+
no_items_yet: Det er ingen Ballots enda. Klikk på "Lag en ny Ballot" for å legge til din første ballot.
|
15
|
+
ballot:
|
16
|
+
view_live_html: Vis hvordan denne ballot ser ut offentlig <br/><em>(åpner i et nytt vindu)</em>
|
17
|
+
edit: Rediger denne ballot
|
18
|
+
delete: Fjern denne ballot permanent
|
19
|
+
ballots:
|
20
|
+
show:
|
21
|
+
other: Andre Ballots
|
@@ -0,0 +1,21 @@
|
|
1
|
+
nl:
|
2
|
+
plugins:
|
3
|
+
ballots:
|
4
|
+
title: Ballots
|
5
|
+
admin:
|
6
|
+
ballots:
|
7
|
+
actions:
|
8
|
+
create_new: Maak een nieuwe Ballot
|
9
|
+
reorder: Wijzig de volgorde van de Ballots
|
10
|
+
reorder_done: Klaar met het wijzingen van de volgorde van de Ballots
|
11
|
+
records:
|
12
|
+
title: Ballots
|
13
|
+
sorry_no_results: Helaas! Er zijn geen resultaten gevonden.
|
14
|
+
no_items_yet: Er zijn nog geen Ballots. Druk op 'Maak een nieuwe Ballot' om de eerste aan te maken.
|
15
|
+
ballot:
|
16
|
+
view_live_html: Bekijk deze ballot op de website <br/><em>(opent een nieuw venster)</em>
|
17
|
+
edit: Bewerk deze ballot
|
18
|
+
delete: Verwijder deze ballot voor eeuwig
|
19
|
+
ballots:
|
20
|
+
show:
|
21
|
+
other: Andere Ballots
|
data/config/routes.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
::Refinery::Application.routes.draw do
|
2
|
+
|
3
|
+
resources :ballots, :only => :index do
|
4
|
+
resources :votes, :only => [] do
|
5
|
+
collection do
|
6
|
+
get :login
|
7
|
+
post :proceed
|
8
|
+
get :proceed, :action => :login
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
scope(:path => 'refinery', :as => 'admin', :module => 'admin') do
|
15
|
+
resources :ballots do
|
16
|
+
collection do
|
17
|
+
post :update_positions
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'refinerycms-base'
|
2
|
+
require 'member_extensions'
|
3
|
+
|
4
|
+
module Refinery
|
5
|
+
module Ballots
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :root
|
9
|
+
def root
|
10
|
+
@root ||= Pathname.new(File.expand_path('../../', __FILE__))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Engine < Rails::Engine
|
15
|
+
initializer "static assets" do |app|
|
16
|
+
app.middleware.insert_after ::ActionDispatch::Static, ::ActionDispatch::Static, "#{root}/public"
|
17
|
+
end
|
18
|
+
|
19
|
+
config.after_initialize do
|
20
|
+
Refinery::Plugin.register do |plugin|
|
21
|
+
plugin.name = "ballots"
|
22
|
+
plugin.pathname = root
|
23
|
+
plugin.activity = {
|
24
|
+
:class => Ballot
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: refinerycms-ballots
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Cheek
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: refinerycms-simple_members
|
16
|
+
requirement: &70326298798500 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.1.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70326298798500
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: factory_girl
|
27
|
+
requirement: &70326298812240 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.1.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70326298812240
|
36
|
+
description: Ruby on Rails Ballots engine for Refinery CMS
|
37
|
+
email:
|
38
|
+
executables: []
|
39
|
+
extensions: []
|
40
|
+
extra_rdoc_files: []
|
41
|
+
files:
|
42
|
+
- lib/generators/refinerycms_ballots_generator.rb
|
43
|
+
- lib/member_extensions.rb
|
44
|
+
- lib/refinerycms-ballots.rb
|
45
|
+
- lib/tasks/ballots.rake
|
46
|
+
- config/locales/en.yml
|
47
|
+
- config/locales/fr.yml
|
48
|
+
- config/locales/lolcat.yml
|
49
|
+
- config/locales/nb.yml
|
50
|
+
- config/locales/nl.yml
|
51
|
+
- config/routes.rb
|
52
|
+
- app/controllers/admin/ballots_controller.rb
|
53
|
+
- app/controllers/ballots_controller.rb
|
54
|
+
- app/controllers/votes_controller.rb
|
55
|
+
- app/helpers/admin/ballots_helper.rb
|
56
|
+
- app/helpers/votes_helper.rb
|
57
|
+
- app/models/ballot.rb
|
58
|
+
- app/models/ballot_vote.rb
|
59
|
+
- app/models/candidate.rb
|
60
|
+
- app/models/candidate_vote.rb
|
61
|
+
- app/models/office.rb
|
62
|
+
- app/models/office_vote.rb
|
63
|
+
- app/views/admin/ballots/_actions.html.erb
|
64
|
+
- app/views/admin/ballots/_ballot.html.erb
|
65
|
+
- app/views/admin/ballots/_ballots.html.erb
|
66
|
+
- app/views/admin/ballots/_candidate_fields.html.erb
|
67
|
+
- app/views/admin/ballots/_form.html.erb
|
68
|
+
- app/views/admin/ballots/_office_fields.html.erb
|
69
|
+
- app/views/admin/ballots/_records.html.erb
|
70
|
+
- app/views/admin/ballots/_show_voters.html.erb
|
71
|
+
- app/views/admin/ballots/_sortable_list.html.erb
|
72
|
+
- app/views/admin/ballots/edit.html.erb
|
73
|
+
- app/views/admin/ballots/index.html.erb
|
74
|
+
- app/views/admin/ballots/new.html.erb
|
75
|
+
- app/views/admin/ballots/show.html.erb
|
76
|
+
- app/views/votes/_confirmation_office_votes_display.html.erb
|
77
|
+
- app/views/votes/confirm.html.erb
|
78
|
+
- app/views/votes/create.html.erb
|
79
|
+
- app/views/votes/login.html.erb
|
80
|
+
- app/views/votes/new.html.erb
|
81
|
+
homepage:
|
82
|
+
licenses: []
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.8.10
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: Ballots engine for Refinery CMS
|
105
|
+
test_files: []
|