decidim-proposals 0.12.2 → 0.13.0.pre1

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 (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 %>