decidim-proposals 0.8.4 → 0.9.0

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