decidim-proposals 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|