decidim-proposals 0.0.3 → 0.0.5

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +2 -0
  3. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +2 -0
  4. data/app/controllers/decidim/proposals/proposals_controller.rb +42 -5
  5. data/app/forms/decidim/proposals/proposal_form.rb +3 -1
  6. data/app/helpers/decidim/proposals/proposal_votes_helper.rb +1 -1
  7. data/app/models/decidim/proposals/abilities/admin_user.rb +45 -0
  8. data/app/models/decidim/proposals/abilities/current_user.rb +22 -4
  9. data/app/models/decidim/proposals/abilities/process_admin_user.rb +57 -0
  10. data/app/models/decidim/proposals/proposal.rb +23 -2
  11. data/app/services/decidim/proposals/proposal_search.rb +25 -13
  12. data/app/views/decidim/proposals/admin/proposal_answers/edit.html.erb +1 -1
  13. data/app/views/decidim/proposals/admin/proposals/_form.html.erb +1 -1
  14. data/app/views/decidim/proposals/admin/proposals/index.html.erb +16 -10
  15. data/app/views/decidim/proposals/admin/proposals/new.html.erb +1 -1
  16. data/app/views/decidim/proposals/proposal_votes/update_buttons_and_counters.js.erb +4 -4
  17. data/app/views/decidim/proposals/proposals/_filters.html.erb +15 -3
  18. data/app/views/decidim/proposals/proposals/_proposal.html.erb +3 -11
  19. data/app/views/decidim/proposals/proposals/_proposals.html.erb +2 -1
  20. data/app/views/decidim/proposals/proposals/_remaining_votes_count.html.erb +3 -1
  21. data/app/views/decidim/proposals/proposals/_tags.html.erb +10 -0
  22. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +23 -21
  23. data/app/views/decidim/proposals/proposals/_votes_count.html.erb +9 -6
  24. data/app/views/decidim/proposals/proposals/_votes_limit.html.erb +1 -3
  25. data/app/views/decidim/proposals/proposals/index.html.erb +3 -3
  26. data/app/views/decidim/proposals/proposals/index.js.erb +6 -1
  27. data/app/views/decidim/proposals/proposals/new.html.erb +2 -2
  28. data/app/views/decidim/proposals/proposals/show.html.erb +23 -28
  29. data/config/i18n-tasks.yml +2 -0
  30. data/config/locales/ca.yml +30 -15
  31. data/config/locales/en.yml +19 -4
  32. data/config/locales/es.yml +19 -4
  33. data/config/locales/eu.yml +5 -0
  34. data/db/migrate/20170205082832_add_index_to_decidim_proposals_proposals_proposal_votes_count.rb +7 -0
  35. data/lib/decidim/proposals/admin_engine.rb +7 -0
  36. data/lib/decidim/proposals/feature.rb +16 -3
  37. data/lib/decidim/proposals/test/factories.rb +1 -1
  38. metadata +19 -15
  39. data/app/views/decidim/proposals/proposals/_share.html.erb +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 72d9615abcde37945383d87bb3f7c74126d66b3c
4
- data.tar.gz: 588e35fae074a55307aab6789d6d3af25db87d9a
3
+ metadata.gz: 1fa61b404c05485715f4617683618718c79f9a92
4
+ data.tar.gz: 3afb55cb4acae13ff3f0a63cd74432eb67c8cc6f
5
5
  SHA512:
6
- metadata.gz: a883d5c8a999da8fb3f0bda1811678905858d1693f69b846b975b66f2c25cbdbf0bfa7c671ceb991519f2b68972ae2cef71bd08be7a21f03cda07a5dfbe6cc9e
7
- data.tar.gz: d0882dff25bb3c47491036de7988d4e1fd392e6d1806e49a287ebcd4cf02e9e70a6a64b829e07102e16607ea31363ba17afb1dbed0968450df66e02ba2033330
6
+ metadata.gz: f9927865a15b7e1310566776820ffd86fa29788fa6db29dc0c030afd681f7055f1991e8c031a043ee80d2219698440c25d2d80ff796b2a112319cd551f7f0445
7
+ data.tar.gz: 9e56593a4e2fc70245eb0c91d60e3172938c3b9932725ef94e385e8ce2dae2522f49d24ac950b38b25e0b1e920f8036bb09fa9dfd09272c08b54dc573d93710b
@@ -7,10 +7,12 @@ module Decidim
7
7
  helper_method :proposal
8
8
 
9
9
  def edit
10
+ authorize! :update, proposal
10
11
  @form = form(Admin::ProposalAnswerForm).from_model(proposal)
11
12
  end
12
13
 
13
14
  def update
15
+ authorize! :update, proposal
14
16
  @form = form(Admin::ProposalAnswerForm).from_params(params)
15
17
 
16
18
  Admin::AnswerProposal.call(@form, proposal) do
@@ -8,10 +8,12 @@ module Decidim
8
8
  helper_method :proposals
9
9
 
10
10
  def new
11
+ authorize! :create, Proposal
11
12
  @form = form(Admin::ProposalForm).from_params({})
12
13
  end
13
14
 
14
15
  def create
16
+ authorize! :create, Proposal
15
17
  @form = form(Admin::ProposalForm).from_params(params)
16
18
 
17
19
  Admin::CreateProposal.call(@form) do
@@ -7,6 +7,8 @@ module Decidim
7
7
  include FormFactory
8
8
  include FilterResource
9
9
 
10
+ helper_method :order, :random_seed
11
+
10
12
  before_action :authenticate_user!, only: [:new, :create]
11
13
 
12
14
  def show
@@ -17,11 +19,20 @@ module Decidim
17
19
  @proposals = search
18
20
  .results
19
21
  .includes(:author)
20
- .includes(votes: [:author])
21
- .page(params[:page])
22
- .per(12)
22
+ .includes(:category)
23
+ .includes(:scope)
24
+
25
+ @proposals = @proposals.page(params[:page]).per(12)
26
+ @proposals = reorder(@proposals)
23
27
 
24
- @random_seed = search.random_seed
28
+ @voted_proposals = if current_user
29
+ ProposalVote.where(
30
+ author: current_user,
31
+ proposal: @proposals
32
+ ).pluck(:decidim_proposal_id)
33
+ else
34
+ []
35
+ end
25
36
  end
26
37
 
27
38
  def new
@@ -50,6 +61,31 @@ module Decidim
50
61
 
51
62
  private
52
63
 
64
+ def order
65
+ @order = params[:order] || "random"
66
+ end
67
+
68
+ # Returns: A random float number between -1 and 1 to be used as a random seed at the database.
69
+ def random_seed
70
+ @random_seed ||= (params[:random_seed] ? params[:random_seed].to_f : (rand * 2 - 1))
71
+ end
72
+
73
+ def reorder(proposals)
74
+ case order
75
+ when "random"
76
+ Proposal.transaction do
77
+ Proposal.connection.execute("SELECT setseed(#{Proposal.connection.quote(random_seed)})")
78
+ proposals.order("RANDOM()").load
79
+ end
80
+ when "most_voted"
81
+ proposals.order(proposal_votes_count: :desc)
82
+ when "recent"
83
+ proposals.order(created_at: :desc)
84
+ else
85
+ proposals
86
+ end
87
+ end
88
+
53
89
  def search_klass
54
90
  ProposalSearch
55
91
  end
@@ -61,7 +97,8 @@ module Decidim
61
97
  activity: "",
62
98
  category_id: "",
63
99
  state: "all",
64
- random_seed: params[:random_seed]
100
+ scope_id: "",
101
+ related_to: ""
65
102
  }
66
103
  end
67
104
  end
@@ -11,7 +11,9 @@ module Decidim
11
11
  attribute :scope_id, Integer
12
12
  attribute :user_group_id, Integer
13
13
 
14
- validates :title, :body, presence: true
14
+ validates :title, :body, presence: true, etiquette: true
15
+ validates :title, length: { maximum: 150 }
16
+ validates :body, length: { maximum: 500 }, etiquette: true
15
17
  validates :category, presence: true, if: ->(form) { form.category_id.present? }
16
18
  validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
17
19
 
@@ -27,7 +27,7 @@ module Decidim
27
27
  #
28
28
  # Returns true if the vote limit is enabled
29
29
  def vote_limit_enabled?
30
- current_user && feature_settings.vote_limit.present? && feature_settings.vote_limit.positive?
30
+ current_user && current_settings.votes_enabled? && feature_settings.vote_limit.present? && feature_settings.vote_limit.positive?
31
31
  end
32
32
 
33
33
  # Return the remaining votes for a user if the current feature has a vote limit
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Proposals
4
+ module Abilities
5
+ # Defines the abilities related to proposals for a logged in admin user.
6
+ # Intended to be used with `cancancan`.
7
+ class AdminUser
8
+ include CanCan::Ability
9
+
10
+ attr_reader :user, :context
11
+
12
+ def initialize(user, context)
13
+ return unless user && user.role?(:admin)
14
+
15
+ @user = user
16
+ @context = context
17
+
18
+ can :manage, Proposal
19
+ cannot :create, Proposal unless can_create_proposal?
20
+ cannot :update, Proposal unless can_update_proposal?
21
+ end
22
+
23
+ private
24
+
25
+ def current_settings
26
+ context.fetch(:current_settings, nil)
27
+ end
28
+
29
+ def feature_settings
30
+ context.fetch(:feature_settings, nil)
31
+ end
32
+
33
+ def can_create_proposal?
34
+ current_settings.try(:creation_enabled?) &&
35
+ feature_settings.try(:official_proposals_enabled)
36
+ end
37
+
38
+ def can_update_proposal?
39
+ current_settings.try(:proposal_answering_enabled) &&
40
+ feature_settings.try(:proposal_answering_enabled)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -16,27 +16,38 @@ module Decidim
16
16
  @context = context
17
17
 
18
18
  can :vote, Proposal do |_proposal|
19
- voting_enabled? && remaining_votes.positive?
19
+ authorized?(:vote) && voting_enabled? && remaining_votes.positive?
20
20
  end
21
21
 
22
22
  can :unvote, Proposal do |_proposal|
23
- voting_enabled? && vote_limit_enabled?
23
+ authorized?(:vote) && voting_enabled? && vote_limit_enabled?
24
24
  end
25
25
 
26
- can :create, Proposal if current_settings.try(:creation_enabled?)
26
+ can :create, Proposal if authorized?(:create) && creation_enabled?
27
27
  end
28
28
 
29
29
  private
30
30
 
31
+ def authorized?(action)
32
+ return unless feature
33
+
34
+ ActionAuthorizer.new(user, feature, action).authorize.ok?
35
+ end
36
+
31
37
  def vote_limit_enabled?
32
38
  return unless feature_settings
33
39
  feature_settings.vote_limit.present? && feature_settings.vote_limit.positive?
34
40
  end
35
41
 
42
+ def creation_enabled?
43
+ return unless current_settings
44
+ current_settings.creation_enabled?
45
+ end
46
+
36
47
  def remaining_votes
37
48
  return 1 unless vote_limit_enabled?
38
49
 
39
- proposals = Proposal.where(feature: context.fetch(:current_feature))
50
+ proposals = Proposal.where(feature: feature)
40
51
  votes_count = ProposalVote.where(author: user, proposal: proposals).size
41
52
  feature_settings.vote_limit - votes_count
42
53
  end
@@ -53,6 +64,13 @@ module Decidim
53
64
  def feature_settings
54
65
  context.fetch(:feature_settings, nil)
55
66
  end
67
+
68
+ def feature
69
+ feature = context.fetch(:current_feature, nil)
70
+ return nil unless feature && feature.manifest.name == :proposals
71
+
72
+ feature
73
+ end
56
74
  end
57
75
  end
58
76
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Proposals
4
+ module Abilities
5
+ # Defines the abilities related to proposals for a logged in process admin user.
6
+ # Intended to be used with `cancancan`.
7
+ class ProcessAdminUser
8
+ include CanCan::Ability
9
+
10
+ attr_reader :user, :context
11
+
12
+ def initialize(user, context)
13
+ return unless user && !user.role?(:admin)
14
+
15
+ @user = user
16
+ @context = context
17
+
18
+ can :manage, Proposal do |proposal|
19
+ participatory_processes.include?(proposal.feature.participatory_process)
20
+ end
21
+
22
+ cannot :create, Proposal unless can_create_proposal?
23
+ cannot :update, Proposal unless can_update_proposal?
24
+ end
25
+
26
+ private
27
+
28
+ def current_settings
29
+ context.fetch(:current_settings, nil)
30
+ end
31
+
32
+ def feature_settings
33
+ context.fetch(:feature_settings, nil)
34
+ end
35
+
36
+ def current_feature
37
+ context.fetch(:current_feature, nil)
38
+ end
39
+
40
+ def can_create_proposal?
41
+ current_settings.try(:creation_enabled?) &&
42
+ feature_settings.try(:official_proposals_enabled) &&
43
+ participatory_processes.include?(current_feature.try(:participatory_process))
44
+ end
45
+
46
+ def can_update_proposal?
47
+ current_settings.try(:proposal_answering_enabled) &&
48
+ feature_settings.try(:proposal_answering_enabled)
49
+ end
50
+
51
+ def participatory_processes
52
+ @participatory_processes ||= Decidim::Admin::ManageableParticipatoryProcessesForUser.for(@user)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -8,10 +8,11 @@ module Decidim
8
8
  include Decidim::HasFeature
9
9
  include Decidim::HasScope
10
10
  include Decidim::HasCategory
11
+ include Decidim::Comments::Commentable
11
12
 
12
13
  feature_manifest_name "proposals"
13
14
 
14
- has_many :votes, foreign_key: "decidim_proposal_id", class_name: ProposalVote, dependent: :destroy
15
+ has_many :votes, foreign_key: "decidim_proposal_id", class_name: ProposalVote, dependent: :destroy, counter_cache: "proposal_votes_count"
15
16
 
16
17
  validates :title, :body, presence: true
17
18
 
@@ -30,7 +31,7 @@ module Decidim
30
31
  #
31
32
  # Returns Boolean.
32
33
  def voted_by?(user)
33
- votes.any? { |vote| vote.author == user }
34
+ votes.where(author: user).any?
34
35
  end
35
36
 
36
37
  # Public: Checks if the organization has given an answer for the proposal.
@@ -53,6 +54,26 @@ module Decidim
53
54
  def rejected?
54
55
  state == "rejected"
55
56
  end
57
+
58
+ # Public: Overrides the `commentable?` Commentable concern method.
59
+ def commentable?
60
+ feature.settings.comments_enabled?
61
+ end
62
+
63
+ # Public: Overrides the `accepts_new_comments?` Commentable concern method.
64
+ def accepts_new_comments?
65
+ commentable? && !feature.active_step_settings.comments_blocked
66
+ end
67
+
68
+ # Public: Overrides the `comments_have_alignment?` Commentable concern method.
69
+ def comments_have_alignment?
70
+ true
71
+ end
72
+
73
+ # Public: Overrides the `comments_have_votes?` Commentable concern method.
74
+ def comments_have_votes?
75
+ true
76
+ end
56
77
  end
57
78
  end
58
79
  end
@@ -8,10 +8,8 @@ module Decidim
8
8
  # feature - A Decidim::Feature to get the proposals from.
9
9
  # page - The page number to paginate the results.
10
10
  # per_page - The number of proposals to return per page.
11
- # random_seed - A random flaot number between -1 and 1 to be used as a random seed at the database.
12
11
  def initialize(options = {})
13
12
  super(Proposal.all, options)
14
- @random_seed = options[:random_seed].to_f
15
13
  end
16
14
 
17
15
  # Handle the search_text filter
@@ -46,6 +44,11 @@ module Decidim
46
44
  end
47
45
  end
48
46
 
47
+ # Handle the scope_id filter
48
+ def search_scope_id
49
+ query.where(decidim_scope_id: scope_id)
50
+ end
51
+
49
52
  # Handle the state filter
50
53
  def search_state
51
54
  if state == "accepted"
@@ -57,18 +60,27 @@ module Decidim
57
60
  end
58
61
  end
59
62
 
60
- # Returns the random proposals for the current page.
61
- def results
62
- @proposals ||= Proposal.transaction do
63
- Proposal.connection.execute("SELECT setseed(#{Proposal.connection.quote(random_seed)})")
64
- super.reorder("RANDOM()").load
65
- end
66
- end
63
+ # Filters Proposals by the name of the classes they are linked to. By default,
64
+ # returns all Proposals. When a `related_to` param is given, then it camelcases item
65
+ # to find the real class name and checks the links for the Proposals.
66
+ #
67
+ # The `related_to` param is expected to be in this form:
68
+ #
69
+ # "decidim/meetings/meeting"
70
+ #
71
+ # This can be achieved by performing `klass.name.underscore`.
72
+ #
73
+ # Returns only those proposals that are linked to the given class name.
74
+ def search_related_to
75
+ from = query
76
+ .joins(:resource_links_from)
77
+ .where(decidim_resource_links: { to_type: related_to.camelcase })
78
+
79
+ to = query
80
+ .joins(:resource_links_to)
81
+ .where(decidim_resource_links: { from_type: related_to.camelcase })
67
82
 
68
- # Returns the random seed used to randomize the proposals.
69
- def random_seed
70
- @random_seed = (rand * 2 - 1) if @random_seed == 0.0 || @random_seed > 1 || @random_seed < -1
71
- @random_seed
83
+ query.where(id: from).or(query.where(id: to))
72
84
  end
73
85
  end
74
86
  end
@@ -1,6 +1,6 @@
1
1
  <h3><%= t ".title", title: proposal.title %></h3>
2
2
 
3
- <%= form_for(@form, url: proposal_proposal_answer_path(proposal, @form)) do |f| %>
3
+ <%= decidim_form_for(@form, url: proposal_proposal_answer_path(proposal, @form)) do |f| %>
4
4
  <div class="field">
5
5
  <%= f.collection_radio_buttons :state, [["accepted", t('.accepted')], ["rejected", t('.rejected')]], :first, :last, prompt: true %>
6
6
  </div>
@@ -12,7 +12,7 @@
12
12
  </div>
13
13
  <% end %>
14
14
 
15
- <% if @form.scopes&.any? %>
15
+ <% if @form.scopes&.any? && feature_settings.scoped_proposals_enabled %>
16
16
  <div class="field">
17
17
  <%= form.select :scope_id, @form.scopes.map{|s| [s.name, s.id]}, prompt: t(".select_a_scope") %>
18
18
  </div>
@@ -1,15 +1,19 @@
1
1
  <h2><%= t(".title") %></h2>
2
2
 
3
- <div class="actions title">
4
- <%= link_to t("actions.new", scope: "decidim.proposals", name: t("models.proposal.name", scope: "decidim.proposals.admin")), new_proposal_path, class: 'new' if can? :manage, current_feature %>
5
- </div>
3
+ <% if feature_settings.official_proposals_enabled %>
4
+ <div class="actions title">
5
+ <%= link_to t("actions.new", scope: "decidim.proposals", name: t("models.proposal.name", scope: "decidim.proposals.admin")), new_proposal_path, class: 'new' if can? :manage, current_feature %>
6
+ </div>
7
+ <% end %>
6
8
 
7
9
  <table class="stack">
8
10
  <thead>
9
11
  <tr>
10
12
  <th><%= t("models.proposal.fields.title", scope: "decidim.proposals") %></th>
11
13
  <th><%= t("models.proposal.fields.category", scope: "decidim.proposals") %></th>
12
- <th><%= t("models.proposal.fields.scope", scope: "decidim.proposals") %></th>
14
+ <% if feature_settings.scoped_proposals_enabled %>
15
+ <th><%= t("models.proposal.fields.scope", scope: "decidim.proposals") %></th>
16
+ <% end %>
13
17
  <th><%= t("models.proposal.fields.state", scope: "decidim.proposals") %></th>
14
18
  <th class="actions"><%= t("actions.title", scope: "decidim.proposals") %></th>
15
19
  </tr>
@@ -25,16 +29,18 @@
25
29
  <%= translated_attribute proposal.category.name %>
26
30
  <% end %>
27
31
  </td>
28
- <td>
29
- <% if proposal.scope %>
30
- <%= translated_attribute proposal.scope.name %>
31
- <% end %>
32
- </td>
32
+ <% if feature_settings.scoped_proposals_enabled %>
33
+ <td>
34
+ <% if proposal.scope %>
35
+ <%= translated_attribute proposal.scope.name %>
36
+ <% end %>
37
+ </td>
38
+ <% end %>
33
39
  <td>
34
40
  <%= humanize_proposal_state proposal.state %>
35
41
  </td>
36
42
  <td class="actions">
37
- <%= link_to t("actions.answer", scope: "decidim.proposals"), edit_proposal_proposal_answer_path(proposal_id: proposal.id, id: proposal.id) if can? :update, current_feature %>
43
+ <%= link_to t("actions.answer", scope: "decidim.proposals"), edit_proposal_proposal_answer_path(proposal_id: proposal.id, id: proposal.id) if can? :update, proposal %>
38
44
  </td>
39
45
  </tr>
40
46
  <% end %>