decidim-proposals 0.0.1
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/README.md +24 -0
- data/Rakefile +2 -0
- data/app/commands/decidim/proposals/admin/create_proposal.rb +43 -0
- data/app/commands/decidim/proposals/create_proposal.rb +42 -0
- data/app/controllers/decidim/proposals/admin/application_controller.rb +14 -0
- data/app/controllers/decidim/proposals/admin/proposals_controller.rb +37 -0
- data/app/controllers/decidim/proposals/application_controller.rb +13 -0
- data/app/controllers/decidim/proposals/proposals_controller.rb +41 -0
- data/app/forms/decidim/proposals/admin/proposal_form.rb +38 -0
- data/app/forms/decidim/proposals/proposal_form.rb +37 -0
- data/app/helpers/decidim/proposals/application_helper.rb +10 -0
- data/app/models/decidim/proposals/application_record.rb +9 -0
- data/app/models/decidim/proposals/proposal.rb +53 -0
- data/app/services/decidim/proposals/proposal_search.rb +36 -0
- data/app/views/decidim/proposals/admin/proposals/_form.html.erb +19 -0
- data/app/views/decidim/proposals/admin/proposals/index.html.erb +34 -0
- data/app/views/decidim/proposals/admin/proposals/new.html.erb +9 -0
- data/app/views/decidim/proposals/proposals/_proposal.html.erb +34 -0
- data/app/views/decidim/proposals/proposals/index.html.erb +23 -0
- data/app/views/decidim/proposals/proposals/new.html.erb +40 -0
- data/app/views/decidim/proposals/proposals/show.html.erb +37 -0
- data/config/i18n-tasks.yml +4 -0
- data/config/locales/ca.yml +46 -0
- data/config/locales/en.yml +47 -0
- data/config/locales/es.yml +46 -0
- data/db/migrate/20161212110850_create_decidim_proposals.rb +14 -0
- data/lib/decidim/proposals.rb +12 -0
- data/lib/decidim/proposals/admin.rb +9 -0
- data/lib/decidim/proposals/admin_engine.rb +20 -0
- data/lib/decidim/proposals/engine.rb +18 -0
- data/lib/decidim/proposals/feature.rb +35 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4be5f073d284da3f8ac70f23505dc51a15b6b888
|
4
|
+
data.tar.gz: b6e8be99a9db14b98eec0246f41648d34bbf3d84
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e7d3f307976ba987e3eb993452f98e58bedb727ae06a7caa81fd0fd0b7a0afc24f59950f999db00a41c103984b4a1d188703ea8d70dc808976a24b270ecd0867
|
7
|
+
data.tar.gz: 82905c51a3d79b6fb87579567dbb717fc3e359ff4088c2992fc1e53ba5341e77411e53f0c1f25f4d5a0b23fdee426cd75d38b001973cbde4cc6d6b96e8d06812
|
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Decidim::Proposals
|
2
|
+
|
3
|
+
The Proposals module adds one of the main features of Decidim: allows users to contribute to a particiaptory process by creating proposals.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
Proposals will be available as a Feature for a Participatory Process.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'decidim-proposals
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
```bash
|
17
|
+
$ bundle
|
18
|
+
```
|
19
|
+
|
20
|
+
## Contributing
|
21
|
+
See [Decidim](https://github.com/AjuntamentdeBarcelona/decidim).
|
22
|
+
|
23
|
+
## License
|
24
|
+
See [Decidim](https://github.com/AjuntamentdeBarcelona/decidim).
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Proposals
|
4
|
+
module Admin
|
5
|
+
# A command with all the business logic when a user creates a new proposal.
|
6
|
+
class CreateProposal < Rectify::Command
|
7
|
+
# Public: Initializes the command.
|
8
|
+
#
|
9
|
+
# form - A form object with the params.
|
10
|
+
def initialize(form)
|
11
|
+
@form = form
|
12
|
+
end
|
13
|
+
|
14
|
+
# Executes the command. Broadcasts these events:
|
15
|
+
#
|
16
|
+
# - :ok when everything is valid, together with the proposal.
|
17
|
+
# - :invalid if the form wasn't valid and we couldn't proceed.
|
18
|
+
#
|
19
|
+
# Returns nothing.
|
20
|
+
def call
|
21
|
+
return broadcast(:invalid) if form.invalid?
|
22
|
+
|
23
|
+
create_proposal
|
24
|
+
broadcast(:ok)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :form, :proposal
|
30
|
+
|
31
|
+
def create_proposal
|
32
|
+
@proposal = Proposal.create!(
|
33
|
+
title: form.title,
|
34
|
+
body: form.body,
|
35
|
+
category: form.category,
|
36
|
+
scope: form.scope,
|
37
|
+
feature: form.feature
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Proposals
|
4
|
+
# A command with all the business logic when a user creates a new proposal.
|
5
|
+
class CreateProposal < Rectify::Command
|
6
|
+
# Public: Initializes the command.
|
7
|
+
#
|
8
|
+
# form - A form object with the params.
|
9
|
+
def initialize(form)
|
10
|
+
@form = form
|
11
|
+
end
|
12
|
+
|
13
|
+
# Executes the command. Broadcasts these events:
|
14
|
+
#
|
15
|
+
# - :ok when everything is valid, together with the proposal.
|
16
|
+
# - :invalid if the form wasn't valid and we couldn't proceed.
|
17
|
+
#
|
18
|
+
# Returns nothing.
|
19
|
+
def call
|
20
|
+
return broadcast(:invalid) if form.invalid?
|
21
|
+
|
22
|
+
create_proposal
|
23
|
+
broadcast(:ok, proposal)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :form, :proposal
|
29
|
+
|
30
|
+
def create_proposal
|
31
|
+
@proposal = Proposal.create!(
|
32
|
+
title: form.title,
|
33
|
+
body: form.body,
|
34
|
+
category: form.category,
|
35
|
+
scope: form.scope,
|
36
|
+
author: form.author,
|
37
|
+
feature: form.feature
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Proposals
|
4
|
+
module Admin
|
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::Admin::Features::BaseController`, which
|
9
|
+
# override its layout and provide all kinds of useful methods.
|
10
|
+
class ApplicationController < Decidim::Admin::Features::BaseController
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Proposals
|
4
|
+
module Admin
|
5
|
+
# This controller allows admins to manage proposals in a participatory process.
|
6
|
+
class ProposalsController < Admin::ApplicationController
|
7
|
+
helper_method :proposals
|
8
|
+
|
9
|
+
def new
|
10
|
+
@form = form(ProposalForm).from_params({}, feature: current_feature)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
@form = form(ProposalForm).from_params(params, feature: current_feature)
|
15
|
+
|
16
|
+
CreateProposal.call(@form) do
|
17
|
+
on(:ok) do
|
18
|
+
flash[:notice] = I18n.t("proposals.create.success", scope: "decidim.proposals.admin")
|
19
|
+
redirect_to proposals_path
|
20
|
+
end
|
21
|
+
|
22
|
+
on(:invalid) do
|
23
|
+
flash.now[:alert] = I18n.t("proposals.create.invalid", scope: "decidim.proposals.admin")
|
24
|
+
render action: "new"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def proposals
|
32
|
+
@proposals ||= Proposal.where(feature: current_feature)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
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,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
# Exposes the proposal resource so users can view and create them.
|
6
|
+
class ProposalsController < Decidim::Proposals::ApplicationController
|
7
|
+
include FormFactory
|
8
|
+
before_action :authenticate_user!, only: [:new, :create]
|
9
|
+
|
10
|
+
def show
|
11
|
+
@proposal = Proposal.where(feature: current_feature).find(params[:id])
|
12
|
+
end
|
13
|
+
|
14
|
+
def index
|
15
|
+
@search = ProposalSearch.new(current_feature, params[:page], params[:random_seed])
|
16
|
+
@proposals = @search.proposals
|
17
|
+
@random_seed = @search.random_seed
|
18
|
+
end
|
19
|
+
|
20
|
+
def new
|
21
|
+
@form = form(ProposalForm).from_params({}, author: current_user, feature: current_feature)
|
22
|
+
end
|
23
|
+
|
24
|
+
def create
|
25
|
+
@form = form(ProposalForm).from_params(params, author: current_user, feature: current_feature)
|
26
|
+
|
27
|
+
CreateProposal.call(@form) do
|
28
|
+
on(:ok) do |proposal|
|
29
|
+
flash[:notice] = I18n.t("proposals.create.success", scope: "decidim")
|
30
|
+
redirect_to proposal_path(proposal)
|
31
|
+
end
|
32
|
+
|
33
|
+
on(:invalid) do
|
34
|
+
flash.now[:alert] = I18n.t("proposals.create.error", scope: "decidim")
|
35
|
+
render :new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Proposals
|
4
|
+
module Admin
|
5
|
+
# A form object to be used when admin users want to create a proposal.
|
6
|
+
class ProposalForm < Decidim::Form
|
7
|
+
mimic :proposal
|
8
|
+
|
9
|
+
attribute :title, String
|
10
|
+
attribute :body, String
|
11
|
+
attribute :category_id, Integer
|
12
|
+
attribute :scope_id, Integer
|
13
|
+
attribute :feature, Decidim::Feature
|
14
|
+
|
15
|
+
validates :title, :body, :feature, presence: true
|
16
|
+
validates :category, presence: true, if: ->(form) { form.category_id.present? }
|
17
|
+
validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
|
18
|
+
|
19
|
+
delegate :categories, to: :feature
|
20
|
+
delegate :scopes, to: :current_organization
|
21
|
+
|
22
|
+
# Finds the Category from the category_id.
|
23
|
+
#
|
24
|
+
# Returns a Decidim::Category
|
25
|
+
def category
|
26
|
+
@category ||= feature.categories.where(id: category_id).first
|
27
|
+
end
|
28
|
+
|
29
|
+
# Finds the Scope from the scope_id.
|
30
|
+
#
|
31
|
+
# Returns a Decidim::Scope
|
32
|
+
def scope
|
33
|
+
@scope ||= feature.scopes.where(id: scope_id).first
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Proposals
|
4
|
+
# A form object to be used when public users want to create a proposal.
|
5
|
+
class ProposalForm < Decidim::Form
|
6
|
+
mimic :proposal
|
7
|
+
|
8
|
+
attribute :title, String
|
9
|
+
attribute :body, String
|
10
|
+
attribute :author, Decidim::User
|
11
|
+
attribute :category_id, Integer
|
12
|
+
attribute :scope_id, Integer
|
13
|
+
attribute :feature, Decidim::Feature
|
14
|
+
|
15
|
+
validates :title, :body, :author, :feature, presence: true
|
16
|
+
validates :category, presence: true, if: ->(form) { form.category_id.present? }
|
17
|
+
validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
|
18
|
+
|
19
|
+
delegate :categories, to: :feature
|
20
|
+
delegate :scopes, to: :current_organization
|
21
|
+
|
22
|
+
# Finds the Category from the category_id.
|
23
|
+
#
|
24
|
+
# Returns a Decidim::Category
|
25
|
+
def category
|
26
|
+
@category ||= feature.categories.where(id: category_id).first
|
27
|
+
end
|
28
|
+
|
29
|
+
# Finds the Scope from the scope_id.
|
30
|
+
#
|
31
|
+
# Returns a Decidim::Scope
|
32
|
+
def scope
|
33
|
+
@scope ||= feature.scopes.where(id: scope_id).first
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Proposals
|
4
|
+
# The data store for a Proposal in the Decidim::Proposals component.
|
5
|
+
class Proposal < Proposals::ApplicationRecord
|
6
|
+
belongs_to :feature, foreign_key: "decidim_feature_id", class_name: Decidim::Feature
|
7
|
+
belongs_to :author, foreign_key: "decidim_author_id", class_name: Decidim::User
|
8
|
+
belongs_to :category, foreign_key: "decidim_category_id", class_name: Decidim::Category
|
9
|
+
belongs_to :scope, foreign_key: "decidim_scope_id", class_name: Decidim::Scope
|
10
|
+
has_one :organization, through: :feature
|
11
|
+
|
12
|
+
validates :title, :feature, :body, presence: true
|
13
|
+
validate :category_belongs_to_feature
|
14
|
+
validate :scope_belongs_to_organization
|
15
|
+
validate :author_belongs_to_organization
|
16
|
+
|
17
|
+
def author_name
|
18
|
+
author&.name || I18n.t("decidim.proposals.models.proposal.fields.official_proposal")
|
19
|
+
end
|
20
|
+
|
21
|
+
def author_avatar_url
|
22
|
+
author&.avatar&.url || ActionController::Base.helpers.asset_path("decidim/default-avatar.svg")
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Canpeople comment on this proposal?
|
26
|
+
#
|
27
|
+
# Until we have a way to store options fore features and its resources we
|
28
|
+
# assume all proposals can be commented.
|
29
|
+
#
|
30
|
+
# Returns Boolean
|
31
|
+
def commentable?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def category_belongs_to_feature
|
38
|
+
return unless category
|
39
|
+
errors.add(:category, :invalid) unless feature.categories.where(id: category.id).exists?
|
40
|
+
end
|
41
|
+
|
42
|
+
def scope_belongs_to_organization
|
43
|
+
return unless scope
|
44
|
+
errors.add(:scope, :invalid) unless feature.scopes.where(id: scope.id).exists?
|
45
|
+
end
|
46
|
+
|
47
|
+
def author_belongs_to_organization
|
48
|
+
return unless author
|
49
|
+
errors.add(:author, :invalid) unless Decidim::User.where(decidim_organization_id: feature.organization.id, id: author.id).exists?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Proposals
|
4
|
+
# A service to encapsualte all the logic when searching and filtering
|
5
|
+
# proposals in a participatory process.
|
6
|
+
class ProposalSearch
|
7
|
+
attr_reader :feature, :page, :per_page
|
8
|
+
|
9
|
+
# Public: Initializes the service.
|
10
|
+
# feature - A Decidim::Feature to get the proposals from.
|
11
|
+
# page - The page number to paginate the results.
|
12
|
+
# random_seed - A random flaot number between -1 and 1 to be used as a random seed at the database.
|
13
|
+
# per_page - The number of proposals to return per page.
|
14
|
+
def initialize(feature, page = nil, random_seed = nil, per_page = nil)
|
15
|
+
@feature = feature
|
16
|
+
@page = (page || 1).to_i
|
17
|
+
@per_page = (per_page || 12).to_i
|
18
|
+
@random_seed = random_seed.to_f
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the random proposals for the current page.
|
22
|
+
def proposals
|
23
|
+
@proposals ||= Proposal.transaction do
|
24
|
+
Proposal.connection.execute("SELECT setseed(#{Proposal.connection.quote(random_seed)})")
|
25
|
+
Proposal.where(feature: feature).reorder("RANDOM()").page(page).per(per_page).load
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the random seed used to randomize the proposals.
|
30
|
+
def random_seed
|
31
|
+
@random_seed = (rand * 2 - 1) if @random_seed == 0.0 || @random_seed > 1 || @random_seed < -1
|
32
|
+
@random_seed
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<div class="field">
|
2
|
+
<%= form.text_field :title %>
|
3
|
+
</div>
|
4
|
+
|
5
|
+
<div class="field">
|
6
|
+
<%= form.text_area :body, rows: 10 %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<% if @form.categories&.any? %>
|
10
|
+
<div class="field">
|
11
|
+
<%= form.categories_select :category_id, @form.categories, t(".select_a_category") %>
|
12
|
+
</div>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<% if @form.scopes&.any? %>
|
16
|
+
<div class="field">
|
17
|
+
<%= form.select :scope_id, @form.scopes.map{|s| [translated_attribute(s.name), s.id]}, prompt: t(".select_a_scope") %>
|
18
|
+
</div>
|
19
|
+
<% end %>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<h2><%= t(".title") %></h2>
|
2
|
+
|
3
|
+
<div class="actions title">
|
4
|
+
<%= link_to t("actions.new", scope: "decidim.proposals", name: t("models.proposal.name", scope: "decidim.proposals.admin")), new_proposal_path, class: 'new' if can? :manage, current_feature %>
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<table class="stack">
|
8
|
+
<thead>
|
9
|
+
<tr>
|
10
|
+
<th><%= t("models.proposal.fields.title", scope: "decidim.proposals") %></th>
|
11
|
+
<th><%= t("models.proposal.fields.category", scope: "decidim.proposals") %></th>
|
12
|
+
<th><%= t("models.proposal.fields.scope", scope: "decidim.proposals") %></th>
|
13
|
+
</tr>
|
14
|
+
</thead>
|
15
|
+
<tbody>
|
16
|
+
<% proposals.each do |proposal| %>
|
17
|
+
<tr data-id="<%= proposal.id %>">
|
18
|
+
<td>
|
19
|
+
<%= link_to proposal.title, decidim_proposals.proposal_path(id: proposal, feature_id: current_feature, participatory_process_id: current_participatory_process), target: :blank %><br />
|
20
|
+
</td>
|
21
|
+
<td>
|
22
|
+
<% if proposal.category %>
|
23
|
+
<%= translated_attribute proposal.category.name %>
|
24
|
+
<% end %>
|
25
|
+
</td>
|
26
|
+
<td>
|
27
|
+
<% if proposal.scope %>
|
28
|
+
<%= translated_attribute proposal.scope.name %>
|
29
|
+
<% end %>
|
30
|
+
</td>
|
31
|
+
</tr>
|
32
|
+
<% end %>
|
33
|
+
</tbody>
|
34
|
+
</table>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<div class="column">
|
2
|
+
<article class="card card--proposal">
|
3
|
+
<div class="card__content">
|
4
|
+
<div class="card__header">
|
5
|
+
<%= link_to proposal do%>
|
6
|
+
<h5 class="card__title"><%= proposal.title %></h5>
|
7
|
+
<% end %>
|
8
|
+
<div class="card__author author-data author-data--small">
|
9
|
+
<div class="author-data__main">
|
10
|
+
<div class="author author--inline">
|
11
|
+
<span class="author__avatar author__avatar--small">
|
12
|
+
<%= image_tag proposal.author_avatar_url %>
|
13
|
+
</span>
|
14
|
+
<span class="author__name"><%= proposal.author_name %></span>
|
15
|
+
<%= l proposal.created_at, format: "%d/%m/%Y" %>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
<p><%= truncate(proposal.body, length: 100) %></p>
|
21
|
+
<% if proposal.category %>
|
22
|
+
<ul class="tags tags--proposal">
|
23
|
+
<li><a href=""><%= translated_attribute(proposal.category.name) %></a></li>
|
24
|
+
</ul>
|
25
|
+
<% end %>
|
26
|
+
</div>
|
27
|
+
<div class="card__footer">
|
28
|
+
<div class="card__support">
|
29
|
+
<div class="card__support__data"></div>
|
30
|
+
<%= link_to t(".view_proposal"), proposal, class: "card__button button small secondary" %>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</article>
|
34
|
+
</div>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<div class="row columns">
|
2
|
+
<div class="title-action">
|
3
|
+
<h2 class="title-action__title section-heading"><%= t(".proposals_count", count: @proposals.total_count) %></h2>
|
4
|
+
<%= link_to new_proposal_path, class: "title-action__action button small hollow" do %>
|
5
|
+
<%= t(".new_proposal") %>
|
6
|
+
<%= icon "plus" %>
|
7
|
+
<% end %>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
<div class="row">
|
11
|
+
<div class="columns mediumlarge-12 large-12">
|
12
|
+
<div class="row small-up-1 medium-up-3 card-grid">
|
13
|
+
<%= render @proposals %>
|
14
|
+
</div>
|
15
|
+
<%
|
16
|
+
# Kaminari uses url_for to generate the url, but this doesn't play nice with our engine system
|
17
|
+
# and unless we remove these params they are added again as query string 😩
|
18
|
+
params.delete("participatory_process_id")
|
19
|
+
params.delete("feature_id")
|
20
|
+
%>
|
21
|
+
<%= paginate @proposals, theme: "decidim", params: { random_seed: @random_seed } %>
|
22
|
+
</div>
|
23
|
+
</div>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<div class="row columns">
|
2
|
+
<%= link_to :back, class: "muted-link" do %>
|
3
|
+
<%= icon "chevron-left", class: "icon--small" %>
|
4
|
+
<%= t(".back") %>
|
5
|
+
<% end %>
|
6
|
+
<h2 class="section-heading"><%= t(".title") %></h2>
|
7
|
+
</div>
|
8
|
+
<div class="row">
|
9
|
+
<div class="columns large-6 medium-centered">
|
10
|
+
<div class="card">
|
11
|
+
<div class="card__content">
|
12
|
+
<%= form_for(@form) do |form| %>
|
13
|
+
<div class="field">
|
14
|
+
<%= form.text_field :title %>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div class="field">
|
18
|
+
<%= form.text_area :body, rows: 10 %>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<% if @form.categories&.any? %>
|
22
|
+
<div class="field">
|
23
|
+
<%= form.categories_select :category_id, @form.categories, prompt: t(".select_a_category") %>
|
24
|
+
</div>
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
<% if @form.scopes&.any? %>
|
28
|
+
<div class="field">
|
29
|
+
<%= form.select :scope_id, @form.scopes.map{|s| [s.name, s.id]}, prompt: t(".select_a_scope") %>
|
30
|
+
</div>
|
31
|
+
<% end %>
|
32
|
+
|
33
|
+
<div class="actions">
|
34
|
+
<%= form.submit t(".send"), class: "button expanded" %>
|
35
|
+
</div>
|
36
|
+
<% end %>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
</div>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<div class="row column view-header">
|
2
|
+
<h2 class="heading2"><%= @proposal.title %></h2>
|
3
|
+
<div class="author-data">
|
4
|
+
<div class="author-data__main">
|
5
|
+
<div class="author author--inline">
|
6
|
+
<span class="author__avatar">
|
7
|
+
<%= image_tag @proposal.author_avatar_url %>
|
8
|
+
</span>
|
9
|
+
<%= l @proposal.created_at, format: :long %>
|
10
|
+
<span class="author__name">
|
11
|
+
<%= @proposal.author_name %>
|
12
|
+
</span>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
<div class="row">
|
18
|
+
<div class="columns section view-side mediumlarge-4 mediumlarge-push-8 large-3 large-push-9">
|
19
|
+
</div>
|
20
|
+
<div class="columns mediumlarge-8 mediumlarge-pull-4">
|
21
|
+
<div class="section">
|
22
|
+
<p><%= @proposal.body %></p>
|
23
|
+
<% if @proposal.category %>
|
24
|
+
<ul class="tags tags--proposal">
|
25
|
+
<li><a href=""><%= translated_attribute(@proposal.category.name) %></a></li>
|
26
|
+
</ul>
|
27
|
+
<% end %>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
|
33
|
+
<%= content_for :expanded do %>
|
34
|
+
<% if @proposal.commentable? %>
|
35
|
+
<%= comments_for @proposal, arguable: true %>
|
36
|
+
<% end %>
|
37
|
+
<% end %>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
ca:
|
2
|
+
decidim:
|
3
|
+
features:
|
4
|
+
proposals:
|
5
|
+
name: Propostes
|
6
|
+
proposals:
|
7
|
+
actions:
|
8
|
+
new: Nova proposta
|
9
|
+
admin:
|
10
|
+
models:
|
11
|
+
proposal:
|
12
|
+
name: Proposta
|
13
|
+
proposals:
|
14
|
+
create:
|
15
|
+
invalid: Hi ha hagut un problema en crear aquesta proposta
|
16
|
+
success: Proposta creada correctament
|
17
|
+
form:
|
18
|
+
select_a_category: Selecciona una categoria
|
19
|
+
select_a_scope: Seleccioneu un àmbit
|
20
|
+
index:
|
21
|
+
title: Propostes
|
22
|
+
new:
|
23
|
+
create: Crear proposta
|
24
|
+
title: Nova proposta
|
25
|
+
create:
|
26
|
+
error: Hi ha hagut errors en desar la proposta.
|
27
|
+
success: Proposta creada correctament.
|
28
|
+
models:
|
29
|
+
proposal:
|
30
|
+
fields:
|
31
|
+
category: Categoria
|
32
|
+
official_proposal: Proposta oficial
|
33
|
+
scope: Àmbit
|
34
|
+
title: Títol
|
35
|
+
proposals:
|
36
|
+
index:
|
37
|
+
new_proposal: Nova proposta
|
38
|
+
proposals_count: "%{count} propostes"
|
39
|
+
new:
|
40
|
+
back: Enrere
|
41
|
+
select_a_category: Si us plau, seleccioni una categoria
|
42
|
+
select_a_scope: Si us plau, seleccioni un àmbit
|
43
|
+
send: Enviar
|
44
|
+
title: Nova proposta
|
45
|
+
proposal:
|
46
|
+
view_proposal: Veure proposta
|
@@ -0,0 +1,47 @@
|
|
1
|
+
---
|
2
|
+
en:
|
3
|
+
decidim:
|
4
|
+
features:
|
5
|
+
proposals:
|
6
|
+
name: Proposals
|
7
|
+
proposals:
|
8
|
+
actions:
|
9
|
+
new: New proposal
|
10
|
+
admin:
|
11
|
+
models:
|
12
|
+
proposal:
|
13
|
+
name: Proposal
|
14
|
+
proposals:
|
15
|
+
create:
|
16
|
+
invalid: There's been a problem creating this proposal
|
17
|
+
success: Proposal successfully created
|
18
|
+
form:
|
19
|
+
select_a_category: Select a category
|
20
|
+
select_a_scope: Select a scope
|
21
|
+
index:
|
22
|
+
title: Proposals
|
23
|
+
new:
|
24
|
+
create: Create proposal
|
25
|
+
title: New proposal
|
26
|
+
create:
|
27
|
+
error: There's been errors when saving the proposal.
|
28
|
+
success: Proposal created successfully.
|
29
|
+
models:
|
30
|
+
proposal:
|
31
|
+
fields:
|
32
|
+
category: Category
|
33
|
+
official_proposal: Official proposal
|
34
|
+
scope: Scope
|
35
|
+
title: Title
|
36
|
+
proposals:
|
37
|
+
index:
|
38
|
+
new_proposal: New proposal
|
39
|
+
proposals_count: "%{count} proposals"
|
40
|
+
new:
|
41
|
+
back: Back
|
42
|
+
select_a_category: Please select a category
|
43
|
+
select_a_scope: Please select a scope
|
44
|
+
send: Send
|
45
|
+
title: New proposal
|
46
|
+
proposal:
|
47
|
+
view_proposal: View proposal
|
@@ -0,0 +1,46 @@
|
|
1
|
+
es:
|
2
|
+
decidim:
|
3
|
+
features:
|
4
|
+
proposals:
|
5
|
+
name: Propuestas
|
6
|
+
proposals:
|
7
|
+
actions:
|
8
|
+
new: Nueva propuesta
|
9
|
+
admin:
|
10
|
+
models:
|
11
|
+
proposal:
|
12
|
+
name: Propuesta
|
13
|
+
proposals:
|
14
|
+
create:
|
15
|
+
invalid: Ha habido un problema al crear esta propuesta
|
16
|
+
success: Propuesta creada correctamente
|
17
|
+
form:
|
18
|
+
select_a_category: Seleccione una categoría
|
19
|
+
select_a_scope: Seleccione un ámbito
|
20
|
+
index:
|
21
|
+
title: Propuestas
|
22
|
+
new:
|
23
|
+
create: Crear propuesta
|
24
|
+
title: Nueva propuesta
|
25
|
+
create:
|
26
|
+
error: Ha habido errores al guardar la propuesta.
|
27
|
+
success: Propuesta creada correctamente.
|
28
|
+
models:
|
29
|
+
proposal:
|
30
|
+
fields:
|
31
|
+
category: Categoría
|
32
|
+
official_proposal: Propuesta oficial
|
33
|
+
scope: Ámbito
|
34
|
+
title: Título
|
35
|
+
proposals:
|
36
|
+
index:
|
37
|
+
new_proposal: Nueva propuesta
|
38
|
+
proposals_count: "%{count} propuestas"
|
39
|
+
new:
|
40
|
+
back: Atrás
|
41
|
+
select_a_category: Por favor, seleccione una categoría
|
42
|
+
select_a_scope: Por favor, seleccione un ámbito
|
43
|
+
send: Enviar
|
44
|
+
title: Nueva propuesta
|
45
|
+
proposal:
|
46
|
+
view_proposal: Ver propuesta
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateDecidimProposals < ActiveRecord::Migration[5.0]
|
2
|
+
def change
|
3
|
+
create_table :decidim_proposals_proposals do |t|
|
4
|
+
t.text :title, null: false
|
5
|
+
t.text :body, null: false
|
6
|
+
t.references :decidim_feature, index: true, null: false
|
7
|
+
t.references :decidim_author, index: true
|
8
|
+
t.references :decidim_category, index: true
|
9
|
+
t.references :decidim_scope, index: true
|
10
|
+
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "decidim/proposals/admin"
|
3
|
+
require "decidim/proposals/engine"
|
4
|
+
require "decidim/proposals/admin_engine"
|
5
|
+
require "decidim/proposals/feature"
|
6
|
+
|
7
|
+
module Decidim
|
8
|
+
# This namespace holds the logic of the `Proposals` component. This component
|
9
|
+
# allows users to create proposals in a participatory process.
|
10
|
+
module Proposals
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Proposals
|
4
|
+
# This is the engine that runs on the public interface of `decidim-proposals`.
|
5
|
+
class AdminEngine < ::Rails::Engine
|
6
|
+
isolate_namespace Decidim::Proposals::Admin
|
7
|
+
|
8
|
+
paths["db/migrate"] = nil
|
9
|
+
|
10
|
+
routes do
|
11
|
+
resources :proposals, only: [:index, :new, :create]
|
12
|
+
root to: "proposals#index"
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_seed
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "kaminari"
|
3
|
+
|
4
|
+
module Decidim
|
5
|
+
module Proposals
|
6
|
+
# This is the engine that runs on the public interface of `decidim-proposals`.
|
7
|
+
# It mostly handles rendering the created page associated to a participatory
|
8
|
+
# process.
|
9
|
+
class Engine < ::Rails::Engine
|
10
|
+
isolate_namespace Decidim::Proposals
|
11
|
+
|
12
|
+
routes do
|
13
|
+
resources :proposals, only: [:create, :new, :index, :show]
|
14
|
+
root to: "proposals#index"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_dependency "decidim/features/namer"
|
4
|
+
|
5
|
+
Decidim.register_feature(:proposals) do |feature|
|
6
|
+
feature.engine = Decidim::Proposals::Engine
|
7
|
+
feature.admin_engine = Decidim::Proposals::AdminEngine
|
8
|
+
|
9
|
+
feature.on(:destroy) do |instance|
|
10
|
+
if Decidim::Proposals::Proposal.where(feature: instance).any?
|
11
|
+
raise "Can't destroy this feature when there are proposals"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
feature.seeds do
|
16
|
+
Decidim::ParticipatoryProcess.all.each do |process|
|
17
|
+
next unless process.steps.any?
|
18
|
+
|
19
|
+
feature = Decidim::Feature.create!(
|
20
|
+
name: Decidim::Features::Namer.new(process.organization.available_locales, :proposals).i18n_name,
|
21
|
+
manifest_name: :proposals,
|
22
|
+
participatory_process: process
|
23
|
+
)
|
24
|
+
|
25
|
+
20.times do
|
26
|
+
Decidim::Proposals::Proposal.create!(
|
27
|
+
feature: feature,
|
28
|
+
title: Faker::Lorem.sentence(2),
|
29
|
+
body: Faker::Lorem.paragraphs(2).join("\n"),
|
30
|
+
author: Decidim::User.where(organization: feature.organization).all.sample
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: decidim-proposals
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Josep Jaume Rey Peroy
|
8
|
+
- Marc Riera Casals
|
9
|
+
- Oriol Gual Oliva
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2016-12-21 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: decidim-core
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.0.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - '='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 0.0.1
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: decidim-comments
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - '='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.0.1
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - '='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.0.1
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: rectify
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.8.0
|
50
|
+
type: :runtime
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - "~>"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.8.0
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: kaminari
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 1.0.0.rc1
|
64
|
+
type: :runtime
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 1.0.0.rc1
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: decidim-dev
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - '='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.0.1
|
78
|
+
type: :development
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - '='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 0.0.1
|
85
|
+
description: A proposals component for decidim's participatory processes.
|
86
|
+
email:
|
87
|
+
- josepjaume@gmail.com
|
88
|
+
- mrc2407@gmail.com
|
89
|
+
- oriolgual@gmail.com
|
90
|
+
executables: []
|
91
|
+
extensions: []
|
92
|
+
extra_rdoc_files: []
|
93
|
+
files:
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- app/commands/decidim/proposals/admin/create_proposal.rb
|
97
|
+
- app/commands/decidim/proposals/create_proposal.rb
|
98
|
+
- app/controllers/decidim/proposals/admin/application_controller.rb
|
99
|
+
- app/controllers/decidim/proposals/admin/proposals_controller.rb
|
100
|
+
- app/controllers/decidim/proposals/application_controller.rb
|
101
|
+
- app/controllers/decidim/proposals/proposals_controller.rb
|
102
|
+
- app/forms/decidim/proposals/admin/proposal_form.rb
|
103
|
+
- app/forms/decidim/proposals/proposal_form.rb
|
104
|
+
- app/helpers/decidim/proposals/application_helper.rb
|
105
|
+
- app/models/decidim/proposals/application_record.rb
|
106
|
+
- app/models/decidim/proposals/proposal.rb
|
107
|
+
- app/services/decidim/proposals/proposal_search.rb
|
108
|
+
- app/views/decidim/proposals/admin/proposals/_form.html.erb
|
109
|
+
- app/views/decidim/proposals/admin/proposals/index.html.erb
|
110
|
+
- app/views/decidim/proposals/admin/proposals/new.html.erb
|
111
|
+
- app/views/decidim/proposals/proposals/_proposal.html.erb
|
112
|
+
- app/views/decidim/proposals/proposals/index.html.erb
|
113
|
+
- app/views/decidim/proposals/proposals/new.html.erb
|
114
|
+
- app/views/decidim/proposals/proposals/show.html.erb
|
115
|
+
- config/i18n-tasks.yml
|
116
|
+
- config/locales/ca.yml
|
117
|
+
- config/locales/en.yml
|
118
|
+
- config/locales/es.yml
|
119
|
+
- db/migrate/20161212110850_create_decidim_proposals.rb
|
120
|
+
- lib/decidim/proposals.rb
|
121
|
+
- lib/decidim/proposals/admin.rb
|
122
|
+
- lib/decidim/proposals/admin_engine.rb
|
123
|
+
- lib/decidim/proposals/engine.rb
|
124
|
+
- lib/decidim/proposals/feature.rb
|
125
|
+
homepage: https://github.com/AjuntamentdeBarcelona/decidim
|
126
|
+
licenses:
|
127
|
+
- AGPLv3
|
128
|
+
metadata: {}
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: 2.3.1
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
requirements: []
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 2.5.2
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: A proposals component for decidim's participatory processes.
|
149
|
+
test_files: []
|