decidim-proposals 0.14.4 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -0
- data/app/assets/images/decidim/gamification/badges/accepted_proposals.svg +194 -104
- data/app/assets/images/decidim/gamification/badges/proposals.svg +192 -108
- data/app/assets/javascripts/decidim/proposals/admin/proposals.es6 +25 -20
- data/app/cells/decidim/proposals/proposal_activity_cell.rb +19 -0
- data/app/commands/decidim/proposals/admin/answer_proposal.rb +6 -2
- data/app/commands/decidim/proposals/admin/create_proposal.rb +4 -4
- data/app/commands/decidim/proposals/admin/import_participatory_text.rb +49 -0
- data/app/commands/decidim/proposals/admin/import_proposals.rb +7 -21
- data/app/commands/decidim/proposals/admin/merge_proposals.rb +68 -0
- data/app/commands/decidim/proposals/admin/publish_participatory_text.rb +65 -0
- data/app/commands/decidim/proposals/admin/split_proposals.rb +67 -0
- data/app/commands/decidim/proposals/admin/update_proposal.rb +67 -0
- data/app/commands/decidim/proposals/create_collaborative_draft.rb +19 -14
- data/app/commands/decidim/proposals/create_proposal.rb +4 -2
- data/app/commands/decidim/proposals/endorse_proposal.rb +5 -1
- data/app/commands/decidim/proposals/publish_proposal.rb +15 -3
- data/app/commands/decidim/proposals/unvote_proposal.rb +34 -3
- data/app/commands/decidim/proposals/vote_proposal.rb +32 -2
- data/app/controllers/decidim/proposals/admin/participatory_texts_controller.rb +62 -0
- data/app/controllers/decidim/proposals/admin/proposals_controller.rb +28 -1
- data/app/controllers/decidim/proposals/admin/proposals_merges_controller.rb +27 -0
- data/app/controllers/decidim/proposals/admin/proposals_splits_controller.rb +27 -0
- data/app/controllers/decidim/proposals/collaborative_drafts_controller.rb +0 -1
- data/app/controllers/decidim/proposals/proposal_endorsements_controller.rb +6 -2
- data/app/controllers/decidim/proposals/proposal_votes_controller.rb +15 -0
- data/app/controllers/decidim/proposals/proposals_controller.rb +1 -1
- data/app/forms/decidim/proposals/admin/import_participatory_text_form.rb +28 -0
- data/app/forms/decidim/proposals/admin/preview_participatory_text_form.rb +21 -0
- data/app/forms/decidim/proposals/admin/proposal_form.rb +4 -1
- data/app/forms/decidim/proposals/admin/proposals_fork_form.rb +57 -0
- data/app/forms/decidim/proposals/admin/proposals_merge_form.rb +13 -0
- data/app/forms/decidim/proposals/admin/proposals_split_form.rb +12 -0
- data/app/helpers/decidim/proposals/application_helper.rb +23 -0
- data/app/helpers/decidim/proposals/map_helper.rb +1 -1
- data/app/helpers/decidim/proposals/participatory_texts_helper.rb +18 -0
- data/app/helpers/decidim/proposals/proposal_endorsements_helper.rb +1 -1
- data/app/helpers/decidim/proposals/proposal_wizard_helper.rb +2 -1
- data/app/jobs/decidim/proposals/notify_proposals_mentioned_job.rb +3 -4
- data/app/models/decidim/proposals/participatory_text.rb +13 -0
- data/app/models/decidim/proposals/proposal.rb +31 -9
- data/app/models/decidim/proposals/proposal_vote.rb +21 -1
- data/app/permissions/decidim/proposals/admin/permissions.rb +30 -0
- data/app/presenters/decidim/proposals/admin_log/proposal_presenter.rb +5 -1
- data/app/presenters/decidim/proposals/log/resource_presenter.rb +18 -0
- data/app/presenters/decidim/proposals/official_author_presenter.rb +4 -0
- data/app/queries/decidim/proposals/metrics/accepted_proposals_metric_manage.rb +29 -0
- data/app/queries/decidim/proposals/metrics/proposals_metric_manage.rb +53 -0
- data/app/queries/decidim/proposals/metrics/votes_metric_manage.rb +57 -0
- data/app/services/decidim/proposals/proposal_builder.rb +72 -0
- data/app/services/decidim/proposals/proposal_search.rb +2 -2
- data/app/views/decidim/proposals/admin/participatory_texts/_article-preview.html.erb +13 -0
- data/app/views/decidim/proposals/admin/participatory_texts/_bulk-actions.html.erb +1 -0
- data/app/views/decidim/proposals/admin/participatory_texts/index.html.erb +43 -0
- data/app/views/decidim/proposals/admin/participatory_texts/new_import.html.erb +39 -0
- data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +7 -2
- data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +4 -0
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_dropdown.html.erb +36 -0
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_merge.html.erb +15 -0
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_recategorize.html.erb +15 -0
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_split.html.erb +15 -0
- data/app/views/decidim/proposals/admin/proposals/edit.html.erb +7 -0
- data/app/views/decidim/proposals/admin/proposals/index.html.erb +2 -2
- data/app/views/decidim/proposals/collaborative_drafts/compare.html.erb +2 -2
- data/app/views/decidim/proposals/collaborative_drafts/complete.html.erb +1 -1
- data/app/views/decidim/proposals/collaborative_drafts/show.html.erb +1 -1
- data/app/views/decidim/proposals/proposal_endorsements/_identity.html.erb +5 -1
- data/app/views/decidim/proposals/proposal_endorsements/update_buttons_and_counters.js.erb +1 -1
- data/app/views/decidim/proposals/proposal_votes/update_buttons_and_counters.js.erb +13 -4
- data/app/views/decidim/proposals/proposals/_edit_form_fields.html.erb +2 -2
- data/app/views/decidim/proposals/proposals/_endorsement_identities_cabin.html.erb +1 -1
- data/app/views/decidim/proposals/proposals/_proposal_similar.html.erb +1 -1
- data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +15 -4
- data/app/views/decidim/proposals/proposals/new.html.erb +1 -1
- data/config/locales/ca.yml +72 -4
- data/config/locales/de.yml +71 -3
- data/config/locales/en.yml +71 -3
- data/config/locales/es-PY.yml +71 -3
- data/config/locales/es.yml +73 -5
- data/config/locales/eu.yml +71 -3
- data/config/locales/fi.yml +81 -13
- data/config/locales/fr.yml +71 -3
- data/config/locales/gl.yml +71 -3
- data/config/locales/hu.yml +71 -3
- data/config/locales/it.yml +71 -3
- data/config/locales/nl.yml +71 -3
- data/config/locales/pl.yml +71 -3
- data/config/locales/pt-BR.yml +72 -4
- data/config/locales/pt.yml +71 -3
- data/config/locales/sv.yml +71 -3
- data/db/migrate/20180927111721_create_participatory_texts.rb +13 -0
- data/db/migrate/20180930125321_add_participatory_text_level_to_proposals.rb +7 -0
- data/db/migrate/20180930125321_add_position_to_proposals.rb +7 -0
- data/db/migrate/20181003074440_fix_user_groups_ids_in_proposals_endorsements.rb +16 -0
- data/db/migrate/20181010114622_add_temporary_votes.rb +9 -0
- data/db/migrate/20181016132225_add_organization_as_author.rb +13 -0
- data/db/migrate/20181017084221_make_author_polymorhpic_for_proposal_endorsements.rb +31 -0
- data/lib/decidim/content_parsers/proposal_parser.rb +9 -3
- data/lib/decidim/proposals.rb +3 -1
- data/lib/decidim/proposals/admin_engine.rb +12 -1
- data/lib/decidim/proposals/component.rb +60 -23
- data/lib/decidim/proposals/engine.rb +55 -12
- data/lib/decidim/proposals/markdown_to_proposals.rb +90 -0
- data/lib/decidim/proposals/participatory_text_section.rb +21 -0
- data/lib/decidim/proposals/test/factories.rb +35 -7
- data/lib/decidim/proposals/version.rb +1 -1
- metadata +86 -19
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
module Admin
|
6
|
+
# A form object to be used when admin users want to import a collection of proposals
|
7
|
+
# from a participatory text.
|
8
|
+
class ImportParticipatoryTextForm < Decidim::Form
|
9
|
+
include TranslatableAttributes
|
10
|
+
|
11
|
+
translatable_attribute :title, String
|
12
|
+
translatable_attribute :description, String
|
13
|
+
attribute :document
|
14
|
+
|
15
|
+
validates :document, presence: true
|
16
|
+
validates :title, translatable_presence: true
|
17
|
+
|
18
|
+
def default_locale
|
19
|
+
current_participatory_space.organization.default_locale
|
20
|
+
end
|
21
|
+
|
22
|
+
def document_text
|
23
|
+
document&.read
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
module Admin
|
6
|
+
# A form object to be used when admin users want to review a collection of proposals
|
7
|
+
# from a participatory text.
|
8
|
+
class PreviewParticipatoryTextForm < Decidim::Form
|
9
|
+
attribute :proposals, Array[ProposalForm]
|
10
|
+
|
11
|
+
def from_models(proposals)
|
12
|
+
self.proposals = proposals.collect do |proposal|
|
13
|
+
ProposalForm.from_model(proposal)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def proposals_attributes=(attributes); end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -15,8 +15,10 @@ module Decidim
|
|
15
15
|
attribute :category_id, Integer
|
16
16
|
attribute :scope_id, Integer
|
17
17
|
attribute :attachment, AttachmentForm
|
18
|
+
attribute :position, Integer
|
18
19
|
|
19
|
-
validates :title, :body, presence: true
|
20
|
+
validates :title, :body, presence: true, etiquette: true
|
21
|
+
validates :title, length: { maximum: 150 }
|
20
22
|
validates :address, geocoding: true, if: -> { current_component.settings.geocoding_enabled? }
|
21
23
|
validates :category, presence: true, if: ->(form) { form.category_id.present? }
|
22
24
|
validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
|
@@ -31,6 +33,7 @@ module Decidim
|
|
31
33
|
return unless model.categorization
|
32
34
|
|
33
35
|
self.category_id = model.categorization.decidim_category_id
|
36
|
+
self.scope_id = model.decidim_scope_id
|
34
37
|
end
|
35
38
|
|
36
39
|
alias component current_component
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
module Admin
|
6
|
+
# A common abstract to be used by the Merge and Split proposals forms.
|
7
|
+
class ProposalsForkForm < Decidim::Form
|
8
|
+
mimic :proposals_import
|
9
|
+
|
10
|
+
attribute :target_component_id, Integer
|
11
|
+
attribute :proposal_ids, Array
|
12
|
+
|
13
|
+
validates :target_component, :proposals, :current_component, presence: true
|
14
|
+
validate :same_participatory_space
|
15
|
+
validate :mergeable_to_same_component
|
16
|
+
|
17
|
+
def proposals
|
18
|
+
@proposals ||= Decidim::Proposals::Proposal.where(component: current_component, id: proposal_ids).uniq
|
19
|
+
end
|
20
|
+
|
21
|
+
def target_component
|
22
|
+
return current_component if clean_target_component_id == current_component.id
|
23
|
+
@target_component ||= current_component.siblings.find_by(id: target_component_id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def same_component?
|
27
|
+
target_component == current_component
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def mergeable_to_same_component
|
33
|
+
return true unless same_component?
|
34
|
+
|
35
|
+
public_proposals = proposals.any? do |proposal|
|
36
|
+
!proposal.official? || proposal.votes.any? || proposal.endorsements.any?
|
37
|
+
end
|
38
|
+
|
39
|
+
errors.add(:proposal_ids, :invalid) if public_proposals
|
40
|
+
end
|
41
|
+
|
42
|
+
def same_participatory_space
|
43
|
+
return if !target_component || !current_component
|
44
|
+
|
45
|
+
errors.add(:target_component, :invalid) if current_component.participatory_space != target_component.participatory_space
|
46
|
+
end
|
47
|
+
|
48
|
+
# Private: Returns the id of the target component.
|
49
|
+
#
|
50
|
+
# We receive this as ["id"] since it's from a select in a form.
|
51
|
+
def clean_target_component_id
|
52
|
+
target_component_id.first.to_i
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
module Admin
|
6
|
+
# A form object to be used when admin users wants to merge two or more
|
7
|
+
# proposals into a new one to another proposal component in the same space.
|
8
|
+
class ProposalsMergeForm < ProposalsForkForm
|
9
|
+
validates :proposals, length: { minimum: 2 }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
module Admin
|
6
|
+
# A form object to be used when admin users wants to split two or more
|
7
|
+
# proposals into a new one to another proposal component in the same space.
|
8
|
+
class ProposalsSplitForm < ProposalsForkForm
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -13,6 +13,8 @@ module Decidim
|
|
13
13
|
include Decidim::Proposals::MapHelper
|
14
14
|
include CollaborativeDraftHelper
|
15
15
|
|
16
|
+
delegate :minimum_votes_per_user, to: :component_settings
|
17
|
+
|
16
18
|
# Public: The state of a proposal in a way a human can understand.
|
17
19
|
#
|
18
20
|
# state - The String state of the proposal.
|
@@ -69,12 +71,23 @@ module Decidim
|
|
69
71
|
proposal_limit.present?
|
70
72
|
end
|
71
73
|
|
74
|
+
def minimum_votes_per_user_enabled?
|
75
|
+
minimum_votes_per_user.positive?
|
76
|
+
end
|
77
|
+
|
72
78
|
def proposal_limit
|
73
79
|
return if component_settings.proposal_limit.zero?
|
74
80
|
|
75
81
|
component_settings.proposal_limit
|
76
82
|
end
|
77
83
|
|
84
|
+
def votes_given
|
85
|
+
@votes_given ||= ProposalVote.where(
|
86
|
+
proposal: Proposal.where(component: current_component),
|
87
|
+
author: current_user
|
88
|
+
).count
|
89
|
+
end
|
90
|
+
|
78
91
|
def current_user_proposals
|
79
92
|
Proposal.where(component: current_component, author: current_user)
|
80
93
|
end
|
@@ -102,6 +115,16 @@ module Decidim
|
|
102
115
|
def authors_for(collaborative_draft)
|
103
116
|
collaborative_draft.identities.map { |identity| present(identity) }
|
104
117
|
end
|
118
|
+
|
119
|
+
def show_voting_rules?
|
120
|
+
return false unless votes_enabled?
|
121
|
+
|
122
|
+
return true if vote_limit_enabled?
|
123
|
+
return true if threshold_per_proposal_enabled?
|
124
|
+
return true if proposal_limit_enabled?
|
125
|
+
return true if can_accumulate_supports_beyond_threshold?
|
126
|
+
return true if minimum_votes_per_user_enabled?
|
127
|
+
end
|
105
128
|
end
|
106
129
|
end
|
107
130
|
end
|
@@ -10,7 +10,7 @@ module Decidim
|
|
10
10
|
# geocoded_proposals - A collection of geocoded proposals
|
11
11
|
def proposals_data_for_map(geocoded_proposals)
|
12
12
|
geocoded_proposals.map do |proposal|
|
13
|
-
proposal.slice(:latitude, :longitude, :address).merge(title:
|
13
|
+
proposal.slice(:latitude, :longitude, :address).merge(title: present(proposal).title,
|
14
14
|
body: truncate(present(proposal).body, length: 100),
|
15
15
|
icon: icon("proposals", width: 40, height: 70, remove_icon_class: true),
|
16
16
|
link: proposal_path(proposal))
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
# Simple helper to handle markup variations for participatory texts related partials
|
6
|
+
module ParticipatoryTextsHelper
|
7
|
+
# Returns the title for a given participatory text section.
|
8
|
+
#
|
9
|
+
# proposal - The current proposal item.
|
10
|
+
#
|
11
|
+
# Returns a string with the title of the section, subsection or article.
|
12
|
+
def preview_participatory_text_section_title(proposal)
|
13
|
+
translated = t(proposal.participatory_text_level, scope: "decidim.proposals.admin.participatory_texts.sections", title: proposal.title)
|
14
|
+
translated.html_safe
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -79,7 +79,7 @@ module Decidim
|
|
79
79
|
def fully_endorsed?(proposal, user)
|
80
80
|
return false unless user
|
81
81
|
|
82
|
-
user_group_endorsements = user.
|
82
|
+
user_group_endorsements = Decidim::UserGroups::ManageableUserGroups.for(user).verified.all? { |user_group| proposal.endorsed_by?(user, user_group) }
|
83
83
|
|
84
84
|
user_group_endorsements && proposal.endorsed_by?(user)
|
85
85
|
end
|
@@ -101,9 +101,10 @@ module Decidim
|
|
101
101
|
# Returns nothing.
|
102
102
|
def user_group_select_field(form, name)
|
103
103
|
selected = @form.user_group_id.presence
|
104
|
+
user_groups = Decidim::UserGroups::ManageableUserGroups.for(current_user).verified
|
104
105
|
form.select(
|
105
106
|
name,
|
106
|
-
|
107
|
+
user_groups.map { |g| [g.name, g.id] },
|
107
108
|
selected: selected,
|
108
109
|
include_blank: current_user.name
|
109
110
|
)
|
@@ -3,14 +3,13 @@
|
|
3
3
|
module Decidim
|
4
4
|
module Proposals
|
5
5
|
class NotifyProposalsMentionedJob < ApplicationJob
|
6
|
-
def perform(comment_id,
|
6
|
+
def perform(comment_id, linked_proposals)
|
7
7
|
comment = Decidim::Comments::Comment.find(comment_id)
|
8
|
-
|
8
|
+
|
9
9
|
linked_proposals.each do |proposal_id|
|
10
10
|
proposal = Proposal.find(proposal_id)
|
11
|
-
|
11
|
+
recipient_ids = proposal.notifiable_authors.map(&:id)
|
12
12
|
|
13
|
-
recipient_ids = proposal.authors.pluck(:decidim_author_id)
|
14
13
|
Decidim::EventsManager.publish(
|
15
14
|
event: "decidim.events.proposals.proposal_mentioned",
|
16
15
|
event_class: Decidim::Proposals::ProposalMentionedEvent,
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
# Contains the meta data of the document, like title and description.
|
6
|
+
#
|
7
|
+
class ParticipatoryText < Proposals::ApplicationRecord
|
8
|
+
include Decidim::HasComponent
|
9
|
+
include Decidim::Traceable
|
10
|
+
include Decidim::Loggable
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -20,13 +20,21 @@ module Decidim
|
|
20
20
|
include Decidim::Fingerprintable
|
21
21
|
include Decidim::DataPortability
|
22
22
|
include Decidim::Hashtaggable
|
23
|
+
include Decidim::Proposals::ParticipatoryTextSection
|
23
24
|
|
24
25
|
fingerprint fields: [:title, :body]
|
25
26
|
|
26
27
|
component_manifest_name "proposals"
|
27
28
|
|
28
29
|
has_many :endorsements, foreign_key: "decidim_proposal_id", class_name: "ProposalEndorsement", dependent: :destroy, counter_cache: "proposal_endorsements_count"
|
29
|
-
|
30
|
+
|
31
|
+
has_many :votes,
|
32
|
+
-> { final },
|
33
|
+
foreign_key: "decidim_proposal_id",
|
34
|
+
class_name: "Decidim::Proposals::ProposalVote",
|
35
|
+
dependent: :destroy,
|
36
|
+
counter_cache: "proposal_votes_count"
|
37
|
+
|
30
38
|
has_many :notes, foreign_key: "decidim_proposal_id", class_name: "ProposalNote", dependent: :destroy, counter_cache: "proposal_notes_count"
|
31
39
|
|
32
40
|
validates :title, :body, presence: true
|
@@ -39,8 +47,11 @@ module Decidim
|
|
39
47
|
scope :withdrawn, -> { where(state: "withdrawn") }
|
40
48
|
scope :except_rejected, -> { where.not(state: "rejected").or(where(state: nil)) }
|
41
49
|
scope :except_withdrawn, -> { where.not(state: "withdrawn").or(where(state: nil)) }
|
50
|
+
scope :drafts, -> { where(published_at: nil) }
|
42
51
|
scope :published, -> { where.not(published_at: nil) }
|
43
52
|
|
53
|
+
acts_as_list scope: :decidim_component_id
|
54
|
+
|
44
55
|
searchable_fields({
|
45
56
|
scope_id: :decidim_scope_id,
|
46
57
|
participatory_space: { component: :participatory_space },
|
@@ -62,19 +73,30 @@ module Decidim
|
|
62
73
|
Decidim::Proposals::AdminLog::ProposalPresenter
|
63
74
|
end
|
64
75
|
|
65
|
-
# Returns a collection scoped by
|
76
|
+
# Returns a collection scoped by an author.
|
66
77
|
# Overrides this method in DataPortability to support Coauthorable.
|
67
|
-
def self.user_collection(
|
78
|
+
def self.user_collection(author)
|
79
|
+
return unless author.is_a?(Decidim::User)
|
80
|
+
|
68
81
|
joins(:coauthorships)
|
69
82
|
.where("decidim_coauthorships.coauthorable_type = ?", name)
|
70
|
-
.where("decidim_coauthorships.decidim_author_id = ?",
|
83
|
+
.where("decidim_coauthorships.decidim_author_id = ? AND decidim_coauthorships.decidim_author_type = ? ", author.id, author.class.base_class.name)
|
71
84
|
end
|
72
85
|
|
86
|
+
# Public: Updates the vote count of this proposal.
|
87
|
+
#
|
88
|
+
# Returns nothing.
|
89
|
+
# rubocop:disable Rails/SkipsModelValidations
|
90
|
+
def update_votes_count
|
91
|
+
update_columns(proposal_votes_count: votes.count)
|
92
|
+
end
|
93
|
+
# rubocop:enable Rails/SkipsModelValidations
|
94
|
+
|
73
95
|
# Public: Check if the user has voted the proposal.
|
74
96
|
#
|
75
97
|
# Returns Boolean.
|
76
98
|
def voted_by?(user)
|
77
|
-
|
99
|
+
ProposalVote.where(proposal: self, author: user).any?
|
78
100
|
end
|
79
101
|
|
80
102
|
# Public: Check if the user has endorsed the proposal.
|
@@ -134,7 +156,7 @@ module Decidim
|
|
134
156
|
|
135
157
|
# Public: Whether the proposal is official or not.
|
136
158
|
def official?
|
137
|
-
authors.
|
159
|
+
authors.first.is_a?(Decidim::Organization)
|
138
160
|
end
|
139
161
|
|
140
162
|
# Public: The maximum amount of votes allowed for this proposal.
|
@@ -201,7 +223,7 @@ module Decidim
|
|
201
223
|
end
|
202
224
|
|
203
225
|
def self.data_portability_images(user)
|
204
|
-
user_collection(user).map { |p| p.attachments.collect(&:
|
226
|
+
user_collection(user).map { |p| p.attachments.collect(&:file) }
|
205
227
|
end
|
206
228
|
|
207
229
|
# Public: Overrides the `allow_resource_permissions?` Resourceable concern method.
|
@@ -209,8 +231,6 @@ module Decidim
|
|
209
231
|
component.settings.resources_permissions_enabled
|
210
232
|
end
|
211
233
|
|
212
|
-
private
|
213
|
-
|
214
234
|
# Checks whether the proposal is inside the time window to be editable or not once published.
|
215
235
|
def within_edit_time_limit?
|
216
236
|
return true if draft?
|
@@ -218,6 +238,8 @@ module Decidim
|
|
218
238
|
Time.current < limit
|
219
239
|
end
|
220
240
|
|
241
|
+
private
|
242
|
+
|
221
243
|
def copied_from_other_component?
|
222
244
|
linked_resources(:proposals, "copied_from_component").any?
|
223
245
|
end
|
@@ -4,15 +4,35 @@ module Decidim
|
|
4
4
|
module Proposals
|
5
5
|
# A proposal can include a vote per user.
|
6
6
|
class ProposalVote < ApplicationRecord
|
7
|
-
belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal"
|
7
|
+
belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal"
|
8
8
|
belongs_to :author, foreign_key: "decidim_author_id", class_name: "Decidim::User"
|
9
9
|
|
10
10
|
validates :proposal, uniqueness: { scope: :author }
|
11
11
|
validate :author_and_proposal_same_organization
|
12
12
|
validate :proposal_not_rejected
|
13
13
|
|
14
|
+
after_save :update_proposal_votes_count
|
15
|
+
after_destroy :update_proposal_votes_count
|
16
|
+
|
17
|
+
# Temporary votes are used when a minimum amount of votes is configured in
|
18
|
+
# a component. They aren't taken into account unless the amount of votes
|
19
|
+
# exceeds a threshold - meanwhile, they're marked as temporary.
|
20
|
+
def self.temporary
|
21
|
+
where(temporary: true)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Final votes are votes that will be taken into account, that is, they're
|
25
|
+
# not temporary.
|
26
|
+
def self.final
|
27
|
+
where(temporary: false)
|
28
|
+
end
|
29
|
+
|
14
30
|
private
|
15
31
|
|
32
|
+
def update_proposal_votes_count
|
33
|
+
proposal.update_votes_count
|
34
|
+
end
|
35
|
+
|
16
36
|
# Private: check if the proposal and the author have the same organization
|
17
37
|
def author_and_proposal_same_organization
|
18
38
|
return if !proposal || !author
|
@@ -22,22 +22,48 @@ module Decidim
|
|
22
22
|
toggle_allow(admin_proposal_answering_is_enabled?) if permission_action.subject == :proposal_answer
|
23
23
|
end
|
24
24
|
|
25
|
+
# Admins can only edit official proposals if they are within the
|
26
|
+
# time limit.
|
27
|
+
allow! if permission_action.subject == :proposal && permission_action.action == :edit && admin_edition_is_available?
|
28
|
+
|
25
29
|
# Every user allowed by the space can update the category of the proposal
|
26
30
|
allow! if permission_action.subject == :proposal_category && permission_action.action == :update
|
27
31
|
|
28
32
|
# Every user allowed by the space can import proposals from another_component
|
29
33
|
allow! if permission_action.subject == :proposals && permission_action.action == :import
|
30
34
|
|
35
|
+
# Every user allowed by the space can merge proposals to another component
|
36
|
+
allow! if permission_action.subject == :proposals && permission_action.action == :merge
|
37
|
+
|
38
|
+
# Every user allowed by the space can split proposals to another component
|
39
|
+
allow! if permission_action.subject == :proposals && permission_action.action == :split
|
40
|
+
|
41
|
+
if permission_action.subject == :participatory_texts && participatory_texts_are_enabled?
|
42
|
+
# Every user allowed by the space can import participatory texts to proposals
|
43
|
+
allow! if permission_action.action == :import
|
44
|
+
# Every user allowed by the space can publish participatory texts to proposals
|
45
|
+
allow! if permission_action.action == :publish
|
46
|
+
end
|
47
|
+
|
31
48
|
permission_action
|
32
49
|
end
|
33
50
|
|
34
51
|
private
|
35
52
|
|
53
|
+
def proposal
|
54
|
+
@proposal ||= context.fetch(:proposal, nil)
|
55
|
+
end
|
56
|
+
|
36
57
|
def admin_creation_is_enabled?
|
37
58
|
current_settings.try(:creation_enabled?) &&
|
38
59
|
component_settings.try(:official_proposals_enabled)
|
39
60
|
end
|
40
61
|
|
62
|
+
def admin_edition_is_available?
|
63
|
+
return unless proposal
|
64
|
+
proposal.official? && proposal.votes.empty?
|
65
|
+
end
|
66
|
+
|
41
67
|
def admin_proposal_answering_is_enabled?
|
42
68
|
current_settings.try(:proposal_answering_enabled) &&
|
43
69
|
component_settings.try(:proposal_answering_enabled)
|
@@ -46,6 +72,10 @@ module Decidim
|
|
46
72
|
def create_permission_action?
|
47
73
|
permission_action.action == :create
|
48
74
|
end
|
75
|
+
|
76
|
+
def participatory_texts_are_enabled?
|
77
|
+
component_settings.participatory_texts_enabled?
|
78
|
+
end
|
49
79
|
end
|
50
80
|
end
|
51
81
|
end
|