decidim-elections 0.31.5 → 0.32.0.rc2

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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1
  3. data/app/commands/decidim/elections/admin/process_census.rb +1 -1
  4. data/app/commands/decidim/elections/admin/update_election.rb +1 -0
  5. data/app/commands/decidim/elections/admin/update_election_status.rb +1 -1
  6. data/app/commands/decidim/elections/admin/update_questions.rb +1 -0
  7. data/app/commands/decidim/elections/cast_votes.rb +3 -3
  8. data/app/controllers/concerns/decidim/elections/uses_census_access.rb +39 -0
  9. data/app/controllers/concerns/decidim/elections/uses_votes_booth.rb +4 -26
  10. data/app/controllers/decidim/elections/admin/census_controller.rb +5 -5
  11. data/app/controllers/decidim/elections/admin/elections_controller.rb +20 -9
  12. data/app/controllers/decidim/elections/admin/questions_controller.rb +1 -1
  13. data/app/controllers/decidim/elections/census_checks_controller.rb +58 -0
  14. data/app/controllers/decidim/elections/elections_controller.rb +11 -1
  15. data/app/controllers/decidim/elections/votes_controller.rb +8 -0
  16. data/app/forms/decidim/elections/admin/question_form.rb +6 -0
  17. data/app/forms/decidim/elections/censuses/internal_users_form.rb +1 -1
  18. data/app/helpers/decidim/elections/application_helper.rb +8 -3
  19. data/app/models/decidim/elections/question.rb +1 -0
  20. data/app/models/decidim/elections/vote.rb +2 -1
  21. data/app/models/decidim/elections/voter.rb +1 -0
  22. data/app/packs/entrypoints/decidim_elections.js +2 -0
  23. data/app/packs/entrypoints/decidim_elections_admin.js +1 -0
  24. data/app/packs/src/decidim/elections/admin/census_check_toggle.js +44 -0
  25. data/app/packs/src/decidim/elections/census_check_visibility.js +52 -0
  26. data/app/packs/src/decidim/elections/elections.js +32 -0
  27. data/app/permissions/decidim/elections/permissions.rb +13 -0
  28. data/app/presenters/decidim/elections/election_presenter.rb +3 -1
  29. data/app/views/decidim/elections/admin/census/edit.html.erb +2 -1
  30. data/app/views/decidim/elections/admin/censuses/_internal_users_form.html.erb +1 -1
  31. data/app/views/decidim/elections/admin/dashboard/_status.html.erb +12 -0
  32. data/app/views/decidim/elections/admin/elections/_election-tr.html.erb +3 -3
  33. data/app/views/decidim/elections/admin/questions/_question.html.erb +11 -0
  34. data/app/views/decidim/elections/census_checks/show.html.erb +8 -0
  35. data/app/views/decidim/elections/censuses/_internal_users_form.html.erb +2 -2
  36. data/app/views/decidim/elections/censuses/_token_csv_form.html.erb +1 -1
  37. data/app/views/decidim/elections/elections/_election_aside.html.erb +8 -0
  38. data/app/views/decidim/elections/elections/index.html.erb +1 -1
  39. data/app/views/decidim/elections/elections/show.html.erb +1 -1
  40. data/app/views/decidim/elections/votes/show.html.erb +6 -2
  41. data/config/locales/ca-IT.yml +16 -0
  42. data/config/locales/ca.yml +16 -0
  43. data/config/locales/cs.yml +7 -0
  44. data/config/locales/en.yml +16 -0
  45. data/config/locales/es-MX.yml +16 -0
  46. data/config/locales/es-PY.yml +16 -0
  47. data/config/locales/es.yml +16 -0
  48. data/config/locales/eu.yml +16 -0
  49. data/config/locales/fi-plain.yml +16 -0
  50. data/config/locales/fi.yml +16 -0
  51. data/config/locales/fr-CA.yml +16 -0
  52. data/config/locales/fr.yml +16 -0
  53. data/config/locales/ja.yml +16 -0
  54. data/config/locales/pt-BR.yml +16 -0
  55. data/config/locales/sk.yml +16 -0
  56. data/config/locales/sv.yml +15 -0
  57. data/db/migrate/20251007113417_add_max_choices_to_decidim_elections_questions.rb +7 -0
  58. data/db/migrate/20251020130630_add_allow_census_check_before_start_to_decidim_elections_elections.rb +7 -0
  59. data/decidim-elections.gemspec +9 -10
  60. data/lib/decidim/elections/admin_engine.rb +1 -0
  61. data/lib/decidim/elections/component.rb +0 -4
  62. data/lib/decidim/elections/engine.rb +2 -1
  63. data/lib/decidim/elections/seeds.rb +1 -1
  64. data/lib/decidim/elections/test/factories.rb +7 -3
  65. data/lib/decidim/elections/test/vote_controller_examples.rb +6 -6
  66. data/lib/decidim/elections/test/vote_examples.rb +4 -4
  67. data/lib/decidim/elections/version.rb +1 -1
  68. metadata +24 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96c37dba7e69df06576460094925f300e2eaf7df879568b83d2bb9d67c5cf9d6
4
- data.tar.gz: 9202c1b60fef070ae9524a73b2eaad08c85c3b8fbc0eeee3abe2d62169cc5362
3
+ metadata.gz: 4ed28042a5ba0fce7e077a20442c88ba8aa8d8e3521fc4e2f9f6a8ccda083757
4
+ data.tar.gz: 297dcd5aa0f624246f8fc9c0e657b7cf6c734e299f3da9ec5495fcdaa29e2b87
5
5
  SHA512:
6
- metadata.gz: 1f81d1f6f8e9bbf8b5c21c4a9d5327bcb85fc8699f84688a7485334b2295acb22354f0521a14a12afbfd96be50bf2ea77bc2541567ccd5371f7075be079446c3
7
- data.tar.gz: 1b1207e84a3f74151fde3d2e33a07cfb4712527ada1bacf944e0dd5596ba3a1575a9fa568aca41a4062b3ffd7fc2ca14ddde3db130ce5f9f835db676bb369548
6
+ metadata.gz: 0f0cac1305197050665c4f751d57643818f76b99966e180993a78044fb367f75c71b2dab404fff2a5ae247465d1c0b6c1fdaa36fb553693e2ad675eb50d6ebb7
7
+ data.tar.gz: 3bb3edb2068ea51e0e24933e7de96637241d7b1a83b1766c3ed8ba6dd5801b6ee072df2acddbabca53cb965880a247e9cc2e8d42b5526d1c3394d5acfd942d0e
data/README.md CHANGED
@@ -1,7 +1,11 @@
1
- # Decidim::Elections
1
+ # decidim-elections
2
2
 
3
3
  The Decidim::Elections is a component that allows users to setup non-cryptographic elections. Elections are basically polls/surveys with census access management built-in. This allows registered or non-registered users to directly participate in them.
4
4
 
5
+ ## Usage
6
+
7
+ Elections will be available as a Component for a Participatory Space.
8
+
5
9
  ## Installation
6
10
 
7
11
  In order to install use this module, you need at least Decidim 0.31 to be installed.
@@ -10,6 +14,8 @@ To install this module, run in your console:
10
14
 
11
15
  ```bash
12
16
  bundle add decidim-elections
17
+ bundle exec rails decidim:upgrade
18
+ bundle exec rails db:migrate
13
19
  ```
14
20
 
15
21
  ## Contributing
@@ -9,7 +9,7 @@ module Decidim
9
9
  def attributes
10
10
  {
11
11
  census_manifest: resource.census.name,
12
- census_settings: census_settings
12
+ census_settings:
13
13
  }
14
14
  end
15
15
 
@@ -5,6 +5,7 @@ module Decidim
5
5
  module Admin
6
6
  class UpdateElection < Decidim::Commands::UpdateResource
7
7
  include ::Decidim::GalleryMethods
8
+
8
9
  fetch_form_attributes :title, :description, :start_at, :end_at, :results_availability
9
10
 
10
11
  def initialize(form, election)
@@ -5,7 +5,7 @@ module Decidim
5
5
  module Admin
6
6
  class UpdateElectionStatus < Decidim::Command
7
7
  def initialize(action, election)
8
- @action = action.to_sym
8
+ @action = action.try(:to_sym)
9
9
  @election = election
10
10
  end
11
11
 
@@ -47,6 +47,7 @@ module Decidim
47
47
  body: question_form.body,
48
48
  description: question_form.description,
49
49
  question_type: question_form.question_type,
50
+ max_choices: question_form.max_choices,
50
51
  position: index
51
52
  )
52
53
 
@@ -40,11 +40,11 @@ module Decidim
40
40
  voted_questions.each do |question, responses|
41
41
  raise StandardError, "No responses for question #{question.id}" if responses.blank?
42
42
 
43
- question.votes.where(voter_uid: voter_uid).destroy_all
43
+ question.votes.where(voter_uid:).destroy_all
44
44
  responses.each do |response_option|
45
45
  question.votes.create!(
46
- voter_uid: voter_uid,
47
- response_option: response_option
46
+ voter_uid:,
47
+ response_option:
48
48
  )
49
49
  end
50
50
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module UsesCensusAccess
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ helper_method :exit_path, :election, :session_authenticated?, :session_attributes, :voter_uid
10
+ end
11
+
12
+ private
13
+
14
+ def election
15
+ @election ||= Election.where(component: current_component).published.find(params[:election_id])
16
+ end
17
+
18
+ def session_authenticated?
19
+ @session_authenticated ||= election.census.valid_user?(election, session_attributes, current_user:)
20
+ end
21
+
22
+ def voter_uid
23
+ @voter_uid ||= election.census.voter_uid(election, session_attributes, current_user:)
24
+ end
25
+
26
+ def session_attributes
27
+ session[:session_attributes] ||= {}
28
+ end
29
+
30
+ def exit_path
31
+ @exit_path ||= if allowed_to?(:read, :election, election:)
32
+ election_path(election)
33
+ else
34
+ elections_path
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -7,8 +7,10 @@ module Decidim
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
+ include UsesCensusAccess
11
+
10
12
  layout "decidim/election_booth"
11
- helper_method :exit_path, :election, :questions, :question, :response_chosen?, :votes_buffer
13
+ helper_method :questions, :question, :response_chosen?, :votes_buffer
12
14
 
13
15
  before_action except: [:new, :create, :receipt] do
14
16
  next if session_authenticated?
@@ -64,10 +66,6 @@ module Decidim
64
66
 
65
67
  private
66
68
 
67
- def election
68
- @election ||= Election.where(component: current_component).published.find(params[:election_id])
69
- end
70
-
71
69
  def questions
72
70
  @questions ||= election.questions
73
71
  end
@@ -76,22 +74,10 @@ module Decidim
76
74
  @question ||= questions.find_by(id: params[:id]) || questions.first
77
75
  end
78
76
 
79
- def session_authenticated?
80
- @session_authenticated ||= election.census.valid_user?(election, session_attributes, current_user:)
81
- end
82
-
83
- def voter_uid
84
- @voter_uid ||= election.census.voter_uid(election, session_attributes, current_user:)
85
- end
86
-
87
77
  def votes_buffer
88
78
  session[:votes_buffer] ||= {}
89
79
  end
90
80
 
91
- def session_attributes
92
- session[:session_attributes] ||= {}
93
- end
94
-
95
81
  def response_chosen?(response_option)
96
82
  response_ids = if votes_buffer.has_key?(question.id.to_s)
97
83
  votes_buffer[question.id.to_s]
@@ -105,17 +91,9 @@ module Decidim
105
91
 
106
92
  def previous_responses
107
93
  @previous_responses ||= election.questions.to_h do |question|
108
- [question.id.to_s, question.votes.where(voter_uid: voter_uid).pluck(:response_option_id).map(&:to_s)]
94
+ [question.id.to_s, question.votes.where(voter_uid:).pluck(:response_option_id).map(&:to_s)]
109
95
  end
110
96
  end
111
-
112
- def exit_path
113
- @exit_path ||= if allowed_to?(:read, :election, election:)
114
- election_path(election)
115
- else
116
- elections_path
117
- end
118
- end
119
97
  end
120
98
  end
121
99
  end
@@ -11,15 +11,15 @@ module Decidim
11
11
  before_action :set_census_manifest, only: [:edit, :update]
12
12
 
13
13
  def edit
14
- enforce_permission_to :update, :census, election: election
14
+ enforce_permission_to(:update, :census, election:)
15
15
 
16
- @form = form(election.census.admin_form.constantize).from_params(election.census_settings, election: election) if election.census && election.census.admin_form.present?
16
+ @form = form(election.census.admin_form.constantize).from_params(election.census_settings, election:) if election.census && election.census.admin_form.present?
17
17
  end
18
18
 
19
19
  def update
20
- enforce_permission_to :update, :census, election: election
20
+ enforce_permission_to(:update, :census, election:)
21
21
 
22
- @form = form(election.census.admin_form.constantize).from_params(params, election: election) if election.census.admin_form.present?
22
+ @form = form(election.census.admin_form.constantize).from_params(params, election:) if election.census.admin_form.present?
23
23
 
24
24
  ProcessCensus.call(@form, election) do
25
25
  on(:ok) do
@@ -28,7 +28,7 @@ module Decidim
28
28
  end
29
29
  on(:invalid) do
30
30
  flash[:alert] = t("decidim.elections.admin.census.update.error")
31
- render :edit, status: :unprocessable_entity
31
+ render :edit, status: :unprocessable_content
32
32
  end
33
33
  end
34
34
  end
@@ -35,7 +35,7 @@ module Decidim
35
35
 
36
36
  on(:invalid) do
37
37
  flash.now[:alert] = I18n.t("elections.create.invalid", scope: "decidim.elections.admin")
38
- render action: "new", status: :unprocessable_entity
38
+ render action: "new", status: :unprocessable_content
39
39
  end
40
40
  end
41
41
  end
@@ -46,7 +46,7 @@ module Decidim
46
46
  end
47
47
 
48
48
  def update
49
- enforce_permission_to :update, :election, election: election
49
+ enforce_permission_to(:update, :election, election:)
50
50
 
51
51
  @form = form(Decidim::Elections::Admin::ElectionForm).from_params(params, current_component:, election:)
52
52
 
@@ -58,13 +58,13 @@ module Decidim
58
58
 
59
59
  on(:invalid) do
60
60
  flash.now[:alert] = I18n.t("elections.update.invalid", scope: "decidim.elections.admin")
61
- render action: "edit", status: :unprocessable_entity
61
+ render action: "edit", status: :unprocessable_content
62
62
  end
63
63
  end
64
64
  end
65
65
 
66
66
  def publish
67
- enforce_permission_to :publish, :election, election: election
67
+ enforce_permission_to(:publish, :election, election:)
68
68
 
69
69
  PublishElection.call(election, current_user) do
70
70
  on(:ok) do
@@ -74,13 +74,13 @@ module Decidim
74
74
 
75
75
  on(:invalid) do
76
76
  flash.now[:alert] = I18n.t("elections.publish.invalid", scope: "decidim.elections.admin")
77
- render action: "index", status: :unprocessable_entity
77
+ render action: "index", status: :unprocessable_content
78
78
  end
79
79
  end
80
80
  end
81
81
 
82
82
  def unpublish
83
- enforce_permission_to :unpublish, :election, election: election
83
+ enforce_permission_to(:unpublish, :election, election:)
84
84
 
85
85
  Decidim::Elections::Admin::UnpublishElection.call(election, current_user) do
86
86
  on(:ok) do
@@ -90,13 +90,13 @@ module Decidim
90
90
 
91
91
  on(:invalid) do
92
92
  flash.now[:alert] = I18n.t("elections.unpublish.invalid", scope: "decidim.elections.admin")
93
- render action: "index", status: :unprocessable_entity
93
+ render action: "index", status: :unprocessable_content
94
94
  end
95
95
  end
96
96
  end
97
97
 
98
98
  def dashboard
99
- enforce_permission_to :dashboard, :election, election: election
99
+ enforce_permission_to(:dashboard, :election, election:)
100
100
 
101
101
  respond_to do |format|
102
102
  format.html { render :dashboard }
@@ -107,7 +107,7 @@ module Decidim
107
107
  end
108
108
 
109
109
  def update_status
110
- enforce_permission_to :update, :election, election: election
110
+ enforce_permission_to(:update, :election, election:)
111
111
 
112
112
  status_action = params[:status_action]
113
113
  UpdateElectionStatus.call(status_action, election) do
@@ -122,6 +122,17 @@ module Decidim
122
122
  redirect_to dashboard_election_path(election)
123
123
  end
124
124
 
125
+ def toggle_census_check
126
+ enforce_permission_to(:update, :election, election:)
127
+
128
+ value = ActiveModel::Type::Boolean.new.cast(params[:allow_census_check_before_start])
129
+ election.update!(allow_census_check_before_start: value)
130
+
131
+ render json: { success: true, allow_census_check_before_start: election.allow_census_check_before_start }
132
+ rescue StandardError
133
+ render json: { success: false, error: I18n.t("elections.toggle_census_check.error", scope: "decidim.elections.admin") }, status: :unprocessable_content
134
+ end
135
+
125
136
  private
126
137
 
127
138
  def per_question_waiting?
@@ -29,7 +29,7 @@ module Decidim
29
29
 
30
30
  on(:invalid) do
31
31
  flash.now[:alert] = I18n.t("update.invalid", scope: "decidim.elections.admin.questions")
32
- render :edit_questions, status: :unprocessable_entity
32
+ render :edit_questions, status: :unprocessable_content
33
33
  end
34
34
  end
35
35
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ # Allows participants to verify they belong to an election census before voting starts.
6
+ class CensusChecksController < Decidim::Elections::ApplicationController
7
+ include UsesCensusAccess
8
+
9
+ layout "decidim/election_booth"
10
+
11
+ before_action :redirect_if_authenticated, only: :new
12
+ before_action :ensure_session_authenticated!, only: :show
13
+
14
+ def new
15
+ enforce_permission_to(:create, :census_check, election:)
16
+
17
+ @form = election.census.form_instance({}, election:, current_user:)
18
+ render "decidim/elections/votes/new"
19
+ end
20
+
21
+ def create
22
+ enforce_permission_to(:create, :census_check, election:)
23
+
24
+ @form = election.census.form_instance(params, election:, current_user:)
25
+ if @form.valid?
26
+ session[:session_attributes] = @form.attributes
27
+ redirect_to election_census_check_path(election)
28
+ else
29
+ flash[:alert] = @form.errors.full_messages.join("<br>").presence || t("failed", scope: "decidim.elections.votes.check_census")
30
+ redirect_to new_election_census_check_path(election)
31
+ end
32
+ end
33
+
34
+ def show
35
+ enforce_permission_to(:read, :census_check, election:)
36
+ end
37
+
38
+ private
39
+
40
+ # Allows admins to access unpublished elections for preview.
41
+ def election
42
+ @election ||= Election.where(component: current_component)
43
+ .then { |scope| current_user&.admin? ? scope : scope.published }
44
+ .find(params[:election_id])
45
+ end
46
+
47
+ def redirect_if_authenticated
48
+ redirect_to election_census_check_path(election) if session_authenticated?
49
+ end
50
+
51
+ def ensure_session_authenticated!
52
+ return if session_authenticated?
53
+
54
+ redirect_to new_election_census_check_path(election), alert: t("decidim.elections.votes.check_census.failed")
55
+ end
56
+ end
57
+ end
58
+ end
@@ -30,7 +30,7 @@ module Decidim
30
30
  end
31
31
 
32
32
  def election
33
- @election ||= current_user&.admin? ? available_elections.find(params[:id]) : elections.find(params[:id])
33
+ @election ||= current_user&.admin? ? available_elections.find_by(id: params[:id]) : elections.find_by(id: params[:id])
34
34
  end
35
35
 
36
36
  def questions
@@ -59,6 +59,16 @@ module Decidim
59
59
  with_any_state: "all"
60
60
  }
61
61
  end
62
+
63
+ def add_breadcrumb_item
64
+ return {} if election.blank?
65
+
66
+ {
67
+ label: translated_attribute(election.title),
68
+ url: Decidim::EngineRouter.main_proxy(current_component).election_path(election),
69
+ active: false
70
+ }
71
+ end
62
72
  end
63
73
  end
64
74
  end
@@ -20,6 +20,14 @@ module Decidim
20
20
  def update
21
21
  enforce_permission_to(:create, :vote, election:)
22
22
 
23
+ response_ids = Array(params.dig(:response, question.id.to_s)).compact
24
+
25
+ if question.max_choices.present? && response_ids.size > question.max_choices
26
+ flash.now[:alert] = t("votes.question.max_choices_exceeded", scope: "decidim.elections", max: question.max_choices)
27
+ render :show
28
+ return
29
+ end
30
+
23
31
  votes_buffer[question.id.to_s] = params.dig(:response, question.id.to_s)
24
32
  redirect_to next_vote_step_path
25
33
  end
@@ -10,6 +10,7 @@ module Decidim
10
10
 
11
11
  attribute :question_type, String, default: "multiple_option"
12
12
  attribute :response_options, Array[Decidim::Elections::Admin::ResponseOptionForm]
13
+ attribute :max_choices, Integer
13
14
  attribute :deleted, Boolean, default: false
14
15
 
15
16
  translatable_attribute :body, String
@@ -18,6 +19,7 @@ module Decidim
18
19
  validates :body, translatable_presence: true
19
20
  validates :question_type, inclusion: { in: Decidim::Elections::Question.question_types }, if: :editable?
20
21
  validates :response_options, presence: true, if: :editable?
22
+ validates :max_choices, numericality: { only_integer: true, greater_than: 1, less_than_or_equal_to: ->(form) { form.number_of_options } }, allow_blank: true
21
23
 
22
24
  def election
23
25
  @election ||= context[:election]
@@ -32,6 +34,10 @@ module Decidim
32
34
  def editable?
33
35
  @editable ||= id.blank? || Decidim::Elections::Question.exists?(id:)
34
36
  end
37
+
38
+ def number_of_options
39
+ response_options.size
40
+ end
35
41
  end
36
42
  end
37
43
  end
@@ -29,7 +29,7 @@ module Decidim
29
29
  [
30
30
  adapter,
31
31
  Decidim::Verifications::Authorizations.new(
32
- organization: organization,
32
+ organization:,
33
33
  user: current_user,
34
34
  name: adapter.name
35
35
  ).first
@@ -21,6 +21,7 @@ module Decidim
21
21
  @filter_sections ||= begin
22
22
  items = [{
23
23
  method: :with_any_state,
24
+ name: "[with_any_state]",
24
25
  collection: filter_elections_state_values,
25
26
  label: t("decidim.elections.elections.filters.state"),
26
27
  id: "date",
@@ -37,9 +38,13 @@ module Decidim
37
38
  (defined?(current_component) && translated_attribute(current_component&.name).presence) || t("decidim.components.elections.name")
38
39
  end
39
40
 
40
- def question_title(question, tag = :h3, **options)
41
- content_tag(tag, **options) do
42
- translated_attribute(question.body)
41
+ def question_title(question, tag = :h3, **)
42
+ content_tag(tag, **) do
43
+ title = translated_attribute(question.body)
44
+ if question.max_choices.present? && question.question_type == "multiple_option"
45
+ title += " (#{t("decidim.elections.votes.question.max_choices", count: question.max_choices)})"
46
+ end
47
+ title
43
48
  end
44
49
  end
45
50
 
@@ -28,6 +28,7 @@ module Decidim
28
28
  end
29
29
 
30
30
  def max_votable_options
31
+ return max_choices if max_choices.present? && question_type == "multiple_option"
31
32
  return response_options.size if question_type == "multiple_option"
32
33
 
33
34
  1
@@ -4,6 +4,7 @@ module Decidim
4
4
  module Elections
5
5
  class Vote < Elections::ApplicationRecord
6
6
  include Decidim::Traceable
7
+
7
8
  belongs_to :question, class_name: "Decidim::Elections::Question", counter_cache: true, inverse_of: :votes
8
9
  belongs_to :response_option, class_name: "Decidim::Elections::ResponseOption", counter_cache: true, inverse_of: :votes
9
10
 
@@ -31,7 +32,7 @@ module Decidim
31
32
 
32
33
  def max_votable_options
33
34
  return unless question && response_option
34
- return if question.votes.where.not(id: id).where(voter_uid: voter_uid).count < question.max_votable_options
35
+ return if question.votes.where.not(id:).where(voter_uid:).count < question.max_votable_options
35
36
 
36
37
  errors.add(:response_option, :invalid)
37
38
  end
@@ -4,6 +4,7 @@ module Decidim
4
4
  module Elections
5
5
  class Voter < Elections::ApplicationRecord
6
6
  include Decidim::Traceable
7
+
7
8
  belongs_to :election, class_name: "Decidim::Elections::Election"
8
9
 
9
10
  validates :data, presence: true
@@ -1,5 +1,7 @@
1
+ import "src/decidim/elections/elections.js"
1
2
  import "src/decidim/elections/waiting_room.js"
2
3
  import "src/decidim/elections/live_results_update.js";
4
+ import "src/decidim/elections/census_check_visibility.js";
3
5
  import "src/decidim/elections/question_status_checker.js";
4
6
 
5
7
  // Images
@@ -1,6 +1,7 @@
1
1
  // JS
2
2
  import "src/decidim/elections/admin/election_form.js";
3
3
  import "src/decidim/elections/admin/census_form.js";
4
+ import "src/decidim/elections/admin/census_check_toggle.js";
4
5
  import "src/decidim/elections/live_results_update.js";
5
6
 
6
7
  // CSS
@@ -0,0 +1,44 @@
1
+ document.addEventListener("turbo:load", () => {
2
+ const checkbox = document.querySelector(".census-check-toggle");
3
+
4
+ if (!checkbox) {
5
+ return;
6
+ }
7
+
8
+ checkbox.addEventListener("change", () => {
9
+ const url = checkbox.dataset.url;
10
+ const checked = checkbox.checked;
11
+ const csrfToken = document.querySelector("meta[name='csrf-token']");
12
+
13
+ if (!csrfToken) {
14
+ console.error("CSRF token not found. Please refresh the page.");
15
+ checkbox.checked = !checked;
16
+ return;
17
+ }
18
+
19
+ fetch(url, {
20
+ method: "PATCH",
21
+ headers: {
22
+ "Content-Type": "application/json",
23
+ "X-CSRF-Token": csrfToken.content
24
+ },
25
+ body: JSON.stringify({
26
+ // eslint-disable-next-line camelcase
27
+ allow_census_check_before_start: checked
28
+ })
29
+ }).then((response) => {
30
+ if (!response.ok) {
31
+ throw new Error(`HTTP error! status: ${response.status}`);
32
+ }
33
+ return response.json();
34
+ }).then((data) => {
35
+ if (!data.success) {
36
+ checkbox.checked = !checked;
37
+ console.error(`Error updating setting: ${data.error || "Unknown error"}`);
38
+ }
39
+ }).catch((error) => {
40
+ checkbox.checked = !checked;
41
+ console.error(`Error updating setting: ${error.message}`);
42
+ });
43
+ });
44
+ });
@@ -0,0 +1,52 @@
1
+ document.addEventListener("DOMContentLoaded", () => {
2
+ const censusCheckButton = document.querySelector("[data-census-check-button]");
3
+
4
+ if (!censusCheckButton) {
5
+ return;
6
+ }
7
+
8
+ const url = censusCheckButton.dataset.censusCheckUrl;
9
+
10
+ if (!url) {
11
+ return;
12
+ }
13
+
14
+ const explanation = document.querySelector("[data-census-check-explanation]");
15
+
16
+ const toggleVisibility = (show) => {
17
+ censusCheckButton.classList.toggle("hidden", !show);
18
+ if (explanation) {
19
+ explanation.classList.toggle("hidden", !show);
20
+ }
21
+ };
22
+
23
+ const updateVisibility = async () => {
24
+ try {
25
+ const response = await fetch(url, {
26
+ method: "GET",
27
+ headers: {
28
+ "Accept": "application/json",
29
+ "Content-Type": "application/json",
30
+ "X-Requested-With": "XMLHttpRequest"
31
+ }
32
+ });
33
+
34
+ if (!response.ok) {
35
+ return;
36
+ }
37
+
38
+ const data = await response.json();
39
+ const shouldShow = data.allow_census_check_before_start && data.census_ready && data.scheduled;
40
+
41
+ toggleVisibility(shouldShow);
42
+
43
+ if (data.scheduled) {
44
+ setTimeout(updateVisibility, 4000);
45
+ }
46
+ } catch (error) {
47
+ setTimeout(updateVisibility, 4000);
48
+ }
49
+ };
50
+
51
+ updateVisibility();
52
+ });