decidim-proposals 0.12.2 → 0.13.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/proposals/coauthorships_cell.rb +40 -0
  3. data/app/cells/decidim/proposals/proposal_m_cell.rb +5 -0
  4. data/app/commands/decidim/proposals/admin/import_proposals.rb +3 -0
  5. data/app/commands/decidim/proposals/admin/update_proposal_category.rb +2 -2
  6. data/app/commands/decidim/proposals/create_proposal.rb +3 -4
  7. data/app/commands/decidim/proposals/destroy_proposal.rb +1 -1
  8. data/app/commands/decidim/proposals/publish_proposal.rb +12 -4
  9. data/app/commands/decidim/proposals/update_proposal.rb +4 -4
  10. data/app/controllers/decidim/proposals/proposals_controller.rb +1 -2
  11. data/app/events/decidim/proposals/publish_proposal_event.rb +1 -1
  12. data/app/forms/decidim/proposals/proposal_form.rb +1 -1
  13. data/app/helpers/decidim/proposals/application_helper.rb +4 -0
  14. data/app/helpers/decidim/proposals/proposal_endorsements_helper.rb +16 -10
  15. data/app/jobs/decidim/proposals/notify_proposals_mentioned_job.rb +2 -2
  16. data/app/models/decidim/proposals/proposal.rb +19 -2
  17. data/app/permissions/decidim/proposals/permissions.rb +1 -1
  18. data/app/presenters/decidim/proposals/proposal_presenter.rb +6 -3
  19. data/app/services/decidim/proposals/proposal_search.rb +2 -2
  20. data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +1 -1
  21. data/app/views/decidim/proposals/admin/proposals/index.html.erb +1 -1
  22. data/app/views/decidim/proposals/proposal_endorsements/update_buttons_and_counters.js.erb +1 -1
  23. data/app/views/decidim/proposals/proposals/_endorsements_card_row.html.erb +1 -1
  24. data/app/views/decidim/proposals/proposals/_proposal_similar.html.erb +1 -1
  25. data/app/views/decidim/proposals/proposals/show.html.erb +1 -1
  26. data/config/locales/ca.yml +6 -6
  27. data/config/locales/en.yml +6 -6
  28. data/config/locales/es-PY.yml +6 -6
  29. data/config/locales/es.yml +6 -6
  30. data/config/locales/eu.yml +6 -6
  31. data/config/locales/fi.yml +6 -6
  32. data/config/locales/fr.yml +76 -76
  33. data/config/locales/gl.yml +6 -6
  34. data/config/locales/it.yml +6 -6
  35. data/config/locales/nl.yml +5 -5
  36. data/config/locales/pl.yml +6 -6
  37. data/config/locales/pt-BR.yml +6 -6
  38. data/config/locales/pt.yml +6 -6
  39. data/config/locales/ru.yml +1 -2
  40. data/config/locales/sv.yml +6 -6
  41. data/config/locales/uk.yml +3 -3
  42. data/db/migrate/20180529101323_add_counter_cache_coauthorships_to_proposals.rb +8 -0
  43. data/db/migrate/20180529110230_move_authorships_to_coauthorships.rb +28 -0
  44. data/db/migrate/20180529110830_remove_authorships_from_proposals.rb +8 -0
  45. data/lib/decidim/proposals/component.rb +3 -2
  46. data/lib/decidim/proposals/proposal_serializer.rb +1 -1
  47. data/lib/decidim/proposals/test/factories.rb +17 -3
  48. data/lib/decidim/proposals/version.rb +1 -1
  49. metadata +40 -37
  50. data/app/views/decidim/proposals/proposals/_endorsements_count.html.erb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff102e880b0f9c12cbf9aabbb8ae01fecfb4554371386ae32c1d782775a63830
4
- data.tar.gz: 383e5a0b5d15ffea8200e300b78c37cdb8f5f169a12ab1852bc38e0c452e9bdf
3
+ metadata.gz: 2609298045f639a58aa4261912edf5bed42edd6f5f6565af65d277c829c3986a
4
+ data.tar.gz: add5696156559e7a28bb893977788628459f788c132f6c8b4ea2856c726978d5
5
5
  SHA512:
6
- metadata.gz: 9b5576522d8ec8815d3ab711d70ed9aba96d37eab93e14eeeccc9af5ce76870fd87570721361ef2ae5529893e8139921269034bdbc1e8a3b2e06f96a1047100d
7
- data.tar.gz: dc62e6eb64f171108d9082a0f3ec79828db38d63be1fbff5563a2d9421b1df6997f97247fedeff65fa6fd3591151ad239e8014f0b932918a9262701b57c7f494
6
+ metadata.gz: 80c6b74ae952eefbb30a6abe00d13a37d50b8143fd00d0ed9b9b4f9d8dfcadb8a360852bc2a616e41bb27e79d172e18bf8333fca462e2345e0cd93eccd8ca977
7
+ data.tar.gz: de47a3b699f8c2e3e549e60f330e0cacf79e6ebf8bd47cc2d37013b4c9d436b0ce3751250948ac64ae29be17334e36485291767884e955d97909f8b5b66a5ddf
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # This cell renders a collapsible list of the proposal coauthors.
6
+ #
7
+ # Available sizes:
8
+ # - `:small` => collapses after 3 elements.
9
+ # - `:default` => collapses after 7 elements. If not specified, this one is
10
+ # used.
11
+ #
12
+ # Example:
13
+ #
14
+ # cell("decidim/proposals/coauthorships", @proposal)
15
+ class CoauthorshipsCell < Decidim::ViewModel
16
+ include Decidim::ApplicationHelper
17
+
18
+ def show
19
+ if model.official?
20
+ cell "decidim/author", present(model).author, from: model
21
+ else
22
+ cell(
23
+ "decidim/collapsible_authors",
24
+ authors_for(model),
25
+ cell_name: "decidim/author",
26
+ cell_options: { extra_classes: ["author-data--small"] },
27
+ size: :small,
28
+ from: model
29
+ )
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def authors_for(coauthorable)
36
+ coauthorable.identities.map { |identity| present(identity) }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -36,10 +36,15 @@ module Decidim
36
36
  end
37
37
 
38
38
  def statuses
39
+ return [:endorsements_count, :comments_count] if model.draft?
39
40
  return [:creation_date, :endorsements_count, :comments_count] unless has_link_to_resource?
40
41
  [:creation_date, :follow, :endorsements_count, :comments_count]
41
42
  end
42
43
 
44
+ def creation_date_status
45
+ l(model.published_at.to_date, format: :decidim_short)
46
+ end
47
+
43
48
  def endorsements_count_status
44
49
  return endorsements_count unless has_link_to_resource?
45
50
 
@@ -52,6 +52,9 @@ module Decidim
52
52
  proposal.save!
53
53
 
54
54
  proposal.link_resources([original_proposal], "copied_from_component")
55
+ original_proposal.coauthorships.each do |coauthorship|
56
+ Decidim::Coauthorship.create(author: coauthorship.author, user_group: coauthorship.user_group, coauthorable: proposal)
57
+ end
55
58
  end.compact
56
59
  end
57
60
 
@@ -37,7 +37,7 @@ module Decidim
37
37
  else
38
38
  transaction do
39
39
  update_proposal_category proposal
40
- notify_author proposal if proposal.author.present?
40
+ notify_author proposal if proposal.coauthorships.any?
41
41
  end
42
42
  @response[:successful] << proposal.title
43
43
  end
@@ -59,7 +59,7 @@ module Decidim
59
59
  event: "decidim.events.proposals.proposal_update_category",
60
60
  event_class: Decidim::Proposals::Admin::UpdateProposalCategoryEvent,
61
61
  resource: proposal,
62
- recipient_ids: [proposal.decidim_author_id]
62
+ recipient_ids: proposal.coauthorships.pluck(:decidim_author_id)
63
63
  )
64
64
  end
65
65
  end
@@ -50,13 +50,12 @@ module Decidim
50
50
  body: form.body,
51
51
  category: form.category,
52
52
  scope: form.scope,
53
- author: @current_user,
54
- decidim_user_group_id: form.user_group_id,
55
53
  component: form.component,
56
54
  address: form.address,
57
55
  latitude: form.latitude,
58
56
  longitude: form.longitude
59
57
  )
58
+ proposal.add_coauthor(@current_user, decidim_user_group_id: form.user_group_id)
60
59
  end
61
60
 
62
61
  def build_attachment
@@ -112,11 +111,11 @@ module Decidim
112
111
  end
113
112
 
114
113
  def current_user_proposals
115
- Proposal.where(author: @current_user, component: form.current_component).except_withdrawn
114
+ Proposal.from_author(@current_user).where(component: form.current_component).except_withdrawn
116
115
  end
117
116
 
118
117
  def user_group_proposals
119
- Proposal.where(user_group: @user_group, component: form.current_component).except_withdrawn
118
+ Proposal.from_user_group(@user_group).where(component: form.current_component).except_withdrawn
120
119
  end
121
120
  end
122
121
  end
@@ -22,7 +22,7 @@ module Decidim
22
22
  # Returns nothing.
23
23
  def call
24
24
  return broadcast(:invalid) unless @proposal.draft?
25
- return broadcast(:invalid) if @proposal.author != @current_user
25
+ return broadcast(:invalid) unless @proposal.authored_by?(@current_user)
26
26
 
27
27
  @proposal.destroy!
28
28
 
@@ -20,7 +20,7 @@ module Decidim
20
20
  #
21
21
  # Returns nothing.
22
22
  def call
23
- return broadcast(:invalid) if @proposal.author != @current_user
23
+ return broadcast(:invalid) unless @proposal.authored_by?(@current_user)
24
24
 
25
25
  transaction do
26
26
  @proposal.update published_at: Time.current
@@ -34,13 +34,13 @@ module Decidim
34
34
  private
35
35
 
36
36
  def send_notification
37
- return if @proposal.author.blank?
37
+ return if @proposal.coauthorships.empty?
38
38
 
39
39
  Decidim::EventsManager.publish(
40
40
  event: "decidim.events.proposals.proposal_published",
41
41
  event_class: Decidim::Proposals::PublishProposalEvent,
42
42
  resource: @proposal,
43
- recipient_ids: @proposal.author.followers.pluck(:id)
43
+ recipient_ids: coauthors_followers(@proposal)
44
44
  )
45
45
  end
46
46
 
@@ -49,12 +49,20 @@ module Decidim
49
49
  event: "decidim.events.proposals.proposal_published",
50
50
  event_class: Decidim::Proposals::PublishProposalEvent,
51
51
  resource: @proposal,
52
- recipient_ids: @proposal.participatory_space.followers.pluck(:id) - @proposal.author.followers.pluck(:id),
52
+ recipient_ids: @proposal.participatory_space.followers.pluck(:id) - coauthors_followers(@proposal),
53
53
  extra: {
54
54
  participatory_space: true
55
55
  }
56
56
  )
57
57
  end
58
+
59
+ def coauthors_followers(_proposal)
60
+ followers_ids = []
61
+ @proposal.authors.each do |author|
62
+ followers_ids += author.followers.pluck(:id)
63
+ end
64
+ followers_ids
65
+ end
58
66
  end
59
67
  end
60
68
  end
@@ -43,12 +43,12 @@ module Decidim
43
43
  body: form.body,
44
44
  category: form.category,
45
45
  scope: form.scope,
46
- author: current_user,
47
- decidim_user_group_id: user_group.try(:id),
48
46
  address: form.address,
49
47
  latitude: form.latitude,
50
48
  longitude: form.longitude
51
49
  )
50
+ @proposal.coauthorships.clear
51
+ @proposal.add_coauthor(current_user, decidim_user_group_id: user_group&.id)
52
52
  end
53
53
 
54
54
  def proposal_limit_reached?
@@ -72,11 +72,11 @@ module Decidim
72
72
  end
73
73
 
74
74
  def current_user_proposals
75
- Proposal.where(author: current_user, component: form.current_component).published.where.not(id: proposal.id)
75
+ Proposal.from_author(current_user).where(component: form.current_component).published.where.not(id: proposal.id)
76
76
  end
77
77
 
78
78
  def user_group_proposals
79
- Proposal.where(user_group: user_group, component: form.current_component).published.where.not(id: proposal.id)
79
+ Proposal.from_user_group(user_group).where(component: form.current_component).published.where.not(id: proposal.id)
80
80
  end
81
81
  end
82
82
  end
@@ -20,7 +20,6 @@ module Decidim
20
20
  .results
21
21
  .published
22
22
  .not_hidden
23
- .includes(:author)
24
23
  .includes(:category)
25
24
  .includes(:scope)
26
25
 
@@ -222,7 +221,7 @@ module Decidim
222
221
  end
223
222
 
224
223
  def proposal_draft
225
- Proposal.not_hidden.where(component: current_component, author: current_user).find_by(published_at: nil)
224
+ Proposal.from_all_user_identities(current_user).not_hidden.where(component: current_component).find_by(published_at: nil)
226
225
  end
227
226
 
228
227
  def ensure_is_draft
@@ -3,7 +3,7 @@
3
3
  module Decidim
4
4
  module Proposals
5
5
  class PublishProposalEvent < Decidim::Events::SimpleEvent
6
- include Decidim::Events::AuthorEvent
6
+ include Decidim::Events::CoauthorEvent
7
7
 
8
8
  private
9
9
 
@@ -31,7 +31,7 @@ module Decidim
31
31
  delegate :categories, to: :current_component
32
32
 
33
33
  def map_model(model)
34
- self.user_group_id = model.decidim_user_group_id
34
+ self.user_group_id = model.user_groups.first&.id
35
35
  return unless model.categorization
36
36
 
37
37
  self.category_id = model.categorization.decidim_category_id
@@ -83,6 +83,10 @@ module Decidim
83
83
  end
84
84
  end
85
85
  end
86
+
87
+ def endorsers_for(proposal)
88
+ proposal.endorsements.for_listing.map { |identity| present(identity.normalized_author) }
89
+ end
86
90
  end
87
91
  end
88
92
  end
@@ -4,16 +4,6 @@ module Decidim
4
4
  module Proposals
5
5
  # Simple helper to handle markup variations for proposal endorsements partials
6
6
  module ProposalEndorsementsHelper
7
- # Returns the css classes used for proposal endorsements count in both proposals list and show pages
8
- #
9
- # from_proposals_list - A boolean to indicate if the template is rendered from the proposals list page
10
- #
11
- # Returns a hash with the css classes for the count number and label
12
- def endorsements_count_classes(from_proposals_list)
13
- return { number: "card__support__number", label: "" } if from_proposals_list
14
- { number: "extra__suport-number", label: "extra__suport-text" }
15
- end
16
-
17
7
  # Returns the css classes used for proposal endorsement button in both proposals list and show pages
18
8
  #
19
9
  # from_proposals_list - A boolean to indicate if the template is rendered from the proposals list page
@@ -116,6 +106,22 @@ module Decidim
116
106
  render partial: "decidim/proposals/proposal_endorsements/identity", locals:
117
107
  { identity: presenter, selected: selected, current_endorsement_url: current_endorsement_url, http_method: http_method }
118
108
  end
109
+
110
+ # Renders the counter of endorsements that appears in card at show Propoal.
111
+ def render_endorsements_count_card_part(proposal, fully_endorsed)
112
+ content = icon("bullhorn", class: "icon--small", aria_label: "Endorsements", role: "img")
113
+ content += proposal.proposal_endorsements_count.to_s
114
+ tag_params = { id: "proposal-#{proposal.id}-endorsements-count", class: "button small compact light button--sc button--shadow #{fully_endorsed ? "success" : "secondary"}" }
115
+ if proposal.proposal_endorsements_count.positive?
116
+ link_to "#list-of-endorsements", tag_params do
117
+ content
118
+ end
119
+ else
120
+ content_tag("div", tag_params) do
121
+ content
122
+ end
123
+ end
124
+ end
119
125
  end
120
126
  end
121
127
  end
@@ -8,9 +8,9 @@ module Decidim
8
8
  linked_proposals = proposal_metadata.linked_proposals
9
9
  linked_proposals.each do |proposal_id|
10
10
  proposal = Proposal.find(proposal_id)
11
- next if proposal.decidim_author_id.blank?
11
+ next if proposal.official?
12
12
 
13
- recipient_ids = [proposal.decidim_author_id]
13
+ recipient_ids = proposal.authors.pluck(:decidim_author_id)
14
14
  Decidim::EventsManager.publish(
15
15
  event: "decidim.events.proposals.proposal_mentioned",
16
16
  event_class: Decidim::Proposals::ProposalMentionedEvent,
@@ -5,7 +5,7 @@ module Decidim
5
5
  # The data store for a Proposal in the Decidim::Proposals component.
6
6
  class Proposal < Proposals::ApplicationRecord
7
7
  include Decidim::Resourceable
8
- include Decidim::Authorable
8
+ include Decidim::Coauthorable
9
9
  include Decidim::HasComponent
10
10
  include Decidim::ScopableComponent
11
11
  include Decidim::HasReference
@@ -18,6 +18,7 @@ module Decidim
18
18
  include Decidim::Traceable
19
19
  include Decidim::Loggable
20
20
  include Decidim::Fingerprintable
21
+ include Decidim::DataPortability
21
22
 
22
23
  fingerprint fields: [:title, :body]
23
24
 
@@ -60,6 +61,14 @@ module Decidim
60
61
  Decidim::Proposals::AdminLog::ProposalPresenter
61
62
  end
62
63
 
64
+ # Returns a collection scoped by user.
65
+ # Overrides this method in DataPortability to support Coauthorable.
66
+ def self.user_collection(user)
67
+ joins(:coauthorships)
68
+ .where("decidim_coauthorships.coauthorable_type = ?", name)
69
+ .where("decidim_coauthorships.decidim_author_id = ?", user.id)
70
+ end
71
+
63
72
  # Public: Check if the user has voted the proposal.
64
73
  #
65
74
  # Returns Boolean.
@@ -124,7 +133,7 @@ module Decidim
124
133
 
125
134
  # Public: Whether the proposal is official or not.
126
135
  def official?
127
- author.nil?
136
+ authors.empty?
128
137
  end
129
138
 
130
139
  # Public: The maximum amount of votes allowed for this proposal.
@@ -186,6 +195,14 @@ module Decidim
186
195
  Arel.sql(query)
187
196
  end
188
197
 
198
+ def self.export_serializer
199
+ Decidim::Proposals::ProposalSerializer
200
+ end
201
+
202
+ def self.data_portability_images(user)
203
+ user_collection(user).map { |p| p.attachments.collect(&:file_url) }
204
+ end
205
+
189
206
  private
190
207
 
191
208
  # Checks whether the proposal is inside the time window to be editable or not once published.
@@ -67,7 +67,7 @@ module Decidim
67
67
  end
68
68
 
69
69
  def can_withdraw_proposal?
70
- toggle_allow(proposal && proposal.author == user)
70
+ toggle_allow(proposal && proposal.authored_by?(user))
71
71
  end
72
72
 
73
73
  def can_endorse_proposal?
@@ -12,10 +12,13 @@ module Decidim
12
12
  def author
13
13
  @author ||= if official?
14
14
  Decidim::Proposals::OfficialAuthorPresenter.new
15
- elsif user_group
16
- Decidim::UserGroupPresenter.new(user_group)
17
15
  else
18
- Decidim::UserPresenter.new(super)
16
+ coauthorship = coauthorships.first
17
+ if coauthorship.user_group
18
+ Decidim::UserGroupPresenter.new(coauthorship.user_group)
19
+ else
20
+ Decidim::UserPresenter.new(coauthorship.author)
21
+ end
19
22
  end
20
23
  end
21
24
 
@@ -24,9 +24,9 @@ module Decidim
24
24
  # The 'official' proposals doesn't have an author id
25
25
  def search_origin
26
26
  if origin == "official"
27
- query.where(decidim_author_id: nil)
27
+ query.where(coauthorships_count: 0)
28
28
  elsif origin == "citizens"
29
- query.where.not(decidim_author_id: nil)
29
+ query.where.not(coauthorships_count: 0)
30
30
  else # Assume 'all'
31
31
  query
32
32
  end
@@ -5,7 +5,7 @@
5
5
  <%= link_to t("actions.import", scope: "decidim.proposals", name: t("models.proposal.name", scope: "decidim.proposals.admin")), new_proposals_import_path, class: "button tiny button--simple" if allowed_to? :import, :proposals %>
6
6
 
7
7
  <% if allowed_to? :create, :proposal %>
8
- <%= link_to t("actions.new", scope: "decidim.proposals", name: t("models.proposal.name", scope: "decidim.proposals.admin")), new_proposal_path, class: "button tiny button--simple" %>
8
+ <%= link_to t("actions.new", scope: "decidim.proposals"), new_proposal_path, class: "button tiny button--simple" %>
9
9
  <% end %>
10
10
 
11
11
  <%= export_dropdown %>
@@ -55,7 +55,7 @@
55
55
  <% end %>
56
56
 
57
57
  <th>
58
- <%= sort_link(query, :created_at, t("models.proposal.fields.created_at", scope: "decidim.proposals") ) %>
58
+ <%= sort_link(query, :published_at, t("models.proposal.fields.published_at", scope: "decidim.proposals") ) %>
59
59
  </th>
60
60
 
61
61
  <th class="actions"><%= t("actions.title", scope: "decidim.proposals") %></th>
@@ -10,7 +10,7 @@ function update_main_page_button() {
10
10
  function update_identities_rows() {
11
11
  <% fully_endorsed= fully_endorsed?(proposal, current_user) %>
12
12
  var $proposalEndorsementsRowCount = $('#proposal-<%= proposal.id %>-endorsements-count');
13
- morphdom($proposalEndorsementsRowCount[0], '<%= j(render partial: 'decidim/proposals/proposals/endorsements_count', locals: { proposal: proposal, fully_endorsed: fully_endorsed }).strip.html_safe %>');
13
+ morphdom($proposalEndorsementsRowCount[0], '<%= j(render_endorsements_count_card_part(proposal, fully_endorsed)).strip.html_safe %>');
14
14
  var $proposalEndorsementsRowButton = $($('#select-identity-button')[0]);
15
15
  <% if fully_endorsed %>
16
16
  $proposalEndorsementsRowButton.addClass('success')
@@ -3,7 +3,7 @@
3
3
  <% if endorsements_enabled? %>
4
4
  <div class="column small-9 collapse">
5
5
  <div class="button-group button-group--collapse button--nomargin small">
6
- <%= render partial: "endorsements_count", locals: { proposal: @proposal, fully_endorsed: fully_endorsed } %>
6
+ <%= render_endorsements_count_card_part(@proposal, fully_endorsed) %>
7
7
  <% if current_settings.endorsements_blocked? || !current_component.participatory_space.can_participate?(current_user) %>
8
8
  <%= content_tag :span, t(".endorse"), class: "card__button button #{endorsement_button_classes(false)} disabled", disabled: true, title: t(".endorse") %>
9
9
  <% elsif current_user %>