decidim-elections 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
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