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.
- checksums.yaml +5 -5
- data/README.md +1 -1
- data/app/assets/config/decidim_proposals_manifest.css +3 -1
- data/app/assets/javascripts/decidim/proposals/add_proposal.js.es6 +0 -2
- data/app/assets/stylesheets/decidim/proposals/social_share.css.scss +8 -5
- data/app/commands/decidim/proposals/admin/answer_proposal.rb +32 -0
- data/app/commands/decidim/proposals/admin/create_proposal_note.rb +47 -0
- data/app/commands/decidim/proposals/create_proposal.rb +12 -0
- data/app/commands/decidim/proposals/withdraw_proposal.rb +37 -0
- data/app/controllers/decidim/proposals/admin/proposal_notes_controller.rb +40 -0
- data/app/controllers/decidim/proposals/admin/proposals_controller.rb +6 -2
- data/app/controllers/decidim/proposals/proposals_controller.rb +20 -5
- data/app/events/decidim/proposals/accepted_proposal_event.rb +9 -0
- data/app/events/decidim/proposals/create_proposal_event.rb +9 -0
- data/app/events/decidim/proposals/evaluating_proposal_event.rb +8 -0
- data/app/events/decidim/proposals/rejected_proposal_event.rb +9 -0
- data/app/forms/decidim/proposals/admin/proposal_form.rb +12 -12
- data/app/forms/decidim/proposals/admin/proposal_note_form.rb +16 -0
- data/app/forms/decidim/proposals/proposal_form.rb +11 -11
- data/app/helpers/decidim/proposals/application_helper.rb +2 -0
- data/app/models/decidim/proposals/abilities/admin_ability.rb +1 -0
- data/app/models/decidim/proposals/abilities/current_user_ability.rb +8 -0
- data/app/models/decidim/proposals/abilities/participatory_process_admin_ability.rb +1 -0
- data/app/models/decidim/proposals/proposal.rb +30 -9
- data/app/models/decidim/proposals/proposal_note.rb +13 -0
- data/app/presenters/decidim/proposals/official_author_presenter.rb +34 -0
- data/app/presenters/decidim/proposals/proposal_presenter.rb +20 -0
- data/app/services/decidim/proposals/proposal_search.rb +4 -2
- data/app/views/decidim/proposals/admin/proposal_answers/edit.html.erb +1 -0
- data/app/views/decidim/proposals/admin/proposal_notes/_form.html.erb +8 -0
- data/app/views/decidim/proposals/admin/proposal_notes/_proposal_notes.html.erb +45 -0
- data/app/views/decidim/proposals/admin/proposal_notes/index.html.erb +3 -0
- data/app/views/decidim/proposals/admin/proposals/_form.html.erb +1 -1
- data/app/views/decidim/proposals/admin/proposals/index.html.erb +60 -8
- data/app/views/decidim/proposals/admin/shared/_info_proposal.html.erb +20 -0
- data/app/views/decidim/proposals/proposals/_filters.html.erb +1 -1
- data/app/views/decidim/proposals/proposals/_linked_proposals.html.erb +11 -8
- data/app/views/decidim/proposals/proposals/_proposal.html.erb +5 -2
- data/app/views/decidim/proposals/proposals/_proposal_badge.html.erb +3 -0
- data/app/views/decidim/proposals/proposals/_vote_button.html.erb +3 -3
- data/app/views/decidim/proposals/proposals/edit.html.erb +2 -2
- data/app/views/decidim/proposals/proposals/index.html.erb +6 -2
- data/app/views/decidim/proposals/proposals/new.html.erb +2 -2
- data/app/views/decidim/proposals/proposals/show.html.erb +8 -4
- data/config/locales/ca.yml +46 -7
- data/config/locales/en.yml +46 -6
- data/config/locales/es.yml +46 -7
- data/config/locales/eu.yml +46 -7
- data/config/locales/fi.yml +58 -19
- data/config/locales/fr.yml +53 -14
- data/config/locales/gl.yml +231 -0
- data/config/locales/it.yml +46 -7
- data/config/locales/nl.yml +46 -7
- data/config/locales/pl.yml +46 -7
- data/config/locales/pt-BR.yml +231 -0
- data/config/locales/pt.yml +48 -9
- data/config/locales/ru.yml +7 -7
- data/config/locales/sv.yml +231 -0
- data/config/locales/uk.yml +7 -7
- data/db/migrate/20170215132030_add_reference_to_proposals.rb +5 -1
- data/db/migrate/20180111110204_create_decidim_proposal_notes.rb +15 -0
- data/db/migrate/20180115155220_add_index_created_at_proposal_notes.rb +7 -0
- data/lib/decidim/proposals/admin_engine.rb +1 -0
- data/lib/decidim/proposals/engine.rb +4 -0
- data/lib/decidim/proposals/feature.rb +21 -6
- data/lib/decidim/proposals/test/factories.rb +10 -0
- data/lib/decidim/proposals/version.rb +1 -1
- metadata +60 -27
- data/app/views/decidim/proposals/proposals/_author.html.erb +0 -23
@@ -24,6 +24,7 @@ module Decidim
|
|
24
24
|
validates :address, presence: true, if: ->(form) { form.has_address? }
|
25
25
|
validates :category, presence: true, if: ->(form) { form.category_id.present? }
|
26
26
|
validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
|
27
|
+
validate { errors.add(:scope_id, :invalid) if current_participatory_space&.scope && !current_participatory_space&.scope&.ancestor_of?(scope) }
|
27
28
|
|
28
29
|
delegate :categories, to: :current_feature
|
29
30
|
|
@@ -33,28 +34,27 @@ module Decidim
|
|
33
34
|
self.category_id = model.categorization.decidim_category_id
|
34
35
|
end
|
35
36
|
|
36
|
-
def organization_scopes
|
37
|
-
current_organization.scopes
|
38
|
-
end
|
39
|
-
|
40
|
-
def process_scope
|
41
|
-
current_feature.participatory_space.scope
|
42
|
-
end
|
43
|
-
|
44
37
|
alias feature current_feature
|
45
38
|
|
46
39
|
# Finds the Category from the category_id.
|
47
40
|
#
|
48
41
|
# Returns a Decidim::Category
|
49
42
|
def category
|
50
|
-
@category ||= categories.
|
43
|
+
@category ||= categories.find_by(id: category_id)
|
51
44
|
end
|
52
45
|
|
53
|
-
# Finds the Scope from the
|
46
|
+
# Finds the Scope from the given decidim_scope_id, uses participatory space scope if missing.
|
54
47
|
#
|
55
48
|
# Returns a Decidim::Scope
|
56
49
|
def scope
|
57
|
-
@scope ||=
|
50
|
+
@scope ||= @scope_id ? current_feature.scopes.find_by(id: @scope_id) : current_participatory_space&.scope
|
51
|
+
end
|
52
|
+
|
53
|
+
# Scope identifier
|
54
|
+
#
|
55
|
+
# Returns the scope identifier related to the proposal
|
56
|
+
def scope_id
|
57
|
+
@scope_id || scope&.id
|
58
58
|
end
|
59
59
|
|
60
60
|
def has_address?
|
@@ -29,6 +29,10 @@ module Decidim
|
|
29
29
|
proposal.editable_by?(user)
|
30
30
|
end
|
31
31
|
|
32
|
+
can :withdraw, Proposal do |proposal|
|
33
|
+
can_withdraw?(proposal)
|
34
|
+
end
|
35
|
+
|
32
36
|
can :report, Proposal
|
33
37
|
end
|
34
38
|
|
@@ -77,6 +81,10 @@ module Decidim
|
|
77
81
|
|
78
82
|
feature
|
79
83
|
end
|
84
|
+
|
85
|
+
def can_withdraw?(proposal)
|
86
|
+
proposal.decidim_author_id == @user.id
|
87
|
+
end
|
80
88
|
end
|
81
89
|
end
|
82
90
|
end
|
@@ -18,6 +18,7 @@ module Decidim
|
|
18
18
|
feature_manifest_name "proposals"
|
19
19
|
|
20
20
|
has_many :votes, foreign_key: "decidim_proposal_id", class_name: "ProposalVote", dependent: :destroy, counter_cache: "proposal_votes_count"
|
21
|
+
has_many :notes, foreign_key: "decidim_proposal_id", class_name: "ProposalNote", dependent: :destroy, counter_cache: "proposal_notes_count"
|
21
22
|
|
22
23
|
validates :title, :body, presence: true
|
23
24
|
|
@@ -26,6 +27,8 @@ module Decidim
|
|
26
27
|
scope :accepted, -> { where(state: "accepted") }
|
27
28
|
scope :rejected, -> { where(state: "rejected") }
|
28
29
|
scope :evaluating, -> { where(state: "evaluating") }
|
30
|
+
scope :withdrawn, -> { where(state: "withdrawn") }
|
31
|
+
scope :except_withdrawn, -> { where.not(state: "withdrawn").or(where(state: nil)) }
|
29
32
|
|
30
33
|
def self.order_randomly(seed)
|
31
34
|
transaction do
|
@@ -34,15 +37,6 @@ module Decidim
|
|
34
37
|
end
|
35
38
|
end
|
36
39
|
|
37
|
-
def author_name
|
38
|
-
return I18n.t("decidim.proposals.models.proposal.fields.official_proposal") if official?
|
39
|
-
user_group&.name || author.name
|
40
|
-
end
|
41
|
-
|
42
|
-
def author_avatar_url
|
43
|
-
author&.avatar&.url || ActionController::Base.helpers.asset_path("decidim/default-avatar.svg")
|
44
|
-
end
|
45
|
-
|
46
40
|
# Public: Check if the user has voted the proposal.
|
47
41
|
#
|
48
42
|
# Returns Boolean.
|
@@ -78,6 +72,13 @@ module Decidim
|
|
78
72
|
answered? && state == "evaluating"
|
79
73
|
end
|
80
74
|
|
75
|
+
# Public: Checks if the author has withdrawn the proposal.
|
76
|
+
#
|
77
|
+
# Returns Boolean.
|
78
|
+
def withdrawn?
|
79
|
+
state == "withdrawn"
|
80
|
+
end
|
81
|
+
|
81
82
|
# Public: Overrides the `commentable?` Commentable concern method.
|
82
83
|
def commentable?
|
83
84
|
feature.settings.comments_enabled?
|
@@ -148,6 +149,26 @@ module Decidim
|
|
148
149
|
authored_by?(user) && !answered? && within_edit_time_limit?
|
149
150
|
end
|
150
151
|
|
152
|
+
# Checks whether the user can withdraw the given proposal.
|
153
|
+
#
|
154
|
+
# user - the user to check for withdrawability.
|
155
|
+
def withdrawable_by?(user)
|
156
|
+
user && !withdrawn? && authored_by?(user)
|
157
|
+
end
|
158
|
+
|
159
|
+
# method for sort_link by number of comments
|
160
|
+
ransacker :commentable_comments_count do
|
161
|
+
query = <<-SQL
|
162
|
+
(SELECT COUNT(decidim_comments_comments.id)
|
163
|
+
FROM decidim_comments_comments
|
164
|
+
WHERE decidim_comments_comments.decidim_commentable_id = decidim_proposals_proposals.id
|
165
|
+
AND decidim_comments_comments.decidim_commentable_type = 'Decidim::Proposals::Proposal'
|
166
|
+
GROUP BY decidim_comments_comments.decidim_commentable_id
|
167
|
+
)
|
168
|
+
SQL
|
169
|
+
Arel.sql(query)
|
170
|
+
end
|
171
|
+
|
151
172
|
private
|
152
173
|
|
153
174
|
# Checks whether the proposal is inside the time window to be editable or not.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
# A proposal can include a notes created by admins.
|
6
|
+
class ProposalNote < ApplicationRecord
|
7
|
+
belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal", counter_cache: true
|
8
|
+
belongs_to :author, foreign_key: "decidim_author_id", class_name: "Decidim::User"
|
9
|
+
|
10
|
+
default_scope { order(created_at: :asc) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
#
|
6
|
+
# A dummy presenter to abstract out the author of an official proposal.
|
7
|
+
#
|
8
|
+
class OfficialAuthorPresenter
|
9
|
+
def name
|
10
|
+
I18n.t("decidim.proposals.models.proposal.fields.official_proposal")
|
11
|
+
end
|
12
|
+
|
13
|
+
def nickname
|
14
|
+
""
|
15
|
+
end
|
16
|
+
|
17
|
+
def badge
|
18
|
+
""
|
19
|
+
end
|
20
|
+
|
21
|
+
def profile_path
|
22
|
+
""
|
23
|
+
end
|
24
|
+
|
25
|
+
def avatar_url
|
26
|
+
ActionController::Base.helpers.asset_path("decidim/default-avatar.svg")
|
27
|
+
end
|
28
|
+
|
29
|
+
def deleted?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
#
|
6
|
+
# Decorator for proposals
|
7
|
+
#
|
8
|
+
class ProposalPresenter < SimpleDelegator
|
9
|
+
def author
|
10
|
+
@author ||= if official?
|
11
|
+
Decidim::Proposals::OfficialAuthorPresenter.new
|
12
|
+
elsif user_group
|
13
|
+
Decidim::UserGroupPresenter.new(user_group)
|
14
|
+
else
|
15
|
+
Decidim::UserPresenter.new(super)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<%= decidim_form_for(@form, url: proposal_proposal_notes_path(proposal, @form), html: { class: "form new_proposal_note" }) do |f| %>
|
2
|
+
<div class="row column">
|
3
|
+
<%= f.text_area :body, rows: 10, label: t('.note') %>
|
4
|
+
</div>
|
5
|
+
<div class="button--double form-general-submit">
|
6
|
+
<%= f.submit t(".submit") %>
|
7
|
+
</div>
|
8
|
+
<% end %>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<section class="comments">
|
2
|
+
<div class="card">
|
3
|
+
<div class="card-divider">
|
4
|
+
<h2 class="card-title"><%= t("title", scope: 'decidim.proposals.admin.proposal_notes') %></h2>
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<div class="card-section">
|
8
|
+
<div class="comment-thread">
|
9
|
+
<% proposal.notes.each do |note| %>
|
10
|
+
<div class="card">
|
11
|
+
<div class="card-divider">
|
12
|
+
<article class="comment">
|
13
|
+
<div class="comment__header">
|
14
|
+
<div class="author-data">
|
15
|
+
<div class="author-data__main">
|
16
|
+
<div class="author author--inline">
|
17
|
+
<strong><span class="author__name"><%= note.author.name %></span></strong>
|
18
|
+
<span><%= l note.created_at, format: :decidim_short %></span>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
<div class="comment__content">
|
24
|
+
<%= note.body %>
|
25
|
+
</div>
|
26
|
+
</article>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
<% end %>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
<div class="card-section">
|
33
|
+
<div class="add-comment">
|
34
|
+
<div class="card">
|
35
|
+
<div class="card-divider">
|
36
|
+
<h2 class="card-title"><%= t("leave_your_note", scope: 'decidim.proposals.admin.proposal_notes') %></h2>
|
37
|
+
</div>
|
38
|
+
<div class="card-section">
|
39
|
+
<%= render 'form' %>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
</section>
|
@@ -26,7 +26,7 @@
|
|
26
26
|
|
27
27
|
<% if current_participatory_space.has_subscopes? %>
|
28
28
|
<div class="row column">
|
29
|
-
<%= form
|
29
|
+
<%= scopes_picker_field form, :scope_id %>
|
30
30
|
</div>
|
31
31
|
<% end %>
|
32
32
|
|
@@ -19,19 +19,48 @@
|
|
19
19
|
<table class="table-list">
|
20
20
|
<thead>
|
21
21
|
<tr>
|
22
|
-
<th
|
23
|
-
|
24
|
-
|
22
|
+
<th>
|
23
|
+
<%= sort_link(query, :id, t("models.proposal.fields.id", scope: "decidim.proposals"), default_order: :desc ) %>
|
24
|
+
</th>
|
25
|
+
<th>
|
26
|
+
<%= sort_link(query, :title, t("models.proposal.fields.title", scope: "decidim.proposals") ) %>
|
27
|
+
</th>
|
28
|
+
<th>
|
29
|
+
<%= sort_link(query, :category_name, t("models.proposal.fields.category", scope: "decidim.proposals") ) %>
|
30
|
+
</th>
|
31
|
+
|
25
32
|
<% if scopes_enabled?(current_participatory_space) %>
|
26
|
-
<th
|
33
|
+
<th>
|
34
|
+
<%= sort_link(query, :scope_name, t("models.proposal.fields.scope", scope: "decidim.proposals") ) %>
|
35
|
+
</th>
|
27
36
|
<% end %>
|
28
37
|
|
29
|
-
<th
|
38
|
+
<th>
|
39
|
+
<%= sort_link(query, :state, t("models.proposal.fields.state", scope: "decidim.proposals") ) %>
|
40
|
+
</th>
|
30
41
|
|
31
42
|
<% if current_settings.votes_enabled? %>
|
32
|
-
<th
|
43
|
+
<th>
|
44
|
+
<%= sort_link(query, :proposal_votes_count, t("models.proposal.fields.votes", scope: "decidim.proposals") ) %>
|
45
|
+
</th>
|
46
|
+
<% end %>
|
47
|
+
|
48
|
+
<% if current_feature.settings.comments_enabled? and !current_settings.comments_blocked? %>
|
49
|
+
<th>
|
50
|
+
<%= sort_link(query, :commentable_comments_count, t("models.proposal.fields.comments", scope: "decidim.proposals") ) %>
|
51
|
+
</th>
|
33
52
|
<% end %>
|
34
53
|
|
54
|
+
<% if can? :create, Decidim::Proposals::ProposalNote %>
|
55
|
+
<th>
|
56
|
+
<%= sort_link(query, :proposal_notes_count, t("models.proposal.fields.notes", scope: "decidim.proposals") ) %>
|
57
|
+
</th>
|
58
|
+
<% end %>
|
59
|
+
|
60
|
+
<th>
|
61
|
+
<%= sort_link(query, :created_at, t("models.proposal.fields.created_at", scope: "decidim.proposals") ) %>
|
62
|
+
</th>
|
63
|
+
|
35
64
|
<th class="actions"><%= t("actions.title", scope: "decidim.proposals") %></th>
|
36
65
|
</tr>
|
37
66
|
</thead>
|
@@ -63,14 +92,37 @@
|
|
63
92
|
<%= humanize_proposal_state proposal.state %>
|
64
93
|
</strong>
|
65
94
|
</td>
|
95
|
+
|
66
96
|
<% if current_settings.votes_enabled? %>
|
67
97
|
<td>
|
68
|
-
<%= proposal.
|
98
|
+
<%= proposal.proposal_votes_count %>
|
99
|
+
</td>
|
100
|
+
<% end %>
|
101
|
+
|
102
|
+
<% if current_feature.settings.comments_enabled? and !current_settings.comments_blocked? %>
|
103
|
+
<td>
|
104
|
+
<%= proposal.comments.count %>
|
69
105
|
</td>
|
70
106
|
<% end %>
|
107
|
+
|
108
|
+
<% if can? :create, Decidim::Proposals::ProposalNote %>
|
109
|
+
<td>
|
110
|
+
<%= proposal.proposal_notes_count %>
|
111
|
+
</td>
|
112
|
+
<% end %>
|
113
|
+
|
114
|
+
<td>
|
115
|
+
<%= l proposal.created_at, format: :decidim_short %>
|
116
|
+
</td>
|
117
|
+
|
71
118
|
<td class="table-list__actions">
|
119
|
+
|
120
|
+
<% if can? :create, Decidim::Proposals::ProposalNote %>
|
121
|
+
<%= icon_link_to "chat", proposal_proposal_notes_path(proposal_id: proposal.id), t("actions.private_notes", scope: "decidim.proposals"), class: "action-icon--index-notes" %>
|
122
|
+
<% end %>
|
123
|
+
|
72
124
|
<% if can? :update, proposal %>
|
73
|
-
<%= icon_link_to "
|
125
|
+
<%= icon_link_to "comment-square", edit_proposal_proposal_answer_path(proposal_id: proposal.id, id: proposal.id), t("actions.answer", scope: "decidim.proposals"), class: "action-icon--edit-answer" %>
|
74
126
|
<% end %>
|
75
127
|
<%= icon_link_to "eye", resource_locator(proposal).path, t("actions.preview", scope: "decidim.proposals.admin"), class: "action-icon--preview", target: :blank %>
|
76
128
|
</td>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<div class="card">
|
2
|
+
<div class="card-divider">
|
3
|
+
<h2 class="card-title">
|
4
|
+
<%= link_to "#{t ".proposals"} > ", proposals_path %>
|
5
|
+
<%= proposal.title %>
|
6
|
+
</h2>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div class="card-section">
|
10
|
+
<div class="row column">
|
11
|
+
<strong><%= t ".body" %>: </strong><%= proposal.body %>
|
12
|
+
</div>
|
13
|
+
<div class="row column">
|
14
|
+
<strong><%= t ".created_at" %>: </strong> <%= l proposal.created_at, format: :decidim_short %>
|
15
|
+
</div>
|
16
|
+
<div class="row column">
|
17
|
+
<strong><%= t ".proposal_votes_count" %>: </strong> <%= proposal.proposal_votes_count %>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
</div>
|