decidim-elections 0.22.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/decidim_elections_manifest.js +2 -0
  3. data/app/assets/javascripts/decidim/elections/vote.js.es6 +170 -0
  4. data/app/assets/stylesheets/decidim/elections/elections.scss +10 -0
  5. data/app/assets/stylesheets/decidim/elections/focus/_evote.scss +279 -0
  6. data/app/assets/stylesheets/decidim/elections/focus/_focus.scss +128 -0
  7. data/app/cells/decidim/elections/election_cell.rb +22 -0
  8. data/app/cells/decidim/elections/election_m/data.erb +19 -0
  9. data/app/cells/decidim/elections/election_m/footer.erb +5 -0
  10. data/app/cells/decidim/elections/election_m_cell.rb +103 -0
  11. data/app/cells/decidim/elections/voting_step_navigation/show.erb +29 -0
  12. data/app/cells/decidim/elections/voting_step_navigation_cell.rb +42 -0
  13. data/app/commands/decidim/elections/admin/create_election.rb +14 -3
  14. data/app/commands/decidim/elections/admin/create_question.rb +2 -1
  15. data/app/commands/decidim/elections/admin/destroy_election.rb +4 -0
  16. data/app/commands/decidim/elections/admin/import_proposals_to_elections.rb +87 -0
  17. data/app/commands/decidim/elections/admin/publish_election.rb +54 -0
  18. data/app/commands/decidim/elections/admin/unpublish_election.rb +43 -0
  19. data/app/commands/decidim/elections/admin/update_election.rb +15 -3
  20. data/app/commands/decidim/elections/admin/update_question.rb +2 -1
  21. data/app/controllers/concerns/decidim/elections/orderable.rb +36 -0
  22. data/app/controllers/decidim/elections/admin/answers_controller.rb +9 -1
  23. data/app/controllers/decidim/elections/admin/elections_controller.rb +26 -0
  24. data/app/controllers/decidim/elections/admin/proposals_imports_controller.rb +53 -0
  25. data/app/controllers/decidim/elections/elections_controller.rb +68 -0
  26. data/app/controllers/decidim/elections/votes_controller.rb +50 -0
  27. data/app/events/decidim/elections/election_published_event.rb +8 -0
  28. data/app/forms/decidim/elections/admin/answer_import_proposals_form.rb +41 -0
  29. data/app/forms/decidim/elections/admin/election_form.rb +15 -2
  30. data/app/forms/decidim/elections/admin/question_form.rb +1 -0
  31. data/app/helpers/decidim/elections/application_helper.rb +12 -0
  32. data/app/helpers/decidim/elections/election_cells_helper.rb +12 -0
  33. data/app/helpers/decidim/elections/votes_helper.rb +21 -0
  34. data/app/models/decidim/elections/answer.rb +5 -1
  35. data/app/models/decidim/elections/election.rb +69 -0
  36. data/app/models/decidim/elections/question.rb +9 -2
  37. data/app/permissions/decidim/elections/admin/permissions.rb +13 -3
  38. data/app/permissions/decidim/elections/permissions.rb +35 -0
  39. data/app/presenters/decidim/elections/admin_log/election_presenter.rb +41 -0
  40. data/app/presenters/decidim/elections/election_presenter.rb +28 -0
  41. data/app/queries/decidim/elections/filtered_elections.rb +37 -0
  42. data/app/services/decidim/elections/election_search.rb +35 -0
  43. data/app/types/decidim/elections/election_question_type.rb +2 -2
  44. data/app/types/decidim/elections/election_type.rb +2 -1
  45. data/app/types/decidim/elections/elections_type.rb +1 -1
  46. data/app/views/decidim/elections/admin/answers/index.html.erb +2 -1
  47. data/app/views/decidim/elections/admin/elections/_form.html.erb +2 -4
  48. data/app/views/decidim/elections/admin/elections/index.html.erb +23 -1
  49. data/app/views/decidim/elections/admin/proposals_imports/new.html.erb +24 -0
  50. data/app/views/decidim/elections/admin/questions/_form.html.erb +4 -0
  51. data/app/views/decidim/elections/admin/questions/index.html.erb +3 -1
  52. data/app/views/decidim/elections/elections/_count.html.erb +1 -0
  53. data/app/views/decidim/elections/elections/_election.html.erb +1 -0
  54. data/app/views/decidim/elections/elections/_elections.html.erb +24 -0
  55. data/app/views/decidim/elections/elections/_filters.html.erb +24 -0
  56. data/app/views/decidim/elections/elections/_filters_small_view.html.erb +18 -0
  57. data/app/views/decidim/elections/elections/index.html.erb +24 -0
  58. data/app/views/decidim/elections/elections/index.js.erb +10 -0
  59. data/app/views/decidim/elections/elections/show.html.erb +62 -0
  60. data/app/views/decidim/elections/votes/_election_votes_confirm.html.erb +53 -0
  61. data/app/views/decidim/elections/votes/_election_votes_confirm_footer.html.erb +24 -0
  62. data/app/views/decidim/elections/votes/_election_votes_confirmed.html.erb +26 -0
  63. data/app/views/decidim/elections/votes/_election_votes_encrypting.html.erb +20 -0
  64. data/app/views/decidim/elections/votes/_election_votes_header.html.erb +8 -0
  65. data/app/views/decidim/elections/votes/_election_votes_modal.html.erb +46 -0
  66. data/app/views/decidim/elections/votes/_election_votes_question.html.erb +49 -0
  67. data/app/views/decidim/elections/votes/_election_votes_steps_header.html.erb +12 -0
  68. data/app/views/decidim/elections/votes/new.html.erb +44 -0
  69. data/app/views/layouts/decidim/_election_votes_header.html.erb +13 -0
  70. data/app/views/layouts/decidim/election_votes.html.erb +24 -0
  71. data/config/locales/am-ET.yml +1 -0
  72. data/config/locales/bg.yml +7 -0
  73. data/config/locales/ca.yml +136 -6
  74. data/config/locales/cs.yml +133 -1
  75. data/config/locales/da.yml +1 -0
  76. data/config/locales/de.yml +93 -1
  77. data/config/locales/el.yml +11 -1
  78. data/config/locales/en.yml +131 -1
  79. data/config/locales/eo.yml +1 -0
  80. data/config/locales/es-MX.yml +131 -1
  81. data/config/locales/es-PY.yml +131 -1
  82. data/config/locales/es.yml +134 -4
  83. data/config/locales/et.yml +1 -0
  84. data/config/locales/fi-plain.yml +131 -1
  85. data/config/locales/fi.yml +131 -1
  86. data/config/locales/fr-CA.yml +131 -1
  87. data/config/locales/fr.yml +131 -1
  88. data/config/locales/hr.yml +1 -0
  89. data/config/locales/hu.yml +18 -0
  90. data/config/locales/is.yml +1 -0
  91. data/config/locales/it.yml +88 -1
  92. data/config/locales/ja-JP.yml +95 -8
  93. data/config/locales/ja.yml +254 -0
  94. data/config/locales/ko-KR.yml +1 -0
  95. data/config/locales/ko.yml +1 -0
  96. data/config/locales/lt.yml +1 -0
  97. data/config/locales/{lv-LV.yml → lv.yml} +11 -1
  98. data/config/locales/mt.yml +1 -0
  99. data/config/locales/nl.yml +88 -1
  100. data/config/locales/no.yml +64 -0
  101. data/config/locales/om-ET.yml +1 -0
  102. data/config/locales/pl.yml +124 -1
  103. data/config/locales/pt.yml +84 -1
  104. data/config/locales/ro-RO.yml +85 -1
  105. data/config/locales/so-SO.yml +1 -0
  106. data/config/locales/sv.yml +121 -1
  107. data/config/locales/ti-ER.yml +1 -0
  108. data/config/locales/vi-VN.yml +1 -0
  109. data/config/locales/vi.yml +1 -0
  110. data/config/locales/zh-CN.yml +254 -0
  111. data/config/locales/zh-TW.yml +1 -0
  112. data/db/migrate/20200601141412_add_published_at_to_elections.rb +7 -0
  113. data/db/migrate/20200807125040_remove_subtitle_from_decidim_elections.rb +7 -0
  114. data/db/migrate/20200910103648_add_min_selections_to_decidim_elections_questions.rb +7 -0
  115. data/lib/decidim/elections.rb +5 -0
  116. data/lib/decidim/elections/admin_engine.rb +7 -0
  117. data/lib/decidim/elections/bulletin_board_client.rb +35 -0
  118. data/lib/decidim/elections/component.rb +17 -12
  119. data/lib/decidim/elections/engine.rb +8 -1
  120. data/lib/decidim/elections/test/factories.rb +59 -6
  121. data/lib/decidim/elections/version.rb +1 -1
  122. data/lib/tasks/decidim_elections.rake +16 -0
  123. metadata +79 -13
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This command gets called when a election is published from the admin panel.
7
+ class PublishElection < Rectify::Command
8
+ # Public: Initializes the command.
9
+ #
10
+ # election - The election to publish.
11
+ # current_user - the user performing the action
12
+ def initialize(election, current_user)
13
+ @election = election
14
+ @current_user = current_user
15
+ end
16
+
17
+ # Public: Publishes the Election.
18
+ #
19
+ # Broadcasts :ok if published, :invalid otherwise.
20
+ def call
21
+ publish_election
22
+ publish_event
23
+
24
+ broadcast(:ok, election)
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :election, :current_user
30
+
31
+ def publish_election
32
+ Decidim.traceability.perform_action!(
33
+ :publish,
34
+ election,
35
+ current_user,
36
+ visibility: "all"
37
+ ) do
38
+ election.publish!
39
+ election
40
+ end
41
+ end
42
+
43
+ def publish_event
44
+ Decidim::EventsManager.publish(
45
+ event: "decidim.events.elections.election_published",
46
+ event_class: ::Decidim::Elections::ElectionPublishedEvent,
47
+ resource: election,
48
+ followers: election.participatory_space.followers
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This command gets called when a election is unpublished from the admin panel.
7
+ class UnpublishElection < Rectify::Command
8
+ # Public: Initializes the command.
9
+ #
10
+ # election - The election to unpublish.
11
+ # current_user - the user performing the action
12
+ def initialize(election, current_user)
13
+ @election = election
14
+ @current_user = current_user
15
+ end
16
+
17
+ # Public: Unpublishes the Election.
18
+ #
19
+ # Broadcasts :ok if unpublished, :invalid otherwise.
20
+ def call
21
+ unpublish_election
22
+
23
+ broadcast(:ok)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :election, :current_user
29
+
30
+ def unpublish_election
31
+ Decidim.traceability.perform_action!(
32
+ :unpublish,
33
+ election,
34
+ current_user
35
+ ) do
36
+ election.unpublish!
37
+ election
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -6,9 +6,13 @@ module Decidim
6
6
  # This command is executed when the user updates an Election
7
7
  # from the admin panel.
8
8
  class UpdateElection < Rectify::Command
9
+ include ::Decidim::AttachmentMethods
10
+ include ::Decidim::GalleryMethods
11
+
9
12
  def initialize(form, election)
10
13
  @form = form
11
14
  @election = election
15
+ @attached_to = election
12
16
  end
13
17
 
14
18
  # Updates the election if valid.
@@ -17,19 +21,27 @@ module Decidim
17
21
  def call
18
22
  return broadcast(:invalid) if form.invalid?
19
23
 
20
- update_election!
24
+ if process_gallery?
25
+ build_gallery
26
+ return broadcast(:invalid) if gallery_invalid?
27
+ end
28
+
29
+ transaction do
30
+ update_election!
31
+ create_gallery if process_gallery?
32
+ photo_cleanup!
33
+ end
21
34
 
22
35
  broadcast(:ok, election)
23
36
  end
24
37
 
25
38
  private
26
39
 
27
- attr_reader :form, :election
40
+ attr_reader :form, :election, :gallery
28
41
 
29
42
  def update_election!
30
43
  attributes = {
31
44
  title: form.title,
32
- subtitle: form.subtitle,
33
45
  description: form.description,
34
46
  start_time: form.start_time,
35
47
  end_time: form.end_time
@@ -36,7 +36,8 @@ module Decidim
36
36
  description: form.description,
37
37
  max_selections: form.max_selections,
38
38
  weight: form.weight,
39
- random_answers_order: form.random_answers_order
39
+ random_answers_order: form.random_answers_order,
40
+ min_selections: form.min_selections
40
41
  }
41
42
 
42
43
  Decidim.traceability.update!(
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module Elections
7
+ # Common logic to sorting resources
8
+ module Orderable
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Decidim::Orderable
13
+
14
+ private
15
+
16
+ # Available orders based on enabled settings
17
+ def available_orders
18
+ @available_orders ||= %w(recent older)
19
+ end
20
+
21
+ def default_order
22
+ "recent"
23
+ end
24
+
25
+ def reorder(elections)
26
+ case order
27
+ when "recent"
28
+ elections.order(start_time: :asc)
29
+ else
30
+ elections.order(start_time: :desc)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -7,7 +7,11 @@ module Decidim
7
7
  class AnswersController < Admin::ApplicationController
8
8
  include Decidim::Proposals::Admin::Picker
9
9
  helper Decidim::ApplicationHelper
10
- helper_method :election, :question, :answers, :answers
10
+ helper_method :election, :question, :answers, :answers, :missing_answers
11
+
12
+ def index
13
+ flash.now[:alert] = I18n.t("answers.index.invalid_max_selections", scope: "decidim.elections.admin", missing_answers: missing_answers) if missing_answers.positive?
14
+ end
11
15
 
12
16
  def new
13
17
  enforce_permission_to :update, :answer, election: election, question: question
@@ -86,6 +90,10 @@ module Decidim
86
90
  def answer
87
91
  answers.find(params[:id])
88
92
  end
93
+
94
+ def missing_answers
95
+ question.max_selections - answers.count
96
+ end
89
97
  end
90
98
  end
91
99
  end
@@ -7,6 +7,10 @@ module Decidim
7
7
  class ElectionsController < Admin::ApplicationController
8
8
  helper_method :elections, :election
9
9
 
10
+ def index
11
+ flash.now[:alert] ||= I18n.t("elections.index.no_bulletin_board", scope: "decidim.elections.admin").html_safe unless Decidim::Elections.bulletin_board.configured?
12
+ end
13
+
10
14
  def new
11
15
  enforce_permission_to :create, :election
12
16
  @form = form(ElectionForm).instance
@@ -67,6 +71,28 @@ module Decidim
67
71
  redirect_to elections_path
68
72
  end
69
73
 
74
+ def publish
75
+ enforce_permission_to :publish, :election, election: election
76
+
77
+ PublishElection.call(election, current_user) do
78
+ on(:ok) do
79
+ flash[:notice] = I18n.t("admin.elections.publish.success", scope: "decidim.elections")
80
+ redirect_to elections_path
81
+ end
82
+ end
83
+ end
84
+
85
+ def unpublish
86
+ enforce_permission_to :unpublish, :election, election: election
87
+
88
+ UnpublishElection.call(election, current_user) do
89
+ on(:ok) do
90
+ flash[:notice] = I18n.t("admin.elections.unpublish.success", scope: "decidim.elections")
91
+ redirect_to elections_path
92
+ end
93
+ end
94
+ end
95
+
70
96
  private
71
97
 
72
98
  def elections
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This controller allows to import proposals as answers.
7
+ class ProposalsImportsController < Admin::ApplicationController
8
+ helper_method :election, :question, :answers, :answers
9
+
10
+ def new
11
+ enforce_permission_to :import_proposals, :answer, election: election, question: question
12
+ @form = form(Admin::AnswerImportProposalsForm).instance
13
+ end
14
+
15
+ def create
16
+ enforce_permission_to :import_proposals, :answer, election: election, question: question
17
+
18
+ @form = form(Admin::AnswerImportProposalsForm).from_params(params, election: election, question: question)
19
+
20
+ Admin::ImportProposalsToElections.call(@form) do
21
+ on(:ok) do |answers|
22
+ flash[:notice] = I18n.t("proposals_imports.create.success", scope: "decidim.elections.admin", number: answers.length)
23
+ redirect_to election_question_answers_path(election, question)
24
+ end
25
+
26
+ on(:invalid) do
27
+ flash[:alert] = I18n.t("proposals_imports.create.invalid", scope: "decidim.elections.admin")
28
+ render action: "new"
29
+ end
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def election
36
+ @election ||= Election.where(component: current_component).find_by(id: params[:election_id])
37
+ end
38
+
39
+ def question
40
+ @question ||= election.questions.find_by(id: params[:question_id])
41
+ end
42
+
43
+ def answers
44
+ @answers ||= question.answers
45
+ end
46
+
47
+ def answer
48
+ answers.find(params[:id])
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -4,6 +4,74 @@ module Decidim
4
4
  module Elections
5
5
  # Exposes the elections resources so users can participate on them
6
6
  class ElectionsController < Decidim::Elections::ApplicationController
7
+ include FilterResource
8
+ include Paginable
9
+ include Decidim::Elections::Orderable
10
+
11
+ helper_method :elections, :election, :paginated_elections, :scheduled_elections, :single?
12
+
13
+ def index
14
+ redirect_to election_path(single, single: true) if single?
15
+ end
16
+
17
+ def show
18
+ enforce_permission_to :view, :election, election: election
19
+ end
20
+
21
+ private
22
+
23
+ def elections
24
+ @elections ||= Election.where(component: current_component).published
25
+ end
26
+
27
+ def election
28
+ @election ||= Election.where(component: current_component).find(params[:id])
29
+ end
30
+
31
+ # Public: Checks if the component has only one election resource.
32
+ #
33
+ # Returns Boolean.
34
+ def single?
35
+ elections.one?
36
+ end
37
+
38
+ def single
39
+ elections.first if single?
40
+ end
41
+
42
+ def paginated_elections
43
+ @paginated_elections ||= paginate(search.results.published)
44
+ @paginated_elections = reorder(@paginated_elections)
45
+ end
46
+
47
+ def scheduled_elections
48
+ @scheduled_elections ||= search_klass.new(search_params.merge(state: %w(active upcoming))).results
49
+ end
50
+
51
+ def search_klass
52
+ ElectionSearch
53
+ end
54
+
55
+ def default_filter_params
56
+ {
57
+ search_text: "",
58
+ state: default_filter_state_params
59
+ }
60
+ end
61
+
62
+ def default_filter_state_params
63
+ if elections.active.any?
64
+ %w(active)
65
+ elsif elections.upcoming.any?
66
+ %w(upcoming)
67
+ else
68
+ %w()
69
+ end
70
+ end
71
+
72
+ def context_params
73
+ { component: current_component, organization: current_organization }
74
+ end
7
75
  end
8
76
  end
9
77
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ # Exposes the elections resources so users can participate on them
6
+ class VotesController < Decidim::Elections::ApplicationController
7
+ layout "decidim/election_votes"
8
+ include FormFactory
9
+
10
+ helper VotesHelper
11
+ helper_method :elections, :election, :questions, :questions_count, :booth_mode
12
+
13
+ delegate :count, to: :questions, prefix: true
14
+
15
+ def new
16
+ redirect_to(return_path, alert: t("votes.messages.not_allowed", scope: "decidim.elections")) unless booth_mode
17
+ end
18
+
19
+ private
20
+
21
+ def booth_mode
22
+ @booth_mode ||= if allowed_to? :vote, :election, election: election
23
+ :vote
24
+ elsif allowed_to? :preview, :election, election: election
25
+ :preview
26
+ end
27
+ end
28
+
29
+ def return_path
30
+ @return_path ||= if allowed_to? :view, :election, election: election
31
+ election_path(election)
32
+ else
33
+ elections_path
34
+ end
35
+ end
36
+
37
+ def elections
38
+ @elections ||= Election.where(component: current_component)
39
+ end
40
+
41
+ def election
42
+ @election ||= elections.find(params[:election_id])
43
+ end
44
+
45
+ def questions
46
+ @questions ||= election.questions.includes(:answers).order(weight: :asc, id: :asc)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,8 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ class ElectionPublishedEvent < Decidim::Events::SimpleEvent
6
+ end
7
+ end
8
+ end