refinerycms-ballots 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|