decidim-proposals 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +2 -0
- data/app/controllers/decidim/proposals/admin/proposals_controller.rb +2 -0
- data/app/controllers/decidim/proposals/proposals_controller.rb +42 -5
- data/app/forms/decidim/proposals/proposal_form.rb +3 -1
- data/app/helpers/decidim/proposals/proposal_votes_helper.rb +1 -1
- data/app/models/decidim/proposals/abilities/admin_user.rb +45 -0
- data/app/models/decidim/proposals/abilities/current_user.rb +22 -4
- data/app/models/decidim/proposals/abilities/process_admin_user.rb +57 -0
- data/app/models/decidim/proposals/proposal.rb +23 -2
- data/app/services/decidim/proposals/proposal_search.rb +25 -13
- data/app/views/decidim/proposals/admin/proposal_answers/edit.html.erb +1 -1
- data/app/views/decidim/proposals/admin/proposals/_form.html.erb +1 -1
- data/app/views/decidim/proposals/admin/proposals/index.html.erb +16 -10
- data/app/views/decidim/proposals/admin/proposals/new.html.erb +1 -1
- data/app/views/decidim/proposals/proposal_votes/update_buttons_and_counters.js.erb +4 -4
- data/app/views/decidim/proposals/proposals/_filters.html.erb +15 -3
- data/app/views/decidim/proposals/proposals/_proposal.html.erb +3 -11
- data/app/views/decidim/proposals/proposals/_proposals.html.erb +2 -1
- data/app/views/decidim/proposals/proposals/_remaining_votes_count.html.erb +3 -1
- data/app/views/decidim/proposals/proposals/_tags.html.erb +10 -0
- data/app/views/decidim/proposals/proposals/_vote_button.html.erb +23 -21
- data/app/views/decidim/proposals/proposals/_votes_count.html.erb +9 -6
- data/app/views/decidim/proposals/proposals/_votes_limit.html.erb +1 -3
- data/app/views/decidim/proposals/proposals/index.html.erb +3 -3
- data/app/views/decidim/proposals/proposals/index.js.erb +6 -1
- data/app/views/decidim/proposals/proposals/new.html.erb +2 -2
- data/app/views/decidim/proposals/proposals/show.html.erb +23 -28
- data/config/i18n-tasks.yml +2 -0
- data/config/locales/ca.yml +30 -15
- data/config/locales/en.yml +19 -4
- data/config/locales/es.yml +19 -4
- data/config/locales/eu.yml +5 -0
- data/db/migrate/20170205082832_add_index_to_decidim_proposals_proposals_proposal_votes_count.rb +7 -0
- data/lib/decidim/proposals/admin_engine.rb +7 -0
- data/lib/decidim/proposals/feature.rb +16 -3
- data/lib/decidim/proposals/test/factories.rb +1 -1
- metadata +19 -15
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fa61b404c05485715f4617683618718c79f9a92
|
4
|
+
data.tar.gz: 3afb55cb4acae13ff3f0a63cd74432eb67c8cc6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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(
|
21
|
-
.
|
22
|
-
|
22
|
+
.includes(:category)
|
23
|
+
.includes(:scope)
|
24
|
+
|
25
|
+
@proposals = @proposals.page(params[:page]).per(12)
|
26
|
+
@proposals = reorder(@proposals)
|
23
27
|
|
24
|
-
@
|
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
|
-
|
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
|
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:
|
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.
|
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
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
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
|
-
<%=
|
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
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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,
|
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 %>
|