decidim-elections 0.22.0

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 (104) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +31 -0
  3. data/Rakefile +3 -0
  4. data/app/assets/config/decidim_elections_manifest.js +0 -0
  5. data/app/assets/images/decidim/elections/icon.svg +1 -0
  6. data/app/commands/decidim/elections/admin/create_answer.rb +71 -0
  7. data/app/commands/decidim/elections/admin/create_election.rb +48 -0
  8. data/app/commands/decidim/elections/admin/create_question.rb +52 -0
  9. data/app/commands/decidim/elections/admin/destroy_answer.rb +50 -0
  10. data/app/commands/decidim/elections/admin/destroy_election.rb +46 -0
  11. data/app/commands/decidim/elections/admin/destroy_question.rb +46 -0
  12. data/app/commands/decidim/elections/admin/update_answer.rb +73 -0
  13. data/app/commands/decidim/elections/admin/update_election.rb +48 -0
  14. data/app/commands/decidim/elections/admin/update_question.rb +52 -0
  15. data/app/controllers/decidim/elections/admin/answers_controller.rb +92 -0
  16. data/app/controllers/decidim/elections/admin/application_controller.rb +15 -0
  17. data/app/controllers/decidim/elections/admin/elections_controller.rb +82 -0
  18. data/app/controllers/decidim/elections/admin/questions_controller.rb +86 -0
  19. data/app/controllers/decidim/elections/application_controller.rb +13 -0
  20. data/app/controllers/decidim/elections/elections_controller.rb +9 -0
  21. data/app/forms/decidim/elections/admin/answer_form.rb +53 -0
  22. data/app/forms/decidim/elections/admin/election_form.rb +24 -0
  23. data/app/forms/decidim/elections/admin/question_form.rb +26 -0
  24. data/app/helpers/decidim/elections/application_helper.rb +10 -0
  25. data/app/models/decidim/elections/answer.rb +26 -0
  26. data/app/models/decidim/elections/application_record.rb +10 -0
  27. data/app/models/decidim/elections/election.rb +22 -0
  28. data/app/models/decidim/elections/question.rb +20 -0
  29. data/app/permissions/decidim/elections/admin/permissions.rb +42 -0
  30. data/app/permissions/decidim/elections/permissions.rb +16 -0
  31. data/app/types/decidim/elections/election_answer_type.rb +24 -0
  32. data/app/types/decidim/elections/election_question_type.rb +25 -0
  33. data/app/types/decidim/elections/election_type.rb +26 -0
  34. data/app/types/decidim/elections/elections_type.rb +32 -0
  35. data/app/views/decidim/elections/admin/answers/_form.html.erb +25 -0
  36. data/app/views/decidim/elections/admin/answers/edit.html.erb +7 -0
  37. data/app/views/decidim/elections/admin/answers/index.html.erb +46 -0
  38. data/app/views/decidim/elections/admin/answers/new.html.erb +7 -0
  39. data/app/views/decidim/elections/admin/answers/proposals_picker.html.erb +1 -0
  40. data/app/views/decidim/elections/admin/elections/_form.html.erb +28 -0
  41. data/app/views/decidim/elections/admin/elections/edit.html.erb +7 -0
  42. data/app/views/decidim/elections/admin/elections/index.html.erb +54 -0
  43. data/app/views/decidim/elections/admin/elections/new.html.erb +7 -0
  44. data/app/views/decidim/elections/admin/questions/_form.html.erb +27 -0
  45. data/app/views/decidim/elections/admin/questions/edit.html.erb +7 -0
  46. data/app/views/decidim/elections/admin/questions/index.html.erb +53 -0
  47. data/app/views/decidim/elections/admin/questions/new.html.erb +7 -0
  48. data/app/views/decidim/elections/elections/index.html.erb +1 -0
  49. data/app/views/decidim/elections/elections/show.html.erb +0 -0
  50. data/config/i18n-tasks.yml +10 -0
  51. data/config/locales/ar.yml +1 -0
  52. data/config/locales/bg-BG.yml +7 -0
  53. data/config/locales/ca.yml +132 -0
  54. data/config/locales/cs.yml +138 -0
  55. data/config/locales/da-DK.yml +1 -0
  56. data/config/locales/de.yml +132 -0
  57. data/config/locales/el.yml +132 -0
  58. data/config/locales/en.yml +133 -0
  59. data/config/locales/eo-UY.yml +1 -0
  60. data/config/locales/es-MX.yml +132 -0
  61. data/config/locales/es-PY.yml +132 -0
  62. data/config/locales/es.yml +132 -0
  63. data/config/locales/et-EE.yml +1 -0
  64. data/config/locales/eu.yml +1 -0
  65. data/config/locales/fi-plain.yml +132 -0
  66. data/config/locales/fi.yml +132 -0
  67. data/config/locales/fr-CA.yml +132 -0
  68. data/config/locales/fr.yml +132 -0
  69. data/config/locales/ga-IE.yml +1 -0
  70. data/config/locales/gl.yml +1 -0
  71. data/config/locales/hr-HR.yml +1 -0
  72. data/config/locales/hu.yml +1 -0
  73. data/config/locales/id-ID.yml +1 -0
  74. data/config/locales/is-IS.yml +1 -0
  75. data/config/locales/it.yml +132 -0
  76. data/config/locales/ja-JP.yml +129 -0
  77. data/config/locales/lt-LT.yml +1 -0
  78. data/config/locales/lv-LV.yml +135 -0
  79. data/config/locales/mt-MT.yml +1 -0
  80. data/config/locales/nl.yml +132 -0
  81. data/config/locales/no.yml +9 -0
  82. data/config/locales/pl.yml +138 -0
  83. data/config/locales/pt-BR.yml +1 -0
  84. data/config/locales/pt.yml +132 -0
  85. data/config/locales/ro-RO.yml +135 -0
  86. data/config/locales/ru.yml +1 -0
  87. data/config/locales/sk.yml +1 -0
  88. data/config/locales/sl.yml +5 -0
  89. data/config/locales/sr-CS.yml +1 -0
  90. data/config/locales/sv.yml +132 -0
  91. data/config/locales/tr-TR.yml +1 -0
  92. data/config/locales/uk.yml +1 -0
  93. data/db/migrate/20200430083618_create_decidim_elections_elections.rb +15 -0
  94. data/db/migrate/20200518082327_create_decidim_elections_questions.rb +14 -0
  95. data/db/migrate/20200518084144_create_decidim_elections_answers.rb +12 -0
  96. data/lib/decidim/elections.rb +13 -0
  97. data/lib/decidim/elections/admin.rb +10 -0
  98. data/lib/decidim/elections/admin_engine.rb +29 -0
  99. data/lib/decidim/elections/component.rb +123 -0
  100. data/lib/decidim/elections/engine.rb +23 -0
  101. data/lib/decidim/elections/seeds/city.jpeg +0 -0
  102. data/lib/decidim/elections/test/factories.rb +52 -0
  103. data/lib/decidim/elections/version.rb +10 -0
  104. metadata +201 -0
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This command is executed when the user updates a Question
7
+ # from the admin panel.
8
+ class UpdateQuestion < Rectify::Command
9
+ def initialize(form, question)
10
+ @form = form
11
+ @question = question
12
+ end
13
+
14
+ # Updates the question if valid.
15
+ #
16
+ # Broadcasts :ok if successful, :invalid otherwise.
17
+ def call
18
+ return broadcast(:invalid) if invalid?
19
+
20
+ update_question!
21
+
22
+ broadcast(:ok, question)
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :form, :question
28
+
29
+ def invalid?
30
+ question.election.started? || form.invalid?
31
+ end
32
+
33
+ def update_question!
34
+ attributes = {
35
+ title: form.title,
36
+ description: form.description,
37
+ max_selections: form.max_selections,
38
+ weight: form.weight,
39
+ random_answers_order: form.random_answers_order
40
+ }
41
+
42
+ Decidim.traceability.update!(
43
+ question,
44
+ form.current_user,
45
+ attributes,
46
+ visibility: "all"
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This controller allows the create or update answers for a question.
7
+ class AnswersController < Admin::ApplicationController
8
+ include Decidim::Proposals::Admin::Picker
9
+ helper Decidim::ApplicationHelper
10
+ helper_method :election, :question, :answers, :answers
11
+
12
+ def new
13
+ enforce_permission_to :update, :answer, election: election, question: question
14
+ @form = form(AnswerForm).instance
15
+ end
16
+
17
+ def create
18
+ enforce_permission_to :update, :answer, election: election, question: question
19
+ @form = form(AnswerForm).from_params(params, election: election, question: question)
20
+
21
+ CreateAnswer.call(@form) do
22
+ on(:ok) do
23
+ flash[:notice] = I18n.t("answers.create.success", scope: "decidim.elections.admin")
24
+ redirect_to election_question_answers_path(election, question)
25
+ end
26
+
27
+ on(:invalid) do
28
+ flash.now[:alert] = I18n.t("answers.create.invalid", scope: "decidim.elections.admin")
29
+ render action: "new"
30
+ end
31
+ end
32
+ end
33
+
34
+ def edit
35
+ enforce_permission_to :update, :answer, election: election, question: question
36
+ @form = form(AnswerForm).from_model(answer)
37
+ end
38
+
39
+ def update
40
+ enforce_permission_to :update, :answer, election: election, question: question
41
+ @form = form(AnswerForm).from_params(params, election: election, question: question)
42
+
43
+ UpdateAnswer.call(@form, answer) do
44
+ on(:ok) do
45
+ flash[:notice] = I18n.t("answers.update.success", scope: "decidim.elections.admin")
46
+ redirect_to election_question_answers_path(election, question)
47
+ end
48
+
49
+ on(:invalid) do
50
+ flash.now[:alert] = I18n.t("answers.update.invalid", scope: "decidim.elections.admin")
51
+ render action: "edit"
52
+ end
53
+ end
54
+ end
55
+
56
+ def destroy
57
+ enforce_permission_to :update, :answer, election: election, question: question
58
+
59
+ DestroyAnswer.call(answer, current_user) do
60
+ on(:ok) do
61
+ flash[:notice] = I18n.t("answers.destroy.success", scope: "decidim.elections.admin")
62
+ end
63
+
64
+ on(:invalid) do
65
+ flash.now[:alert] = I18n.t("answers.destroy.invalid", scope: "decidim.elections.admin")
66
+ end
67
+ end
68
+
69
+ redirect_to election_question_answers_path(election, question)
70
+ end
71
+
72
+ private
73
+
74
+ def election
75
+ @election ||= Election.where(component: current_component).find_by(id: params[:election_id])
76
+ end
77
+
78
+ def question
79
+ @question ||= election.questions.find_by(id: params[:question_id])
80
+ end
81
+
82
+ def answers
83
+ @answers ||= question.answers
84
+ end
85
+
86
+ def answer
87
+ answers.find(params[:id])
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This controller is the abstract class from which all other controllers of
7
+ # this engine inherit.
8
+ #
9
+ # Note that it inherits from `Decidim::Admin::Components::BaseController`, which
10
+ # override its layout and provide all kinds of useful methods.
11
+ class ApplicationController < Decidim::Admin::Components::BaseController
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This controller allows the create or update an election.
7
+ class ElectionsController < Admin::ApplicationController
8
+ helper_method :elections, :election
9
+
10
+ def new
11
+ enforce_permission_to :create, :election
12
+ @form = form(ElectionForm).instance
13
+ end
14
+
15
+ def create
16
+ enforce_permission_to :create, :election
17
+ @form = form(ElectionForm).from_params(params, current_component: current_component)
18
+
19
+ CreateElection.call(@form) do
20
+ on(:ok) do
21
+ flash[:notice] = I18n.t("elections.create.success", scope: "decidim.elections.admin")
22
+ redirect_to elections_path
23
+ end
24
+
25
+ on(:invalid) do
26
+ flash.now[:alert] = I18n.t("elections.create.invalid", scope: "decidim.elections.admin")
27
+ render action: "new"
28
+ end
29
+ end
30
+ end
31
+
32
+ def edit
33
+ enforce_permission_to :update, :election, election: election
34
+ @form = form(ElectionForm).from_model(election)
35
+ end
36
+
37
+ def update
38
+ enforce_permission_to :update, :election, election: election
39
+ @form = form(ElectionForm).from_params(params, current_component: current_component)
40
+
41
+ UpdateElection.call(@form, election) do
42
+ on(:ok) do
43
+ flash[:notice] = I18n.t("elections.update.success", scope: "decidim.elections.admin")
44
+ redirect_to elections_path
45
+ end
46
+
47
+ on(:invalid) do
48
+ flash.now[:alert] = I18n.t("elections.update.invalid", scope: "decidim.elections.admin")
49
+ render action: "edit"
50
+ end
51
+ end
52
+ end
53
+
54
+ def destroy
55
+ enforce_permission_to :delete, :election, election: election
56
+
57
+ DestroyElection.call(election, current_user) do
58
+ on(:ok) do
59
+ flash[:notice] = I18n.t("elections.destroy.success", scope: "decidim.elections.admin")
60
+ end
61
+
62
+ on(:invalid) do
63
+ flash.now[:alert] = I18n.t("elections.destroy.invalid", scope: "decidim.elections.admin")
64
+ end
65
+ end
66
+
67
+ redirect_to elections_path
68
+ end
69
+
70
+ private
71
+
72
+ def elections
73
+ @elections ||= Election.where(component: current_component)
74
+ end
75
+
76
+ def election
77
+ @election ||= elections.find_by(id: params[:id])
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This controller allows the create or update questions for an election.
7
+ class QuestionsController < Admin::ApplicationController
8
+ helper_method :election, :questions, :question
9
+
10
+ def new
11
+ enforce_permission_to :create, :question, election: election
12
+ @form = form(QuestionForm).instance
13
+ end
14
+
15
+ def create
16
+ enforce_permission_to :create, :question, election: election
17
+ @form = form(QuestionForm).from_params(params, election: election)
18
+
19
+ CreateQuestion.call(@form) do
20
+ on(:ok) do
21
+ flash[:notice] = I18n.t("questions.create.success", scope: "decidim.elections.admin")
22
+ redirect_to election_questions_path(election)
23
+ end
24
+
25
+ on(:invalid) do
26
+ flash.now[:alert] = I18n.t("questions.create.invalid", scope: "decidim.elections.admin")
27
+ render action: "new"
28
+ end
29
+ end
30
+ end
31
+
32
+ def edit
33
+ enforce_permission_to :update, :question, election: election, question: question
34
+ @form = form(QuestionForm).from_model(question)
35
+ end
36
+
37
+ def update
38
+ enforce_permission_to :update, :question, election: election, question: question
39
+ @form = form(QuestionForm).from_params(params, election: election)
40
+
41
+ UpdateQuestion.call(@form, question) do
42
+ on(:ok) do
43
+ flash[:notice] = I18n.t("questions.update.success", scope: "decidim.elections.admin")
44
+ redirect_to election_questions_path(election)
45
+ end
46
+
47
+ on(:invalid) do
48
+ flash.now[:alert] = I18n.t("questions.update.invalid", scope: "decidim.elections.admin")
49
+ render action: "edit"
50
+ end
51
+ end
52
+ end
53
+
54
+ def destroy
55
+ enforce_permission_to :update, :question, election: election, question: question
56
+
57
+ DestroyQuestion.call(question, current_user) do
58
+ on(:ok) do
59
+ flash[:notice] = I18n.t("questions.destroy.success", scope: "decidim.elections.admin")
60
+ end
61
+
62
+ on(:invalid) do
63
+ flash.now[:alert] = I18n.t("questions.destroy.invalid", scope: "decidim.elections.admin")
64
+ end
65
+ end
66
+
67
+ redirect_to election_questions_path(election)
68
+ end
69
+
70
+ private
71
+
72
+ def election
73
+ @election ||= Election.where(component: current_component).find_by(id: params[:election_id])
74
+ end
75
+
76
+ def questions
77
+ @questions ||= election.questions
78
+ end
79
+
80
+ def question
81
+ questions.find(params[:id])
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ # This controller is the abstract class from which all other controllers of
6
+ # this engine inherit.
7
+ #
8
+ # Note that it inherits from `Decidim::Components::BaseController`, which
9
+ # override its layout and provide all kinds of useful methods.
10
+ class ApplicationController < Decidim::Components::BaseController
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
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 ElectionsController < Decidim::Elections::ApplicationController
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This class holds a Form to create/update answers from Decidim's admin panel.
7
+ class AnswerForm < Decidim::Form
8
+ include TranslatableAttributes
9
+
10
+ translatable_attribute :title, String
11
+ translatable_attribute :description, String
12
+ attribute :proposal_ids, Array[Integer]
13
+ attribute :attachment, AttachmentForm
14
+ attribute :photos, Array[String]
15
+ attribute :add_photos, Array
16
+ attribute :weight, Integer, default: 0
17
+
18
+ validates :title, translatable_presence: true
19
+ validate :notify_missing_attachment_if_errored
20
+
21
+ def map_model(model)
22
+ self.proposal_ids = model.linked_resources(:proposals, "related_proposals").pluck(:id)
23
+ end
24
+
25
+ def proposals
26
+ @proposals ||= Decidim.find_resource_manifest(:proposals)
27
+ .try(:resource_scope, current_component)
28
+ &.where(id: proposal_ids)
29
+ &.order(title: :asc)
30
+ end
31
+
32
+ def election
33
+ @election ||= context[:election]
34
+ end
35
+
36
+ def question
37
+ @question ||= context[:question]
38
+ end
39
+
40
+ private
41
+
42
+ # This method will add an error to the `attachment` field only if there's
43
+ # any error in any other field. This is needed because when the form has
44
+ # an error, the attachment is lost, so we need a way to inform the user of
45
+ # this problem.
46
+ def notify_missing_attachment_if_errored
47
+ errors.add(:attachment, :needs_to_be_reattached) if errors.any? && attachment.present?
48
+ errors.add(:add_photos, :needs_to_be_reattached) if errors.any? && add_photos.present?
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This class holds a Form to create/update elections from Decidim's admin panel.
7
+ class ElectionForm < Decidim::Form
8
+ include TranslatableAttributes
9
+
10
+ translatable_attribute :title, String
11
+ translatable_attribute :subtitle, String
12
+ translatable_attribute :description, String
13
+ attribute :start_time, Decidim::Attributes::TimeWithZone
14
+ attribute :end_time, Decidim::Attributes::TimeWithZone
15
+
16
+ validates :title, translatable_presence: true
17
+ validates :subtitle, translatable_presence: true
18
+ validates :description, translatable_presence: true
19
+ validates :start_time, presence: true, date: { before: :end_time }
20
+ validates :end_time, presence: true, date: { after: :start_time }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # This class holds a Form to create/update questions from Decidim's admin panel.
7
+ class QuestionForm < Decidim::Form
8
+ include TranslatableAttributes
9
+
10
+ translatable_attribute :title, String
11
+ translatable_attribute :description, String
12
+ attribute :max_selections, Integer, default: 1
13
+ attribute :weight, Integer, default: 0
14
+ attribute :random_answers_order, Boolean, default: true
15
+
16
+ validates :title, translatable_presence: true
17
+ validates :description, translatable_presence: true
18
+ validates :max_selections, presence: true, numericality: { greater_than_or_equal_to: 0 }
19
+
20
+ def election
21
+ @election ||= context[:election]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end