decidim-sortitions 0.9.0
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.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +31 -0
- data/Rakefile +3 -0
- data/app/assets/config/decidim_sortitions_manifest.css +1 -0
- data/app/assets/config/decidim_sortitions_manifest.js +1 -0
- data/app/assets/images/decidim/sortitions/icon.svg +4 -0
- data/app/assets/javascripts/decidim/sortitions/admin/sortitions.js +2 -0
- data/app/assets/javascripts/decidim/sortitions/social_share.js +2 -0
- data/app/assets/stylesheets/decidim/sortitions/_sortitions.scss +45 -0
- data/app/assets/stylesheets/decidim/sortitions/social_share.css.scss +18 -0
- data/app/commands/decidim/sortitions/admin/create_sortition.rb +81 -0
- data/app/commands/decidim/sortitions/admin/destroy_sortition.rb +45 -0
- data/app/commands/decidim/sortitions/admin/update_sortition.rb +45 -0
- data/app/controllers/concerns/decidim/sortitions/orderable.rb +50 -0
- data/app/controllers/decidim/sortitions/admin/application_controller.rb +28 -0
- data/app/controllers/decidim/sortitions/admin/sortitions_controller.rb +104 -0
- data/app/controllers/decidim/sortitions/application_controller.rb +13 -0
- data/app/controllers/decidim/sortitions/sortition_widgets_controller.rb +20 -0
- data/app/controllers/decidim/sortitions/sortitions_controller.rb +45 -0
- data/app/events/decidim/sortitions/create_sortition_event.rb +8 -0
- data/app/forms/decidim/sortitions/admin/destroy_sortition_form.rb +20 -0
- data/app/forms/decidim/sortitions/admin/edit_sortition_form.rb +22 -0
- data/app/forms/decidim/sortitions/admin/sortition_form.rb +44 -0
- data/app/helpers/decidim/sortitions/admin/sortitions_helper.rb +24 -0
- data/app/helpers/decidim/sortitions/sortitions_helper.rb +38 -0
- data/app/models/decidim/sortitions/abilities/admin/admin_ability.rb +23 -0
- data/app/models/decidim/sortitions/abilities/admin/process_admin_ability.rb +46 -0
- data/app/models/decidim/sortitions/application_record.rb +9 -0
- data/app/models/decidim/sortitions/sortition.rb +81 -0
- data/app/queries/decidim/sortitions/admin/participatory_space_proposal_features.rb +29 -0
- data/app/queries/decidim/sortitions/admin/participatory_space_proposals.rb +49 -0
- data/app/queries/decidim/sortitions/filtered_sortitions.rb +37 -0
- data/app/services/decidim/sortitions/admin/draw.rb +31 -0
- data/app/services/decidim/sortitions/sortition_search.rb +47 -0
- data/app/views/decidim/sortitions/admin/sortitions/_form.html.erb +41 -0
- data/app/views/decidim/sortitions/admin/sortitions/confirm_destroy.html.erb +21 -0
- data/app/views/decidim/sortitions/admin/sortitions/edit.html.erb +27 -0
- data/app/views/decidim/sortitions/admin/sortitions/index.html.erb +57 -0
- data/app/views/decidim/sortitions/admin/sortitions/new.html.erb +11 -0
- data/app/views/decidim/sortitions/admin/sortitions/show.html.erb +55 -0
- data/app/views/decidim/sortitions/sortition_widgets/show.html.erb +4 -0
- data/app/views/decidim/sortitions/sortitions/_count.html.erb +1 -0
- data/app/views/decidim/sortitions/sortitions/_filters.html.erb +31 -0
- data/app/views/decidim/sortitions/sortitions/_filters_small_view.html.erb +18 -0
- data/app/views/decidim/sortitions/sortitions/_filters_small_view0.html.erb +18 -0
- data/app/views/decidim/sortitions/sortitions/_linked_sortitions.html.erb +29 -0
- data/app/views/decidim/sortitions/sortitions/_proposal.html.erb +24 -0
- data/app/views/decidim/sortitions/sortitions/_proposal_badge.html.erb +3 -0
- data/app/views/decidim/sortitions/sortitions/_results_count.html.erb +8 -0
- data/app/views/decidim/sortitions/sortitions/_sortition.html.erb +25 -0
- data/app/views/decidim/sortitions/sortitions/_sortition_author.html.erb +17 -0
- data/app/views/decidim/sortitions/sortitions/_sortition_cancel_author.html.erb +17 -0
- data/app/views/decidim/sortitions/sortitions/_sortitions.html.erb +14 -0
- data/app/views/decidim/sortitions/sortitions/_sortitions_count.html.erb +1 -0
- data/app/views/decidim/sortitions/sortitions/_tags.html.erb +16 -0
- data/app/views/decidim/sortitions/sortitions/index.html.erb +21 -0
- data/app/views/decidim/sortitions/sortitions/index.js.erb +10 -0
- data/app/views/decidim/sortitions/sortitions/show.html.erb +134 -0
- data/config/initializers/social_share_button.rb +5 -0
- data/config/locales/ca.yml +155 -0
- data/config/locales/en.yml +154 -0
- data/config/locales/es.yml +155 -0
- data/db/migrate/20171215161358_create_decidim_module_sortitions_sortitions.rb +23 -0
- data/db/migrate/20171220164658_add_witnesses_to_sortitions.rb +7 -0
- data/db/migrate/20171220164744_add_additional_info_to_sortitions.rb +7 -0
- data/db/migrate/20180102100101_add_author_to_sortitions.rb +7 -0
- data/db/migrate/20180102101128_add_reference_to_sortitions.rb +8 -0
- data/db/migrate/20180103082645_add_title_to_sortition.rb +7 -0
- data/db/migrate/20180103123055_drop_decidim_category_id_from_sortitions.rb +13 -0
- data/db/migrate/20180103160301_add_cancel_data_to_sortition.rb +9 -0
- data/db/migrate/20180104143054_make_sortition_reference_nullable.rb +7 -0
- data/db/migrate/20180104145344_add_candidate_proposals_to_sortitions.rb +7 -0
- data/db/migrate/20180108132729_rename_sortitions_table.rb +7 -0
- data/lib/decidim/sortitions.rb +24 -0
- data/lib/decidim/sortitions/admin.rb +10 -0
- data/lib/decidim/sortitions/admin_engine.rb +35 -0
- data/lib/decidim/sortitions/engine.rb +26 -0
- data/lib/decidim/sortitions/feature.rb +43 -0
- data/lib/decidim/sortitions/test/factories.rb +37 -0
- data/lib/decidim/sortitions/version.rb +7 -0
- metadata +208 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
# This controller is the abstract class from which all other controllers of
|
|
6
|
+
# this engine inherit.
|
|
7
|
+
#
|
|
8
|
+
# Note that it inherits from `Decidim::Features::BaseController`, which
|
|
9
|
+
# override its layout and provide all kinds of useful methods.
|
|
10
|
+
class ApplicationController < Decidim::Features::BaseController
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
class SortitionWidgetsController < Decidim::WidgetsController
|
|
6
|
+
helper Decidim::SanitizeHelper
|
|
7
|
+
helper Sortitions::SortitionsHelper
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def model
|
|
12
|
+
@model ||= Sortition.where(feature: params[:feature_id]).find(params[:sortition_id])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def iframe_url
|
|
16
|
+
@iframe_url ||= sortition_sortition_widget_url(model)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
# Exposes the sortition resource so users can view them
|
|
6
|
+
class SortitionsController < Decidim::Sortitions::ApplicationController
|
|
7
|
+
helper Decidim::WidgetUrlsHelper
|
|
8
|
+
include FilterResource
|
|
9
|
+
include Orderable
|
|
10
|
+
include Paginable
|
|
11
|
+
|
|
12
|
+
helper_method :sortition
|
|
13
|
+
|
|
14
|
+
helper Decidim::Proposals::ApplicationHelper
|
|
15
|
+
|
|
16
|
+
def index
|
|
17
|
+
@sortitions = search
|
|
18
|
+
.results
|
|
19
|
+
.includes(:author)
|
|
20
|
+
.includes(:category)
|
|
21
|
+
|
|
22
|
+
@sortitions = paginate(@sortitions)
|
|
23
|
+
@sortitions = reorder(@sortitions)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def sortition
|
|
29
|
+
Sortition.find(params[:id])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def search_klass
|
|
33
|
+
SortitionSearch
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def default_filter_params
|
|
37
|
+
{
|
|
38
|
+
search_text: "",
|
|
39
|
+
category_id: "",
|
|
40
|
+
state: "active"
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
module Admin
|
|
6
|
+
class DestroySortitionForm < Form
|
|
7
|
+
include TranslatableAttributes
|
|
8
|
+
|
|
9
|
+
mimic :sortition
|
|
10
|
+
|
|
11
|
+
translatable_attribute :cancel_reason, String
|
|
12
|
+
|
|
13
|
+
validates :cancel_reason, translatable_presence: true
|
|
14
|
+
|
|
15
|
+
delegate :current_participatory_space, to: :context
|
|
16
|
+
delegate :current_feature, to: :context
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
module Admin
|
|
6
|
+
class EditSortitionForm < Form
|
|
7
|
+
include TranslatableAttributes
|
|
8
|
+
|
|
9
|
+
mimic :sortition
|
|
10
|
+
|
|
11
|
+
translatable_attribute :title, String
|
|
12
|
+
translatable_attribute :additional_info, String
|
|
13
|
+
|
|
14
|
+
validates :title, translatable_presence: true
|
|
15
|
+
validates :additional_info, translatable_presence: true
|
|
16
|
+
|
|
17
|
+
delegate :current_participatory_space, to: :context
|
|
18
|
+
delegate :current_feature, to: :context
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
module Admin
|
|
6
|
+
class SortitionForm < Form
|
|
7
|
+
include TranslatableAttributes
|
|
8
|
+
|
|
9
|
+
mimic :sortition
|
|
10
|
+
|
|
11
|
+
translatable_attribute :title, String
|
|
12
|
+
attribute :decidim_proposals_feature_id, Integer
|
|
13
|
+
attribute :decidim_category_id, Integer
|
|
14
|
+
attribute :dice, Integer
|
|
15
|
+
attribute :target_items, Integer
|
|
16
|
+
translatable_attribute :witnesses, String
|
|
17
|
+
translatable_attribute :additional_info, String
|
|
18
|
+
|
|
19
|
+
validates :title, translatable_presence: true
|
|
20
|
+
validates :decidim_proposals_feature_id, presence: true
|
|
21
|
+
validates :witnesses, translatable_presence: true
|
|
22
|
+
validates :additional_info, translatable_presence: true
|
|
23
|
+
validates :dice,
|
|
24
|
+
presence: true,
|
|
25
|
+
numericality: {
|
|
26
|
+
only_integer: true,
|
|
27
|
+
greater_than_or_equal_to: 1,
|
|
28
|
+
less_than_or_equal_to: 6
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
validates :target_items,
|
|
32
|
+
presence: true,
|
|
33
|
+
numericality: {
|
|
34
|
+
only_integer: true,
|
|
35
|
+
greater_than_or_equal_to: 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
delegate :categories, to: :current_participatory_space
|
|
39
|
+
delegate :current_participatory_space, to: :context
|
|
40
|
+
delegate :current_feature, to: :context
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
module Admin
|
|
6
|
+
module SortitionsHelper
|
|
7
|
+
include Decidim::TranslationsHelper
|
|
8
|
+
|
|
9
|
+
# Converst a list of features into a list of selectable options
|
|
10
|
+
def features_options(features)
|
|
11
|
+
features.map do |f|
|
|
12
|
+
[translated_attribute(f.name), f.id]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def sortition_category(sortition)
|
|
17
|
+
return translated_attribute sortition.category&.name if sortition.category
|
|
18
|
+
|
|
19
|
+
I18n.t("sortitions.form.all_categories", scope: "decidim.sortitions.admin")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
module SortitionsHelper
|
|
6
|
+
include Decidim::TranslationsHelper
|
|
7
|
+
|
|
8
|
+
def proposal_path(proposal)
|
|
9
|
+
EngineRouter.main_proxy(proposal.feature).proposal_path(proposal)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Generates the sortition category label
|
|
13
|
+
def sortition_category_label(sortition)
|
|
14
|
+
if sortition.category.present?
|
|
15
|
+
return I18n.t("show.category",
|
|
16
|
+
scope: "decidim.sortitions.sortitions",
|
|
17
|
+
category: translated_attribute(sortition.category.name))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
I18n.t("show.any_category", scope: "decidim.sortitions.sortitions")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Show list of candidate proposals for a sortition. Selected sortition ids will appear with bold font.
|
|
24
|
+
def sortition_proposal_candidate_ids(sortition)
|
|
25
|
+
result = []
|
|
26
|
+
sortition.candidate_proposals.each do |proposal_id|
|
|
27
|
+
result << if sortition.selected_proposals.include? proposal_id
|
|
28
|
+
"<b>#{proposal_id}</b>"
|
|
29
|
+
else
|
|
30
|
+
proposal_id.to_s
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
result.join(", ").html_safe
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
module Abilities
|
|
6
|
+
module Admin
|
|
7
|
+
# Defines the abilities for a user in the admin section. Intended to be
|
|
8
|
+
# used with `cancancan`.
|
|
9
|
+
class AdminAbility < Decidim::Abilities::AdminAbility
|
|
10
|
+
def define_abilities
|
|
11
|
+
super
|
|
12
|
+
|
|
13
|
+
can :manage, Sortition
|
|
14
|
+
cannot :destroy, Sortition
|
|
15
|
+
can :destroy, Sortition do |sortition|
|
|
16
|
+
!sortition.cancelled?
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
module Abilities
|
|
6
|
+
module Admin
|
|
7
|
+
class ProcessAdminAbility
|
|
8
|
+
include CanCan::Ability
|
|
9
|
+
|
|
10
|
+
attr_reader :user, :context
|
|
11
|
+
|
|
12
|
+
def initialize(user, context)
|
|
13
|
+
return unless user
|
|
14
|
+
return if user.admin?
|
|
15
|
+
|
|
16
|
+
@user = user
|
|
17
|
+
@context = context
|
|
18
|
+
|
|
19
|
+
return unless process_administrator?
|
|
20
|
+
|
|
21
|
+
can :manage, Sortition
|
|
22
|
+
cannot :destroy, Sortition
|
|
23
|
+
can :destroy, Sortition do |sortition|
|
|
24
|
+
!sortition.cancelled?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def process_administrator?
|
|
31
|
+
return false unless current_participatory_space.is_a? Decidim::ParticipatoryProcess
|
|
32
|
+
|
|
33
|
+
Decidim::ParticipatoryProcesses::Admin::AdminUsers
|
|
34
|
+
.for(current_participatory_space)
|
|
35
|
+
.where(id: user.id)
|
|
36
|
+
.any?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def current_participatory_space
|
|
40
|
+
@current_participatory_space ||= @context[:current_participatory_space]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
# Model that encapsulates the parameters of a sortion
|
|
6
|
+
class Sortition < ApplicationRecord
|
|
7
|
+
include Decidim::Resourceable
|
|
8
|
+
include Decidim::HasCategory
|
|
9
|
+
include Decidim::Authorable
|
|
10
|
+
include Decidim::HasFeature
|
|
11
|
+
include Decidim::HasReference
|
|
12
|
+
include Decidim::Comments::Commentable
|
|
13
|
+
|
|
14
|
+
feature_manifest_name "sortitions"
|
|
15
|
+
|
|
16
|
+
belongs_to :decidim_proposals_feature,
|
|
17
|
+
foreign_key: "decidim_proposals_feature_id",
|
|
18
|
+
class_name: "Decidim::Feature"
|
|
19
|
+
|
|
20
|
+
belongs_to :cancelled_by_user,
|
|
21
|
+
foreign_key: "cancelled_by_user_id",
|
|
22
|
+
class_name: "Decidim::User",
|
|
23
|
+
optional: true
|
|
24
|
+
|
|
25
|
+
scope :categorized_as, lambda { |category_id|
|
|
26
|
+
includes(:categorization)
|
|
27
|
+
.where("decidim_categorizations.decidim_category_id" => category_id)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
scope :active, -> { where(cancelled_on: nil) }
|
|
31
|
+
scope :cancelled, -> { where.not(cancelled_on: nil) }
|
|
32
|
+
|
|
33
|
+
def proposals
|
|
34
|
+
Decidim::Proposals::Proposal.where(id: selected_proposals)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def similar_count
|
|
38
|
+
Sortition.where(feature: feature)
|
|
39
|
+
.where(decidim_proposals_feature: decidim_proposals_feature)
|
|
40
|
+
.categorized_as(category&.id)
|
|
41
|
+
.where(target_items: target_items)
|
|
42
|
+
.count
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def seed
|
|
46
|
+
request_timestamp.to_i * dice
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def cancelled?
|
|
50
|
+
cancelled_on.present?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Public: Overrides the `commentable?` Commentable concern method.
|
|
54
|
+
def commentable?
|
|
55
|
+
feature.settings.comments_enabled?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Public: Overrides the `accepts_new_comments?` Commentable concern method.
|
|
59
|
+
def accepts_new_comments?
|
|
60
|
+
commentable?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Public: Overrides the `comments_have_alignment?` Commentable concern method.
|
|
64
|
+
def comments_have_alignment?
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Public: Overrides the `comments_have_votes?` Commentable concern method.
|
|
69
|
+
def comments_have_votes?
|
|
70
|
+
true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.order_randomly(seed)
|
|
74
|
+
transaction do
|
|
75
|
+
connection.execute("SELECT setseed(#{connection.quote(seed)})")
|
|
76
|
+
order("RANDOM()").load
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Sortitions
|
|
5
|
+
module Admin
|
|
6
|
+
# Query that retrieves a list of proposal features
|
|
7
|
+
class ParticipatorySpaceProposalFeatures < Rectify::Query
|
|
8
|
+
attr_reader :participatory_space
|
|
9
|
+
|
|
10
|
+
# Sugar syntax. Allow retrieving all proposal features for the
|
|
11
|
+
# given participatory space.
|
|
12
|
+
def self.for(participatory_space)
|
|
13
|
+
new(participatory_space).query
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Initializes the query
|
|
17
|
+
def initialize(participatory_space)
|
|
18
|
+
@participatory_space = participatory_space
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def query
|
|
22
|
+
Feature
|
|
23
|
+
.where(participatory_space: participatory_space, manifest_name: "proposals")
|
|
24
|
+
.published
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|