decidim-proposals 0.8.4 → 0.9.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 (69) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +1 -1
  3. data/app/assets/config/decidim_proposals_manifest.css +3 -1
  4. data/app/assets/javascripts/decidim/proposals/add_proposal.js.es6 +0 -2
  5. data/app/assets/stylesheets/decidim/proposals/social_share.css.scss +8 -5
  6. data/app/commands/decidim/proposals/admin/answer_proposal.rb +32 -0
  7. data/app/commands/decidim/proposals/admin/create_proposal_note.rb +47 -0
  8. data/app/commands/decidim/proposals/create_proposal.rb +12 -0
  9. data/app/commands/decidim/proposals/withdraw_proposal.rb +37 -0
  10. data/app/controllers/decidim/proposals/admin/proposal_notes_controller.rb +40 -0
  11. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +6 -2
  12. data/app/controllers/decidim/proposals/proposals_controller.rb +20 -5
  13. data/app/events/decidim/proposals/accepted_proposal_event.rb +9 -0
  14. data/app/events/decidim/proposals/create_proposal_event.rb +9 -0
  15. data/app/events/decidim/proposals/evaluating_proposal_event.rb +8 -0
  16. data/app/events/decidim/proposals/rejected_proposal_event.rb +9 -0
  17. data/app/forms/decidim/proposals/admin/proposal_form.rb +12 -12
  18. data/app/forms/decidim/proposals/admin/proposal_note_form.rb +16 -0
  19. data/app/forms/decidim/proposals/proposal_form.rb +11 -11
  20. data/app/helpers/decidim/proposals/application_helper.rb +2 -0
  21. data/app/models/decidim/proposals/abilities/admin_ability.rb +1 -0
  22. data/app/models/decidim/proposals/abilities/current_user_ability.rb +8 -0
  23. data/app/models/decidim/proposals/abilities/participatory_process_admin_ability.rb +1 -0
  24. data/app/models/decidim/proposals/proposal.rb +30 -9
  25. data/app/models/decidim/proposals/proposal_note.rb +13 -0
  26. data/app/presenters/decidim/proposals/official_author_presenter.rb +34 -0
  27. data/app/presenters/decidim/proposals/proposal_presenter.rb +20 -0
  28. data/app/services/decidim/proposals/proposal_search.rb +4 -2
  29. data/app/views/decidim/proposals/admin/proposal_answers/edit.html.erb +1 -0
  30. data/app/views/decidim/proposals/admin/proposal_notes/_form.html.erb +8 -0
  31. data/app/views/decidim/proposals/admin/proposal_notes/_proposal_notes.html.erb +45 -0
  32. data/app/views/decidim/proposals/admin/proposal_notes/index.html.erb +3 -0
  33. data/app/views/decidim/proposals/admin/proposals/_form.html.erb +1 -1
  34. data/app/views/decidim/proposals/admin/proposals/index.html.erb +60 -8
  35. data/app/views/decidim/proposals/admin/shared/_info_proposal.html.erb +20 -0
  36. data/app/views/decidim/proposals/proposals/_filters.html.erb +1 -1
  37. data/app/views/decidim/proposals/proposals/_linked_proposals.html.erb +11 -8
  38. data/app/views/decidim/proposals/proposals/_proposal.html.erb +5 -2
  39. data/app/views/decidim/proposals/proposals/_proposal_badge.html.erb +3 -0
  40. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +3 -3
  41. data/app/views/decidim/proposals/proposals/edit.html.erb +2 -2
  42. data/app/views/decidim/proposals/proposals/index.html.erb +6 -2
  43. data/app/views/decidim/proposals/proposals/new.html.erb +2 -2
  44. data/app/views/decidim/proposals/proposals/show.html.erb +8 -4
  45. data/config/locales/ca.yml +46 -7
  46. data/config/locales/en.yml +46 -6
  47. data/config/locales/es.yml +46 -7
  48. data/config/locales/eu.yml +46 -7
  49. data/config/locales/fi.yml +58 -19
  50. data/config/locales/fr.yml +53 -14
  51. data/config/locales/gl.yml +231 -0
  52. data/config/locales/it.yml +46 -7
  53. data/config/locales/nl.yml +46 -7
  54. data/config/locales/pl.yml +46 -7
  55. data/config/locales/pt-BR.yml +231 -0
  56. data/config/locales/pt.yml +48 -9
  57. data/config/locales/ru.yml +7 -7
  58. data/config/locales/sv.yml +231 -0
  59. data/config/locales/uk.yml +7 -7
  60. data/db/migrate/20170215132030_add_reference_to_proposals.rb +5 -1
  61. data/db/migrate/20180111110204_create_decidim_proposal_notes.rb +15 -0
  62. data/db/migrate/20180115155220_add_index_created_at_proposal_notes.rb +7 -0
  63. data/lib/decidim/proposals/admin_engine.rb +1 -0
  64. data/lib/decidim/proposals/engine.rb +4 -0
  65. data/lib/decidim/proposals/feature.rb +21 -6
  66. data/lib/decidim/proposals/test/factories.rb +10 -0
  67. data/lib/decidim/proposals/version.rb +1 -1
  68. metadata +60 -27
  69. data/app/views/decidim/proposals/proposals/_author.html.erb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fedda0338cd11c26bf13d6aa263c43a07cd10436
4
- data.tar.gz: be1a4f2582d285333a27462c8ef70869da7764e8
2
+ SHA256:
3
+ metadata.gz: e91f7c5944cada8255ade51d10276d5c666db13515a2dba890ffa2415d4777bf
4
+ data.tar.gz: 94b46b9d2df7e575e7c5fb7c466a4388bb31d7d99a0242bcb5033ea17cf1796c
5
5
  SHA512:
6
- metadata.gz: 5487d3f88af35b6f5e1df664322ca09b8b96cb1f020aff4e3fa8bdfc695d487e73775f5d2ad3e089c2ee5427337baa449abfcdfc3f0334e6e0afe4738c37c5a6
7
- data.tar.gz: c7b66364e67f6101d673396cbf1eb2223c6beeda6433551eafcfbc1ccea017f4d3f56e318fb688218ca1e1a699d84898bae75d00eacb3fbc10efb61fd700ad64
6
+ metadata.gz: '0805a7c4f5b29dc9592ccbd5d818768ad3300dbd7666de2dbe4955a7aa6458031fa6e0efc969bb9f59a37ac15fbd44d790d6e0466bc72a4b574c30a468e25578'
7
+ data.tar.gz: 56d62ad991804d97365baa634e9ad8e7e7350d2f6fe910ec1459e0af8e8f5ccf75d56011da21f8dc439400a6a8b18c9b8d4a412cad1ed997ec400d3739e957a1
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Decidim::Proposals
2
2
 
3
- The Proposals module adds one of the main features of Decidim: allows users to contribute to a particiaptory process by creating proposals.
3
+ The Proposals module adds one of the main features of Decidim: allows users to contribute to a participatory process by creating proposals.
4
4
 
5
5
  ## Usage
6
6
 
@@ -1 +1,3 @@
1
- /*= link decidim/proposals/social_share.css.scss */
1
+ /*
2
+ *= link decidim/proposals/social_share.css.scss
3
+ */
@@ -16,8 +16,6 @@ $(() => {
16
16
  toggleInput();
17
17
  $checkbox.on('change', toggleInput);
18
18
  }
19
-
20
- new window.Decidim.Select2Field($('#proposal_scope_id')); // eslint-disable-line no-new
21
19
  };
22
20
 
23
21
  window.DecidimProposals.bindProposalAddress();
@@ -1,19 +1,22 @@
1
- /*= require social-share-button */
1
+ /*
2
+ *= require social-share-button
3
+ */
4
+
2
5
  $size: 45px;
3
6
 
4
- .share-link:hover {
7
+ .share-link:hover{
5
8
  text-decoration: underline;
6
9
  cursor: pointer;
7
10
  }
8
11
 
9
- .social-share-button {
12
+ .social-share-button{
10
13
  display: inline-block;
11
14
  vertical-align: top;
12
- .ssb-icon {
15
+
16
+ .ssb-icon{
13
17
  margin-right: 5px;
14
18
  background-size: $size $size;
15
19
  height: $size;
16
20
  width: $size;
17
21
  }
18
22
  }
19
-
@@ -24,6 +24,8 @@ module Decidim
24
24
  return broadcast(:invalid) if form.invalid?
25
25
 
26
26
  answer_proposal
27
+ notify_followers
28
+
27
29
  broadcast(:ok)
28
30
  end
29
31
 
@@ -38,6 +40,36 @@ module Decidim
38
40
  answered_at: Time.current
39
41
  )
40
42
  end
43
+
44
+ def notify_followers
45
+ return if (proposal.previous_changes.keys & %w(state)).empty?
46
+
47
+ if proposal.accepted?
48
+ publish_event(
49
+ "decidim.events.proposals.proposal_accepted",
50
+ Decidim::Proposals::AcceptedProposalEvent
51
+ )
52
+ elsif proposal.rejected?
53
+ publish_event(
54
+ "decidim.events.proposals.proposal_rejected",
55
+ Decidim::Proposals::RejectedProposalEvent
56
+ )
57
+ elsif proposal.evaluating?
58
+ publish_event(
59
+ "decidim.events.proposals.proposal_evaluating",
60
+ Decidim::Proposals::EvaluatingProposalEvent
61
+ )
62
+ end
63
+ end
64
+
65
+ def publish_event(event, event_class)
66
+ Decidim::EventsManager.publish(
67
+ event: event,
68
+ event_class: event_class,
69
+ resource: proposal,
70
+ recipient_ids: proposal.followers.pluck(:id)
71
+ )
72
+ end
41
73
  end
42
74
  end
43
75
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # A command with all the business logic when an admin creates a private note proposal.
7
+ class CreateProposalNote < Rectify::Command
8
+ # Public: Initializes the command.
9
+ #
10
+ # form - A form object with the params.
11
+ # current_user - The current user.
12
+ # proposal - the proposal to relate.
13
+ def initialize(form, proposal, current_user)
14
+ @form = form
15
+ @proposal = proposal
16
+ @current_user = current_user
17
+ end
18
+
19
+ # Executes the command. Broadcasts these events:
20
+ #
21
+ # - :ok when everything is valid, together with the note proposal.
22
+ # - :invalid if the form wasn't valid and we couldn't proceed.
23
+ #
24
+ # Returns nothing.
25
+ def call
26
+ return broadcast(:invalid) if form.invalid?
27
+
28
+ create_proposal_note
29
+
30
+ broadcast(:ok, proposal_note)
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :form, :proposal_note
36
+
37
+ def create_proposal_note
38
+ @proposal_note = ProposalNote.create!(
39
+ body: form.body,
40
+ proposal: @proposal,
41
+ author: @current_user
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -35,6 +35,7 @@ module Decidim
35
35
  transaction do
36
36
  create_proposal
37
37
  create_attachment if process_attachments?
38
+ send_notification
38
39
  end
39
40
 
40
41
  broadcast(:ok, proposal)
@@ -42,6 +43,17 @@ module Decidim
42
43
 
43
44
  private
44
45
 
46
+ def send_notification
47
+ return if proposal.author.blank?
48
+
49
+ Decidim::EventsManager.publish(
50
+ event: "decidim.events.proposals.proposal_created",
51
+ event_class: Decidim::Proposals::CreateProposalEvent,
52
+ resource: proposal,
53
+ recipient_ids: proposal.author.followers.pluck(:id)
54
+ )
55
+ end
56
+
45
57
  attr_reader :form, :proposal, :attachment
46
58
 
47
59
  def create_proposal
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # A command with all the business logic when a user withdraws a new proposal.
6
+ class WithdrawProposal < Rectify::Command
7
+ # Public: Initializes the command.
8
+ #
9
+ # proposal - The proposal to withdraw.
10
+ # current_user - The current user.
11
+ def initialize(proposal, current_user)
12
+ @proposal = proposal
13
+ @current_user = current_user
14
+ end
15
+
16
+ # Executes the command. Broadcasts these events:
17
+ #
18
+ # - :ok when everything is valid, together with the proposal.
19
+ # - :invalid if the proposal already has supports or does not belong to current user.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) if @proposal.votes.any?
24
+
25
+ change_proposal_state_to_withdrawn
26
+
27
+ broadcast(:ok, @proposal)
28
+ end
29
+
30
+ private
31
+
32
+ def change_proposal_state_to_withdrawn
33
+ @proposal.update_attributes state: "withdrawn"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # This controller allows admins to make private notes on proposals in a participatory process.
7
+ class ProposalNotesController < Admin::ApplicationController
8
+ helper_method :proposal
9
+
10
+ def index
11
+ authorize! :create, ProposalNote
12
+ @form = form(ProposalNoteForm).instance
13
+ end
14
+
15
+ def create
16
+ authorize! :create, ProposalNote
17
+ @form = form(ProposalNoteForm).from_params(params)
18
+
19
+ CreateProposalNote.call(@form, proposal, current_user) do
20
+ on(:ok) do
21
+ flash[:notice] = I18n.t("proposals.create.success", scope: "decidim")
22
+ redirect_to proposal_proposal_notes_path(proposal_id: proposal.id)
23
+ end
24
+
25
+ on(:invalid) do
26
+ flash.now[:alert] = I18n.t("proposals.create.error", scope: "decidim")
27
+ render :index
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def proposal
35
+ @proposals ||= Proposal.where(feature: current_feature).find(params[:proposal_id])
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -6,7 +6,7 @@ module Decidim
6
6
  # This controller allows admins to manage proposals in a participatory process.
7
7
  class ProposalsController < Admin::ApplicationController
8
8
  helper Proposals::ApplicationHelper
9
- helper_method :proposals
9
+ helper_method :proposals, :query
10
10
 
11
11
  def new
12
12
  authorize! :create, Proposal
@@ -34,8 +34,12 @@ module Decidim
34
34
 
35
35
  private
36
36
 
37
+ def query
38
+ @query ||= Proposal.where(feature: current_feature).ransack(params[:q])
39
+ end
40
+
37
41
  def proposals
38
- @proposals ||= Proposal.where(feature: current_feature).page(params[:page]).per(15)
42
+ @proposals ||= query.result.page(params[:page]).per(15)
39
43
  end
40
44
 
41
45
  def proposal
@@ -55,7 +55,7 @@ module Decidim
55
55
  CreateProposal.call(@form, current_user) do
56
56
  on(:ok) do |proposal|
57
57
  flash[:notice] = I18n.t("proposals.create.success", scope: "decidim")
58
- redirect_to proposal_path(proposal)
58
+ redirect_to Decidim::ResourceLocatorPresenter.new(proposal).path
59
59
  end
60
60
 
61
61
  on(:invalid) do
@@ -77,16 +77,31 @@ module Decidim
77
77
  authorize! :edit, @proposal
78
78
 
79
79
  @form = form(ProposalForm).from_params(params)
80
-
81
80
  UpdateProposal.call(@form, current_user, @proposal) do
82
81
  on(:ok) do |proposal|
83
82
  flash[:notice] = I18n.t("proposals.update.success", scope: "decidim")
84
- redirect_to proposal_path(proposal)
83
+ redirect_to Decidim::ResourceLocatorPresenter.new(proposal).path
85
84
  end
86
85
 
87
86
  on(:invalid) do
88
87
  flash.now[:alert] = I18n.t("proposals.update.error", scope: "decidim")
89
- render :new
88
+ render :edit
89
+ end
90
+ end
91
+ end
92
+
93
+ def withdraw
94
+ @proposal = Proposal.not_hidden.where(feature: current_feature).find(params[:id])
95
+ authorize! :withdraw, @proposal
96
+
97
+ WithdrawProposal.call(@proposal, current_user) do
98
+ on(:ok) do |_proposal|
99
+ flash[:notice] = I18n.t("proposals.update.success", scope: "decidim")
100
+ redirect_to Decidim::ResourceLocatorPresenter.new(@proposal).path
101
+ end
102
+ on(:invalid) do
103
+ flash[:alert] = I18n.t("proposals.update.error", scope: "decidim")
104
+ redirect_to Decidim::ResourceLocatorPresenter.new(@proposal).path
90
105
  end
91
106
  end
92
107
  end
@@ -107,7 +122,7 @@ module Decidim
107
122
  origin: "all",
108
123
  activity: "",
109
124
  category_id: "",
110
- state: "all",
125
+ state: "not_withdrawn",
111
126
  scope_id: nil,
112
127
  related_to: ""
113
128
  }
@@ -0,0 +1,9 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ class AcceptedProposalEvent < Decidim::Events::SimpleEvent
6
+ include Decidim::Events::AuthorEvent
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ class CreateProposalEvent < Decidim::Events::SimpleEvent
6
+ include Decidim::Events::AuthorEvent
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ class EvaluatingProposalEvent < Decidim::Events::SimpleEvent
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ class RejectedProposalEvent < Decidim::Events::SimpleEvent
6
+ include Decidim::Events::AuthorEvent
7
+ end
8
+ end
9
+ end
@@ -20,8 +20,9 @@ module Decidim
20
20
  validates :address, geocoding: true, if: -> { current_feature.settings.geocoding_enabled? }
21
21
  validates :category, presence: true, if: ->(form) { form.category_id.present? }
22
22
  validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
23
+ validate { errors.add(:scope_id, :invalid) if current_participatory_space&.scope && !current_participatory_space&.scope&.ancestor_of?(scope) }
23
24
 
24
- delegate :categories, to: :current_feature, prefix: false
25
+ delegate :categories, to: :current_feature
25
26
 
26
27
  def map_model(model)
27
28
  return unless model.categorization
@@ -29,28 +30,27 @@ module Decidim
29
30
  self.category_id = model.categorization.decidim_category_id
30
31
  end
31
32
 
32
- def organization_scopes
33
- current_organization.scopes
34
- end
35
-
36
- def process_scope
37
- current_feature.participatory_space.scope
38
- end
39
-
40
33
  alias feature current_feature
41
34
 
42
35
  # Finds the Category from the category_id.
43
36
  #
44
37
  # Returns a Decidim::Category
45
38
  def category
46
- @category ||= categories.where(id: category_id).first
39
+ @category ||= categories.find_by(id: category_id)
47
40
  end
48
41
 
49
- # Finds the Scope from the scope_id.
42
+ # Finds the Scope from the given decidim_scope_id, uses participatory space scope if missing.
50
43
  #
51
44
  # Returns a Decidim::Scope
52
45
  def scope
53
- @scope ||= organization_scopes.where(id: scope_id).first || process_scope
46
+ @scope ||= @scope_id ? current_feature.scopes.find_by(id: @scope_id) : current_participatory_space&.scope
47
+ end
48
+
49
+ # Scope identifier
50
+ #
51
+ # Returns the scope identifier related to the proposal
52
+ def scope_id
53
+ @scope_id || scope&.id
54
54
  end
55
55
  end
56
56
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module Admin
6
+ # A form object to be used when admin users want to create a proposal.
7
+ class ProposalNoteForm < Decidim::Form
8
+ mimic :proposal_note
9
+
10
+ attribute :body, String
11
+
12
+ validates :body, presence: true
13
+ end
14
+ end
15
+ end
16
+ end